summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNate Sesti <sestinj@gmail.com>2023-07-24 01:00:42 -0700
committerNate Sesti <sestinj@gmail.com>2023-07-24 01:00:42 -0700
commit85ce06beb9b2d587b0b572117a98318d226bed61 (patch)
tree76fc6232b5219d0bd61b547b26624641a99e7b9b
parent699a74250fd4cf91af930ff63077aeb81f74856f (diff)
parent885f88af1d7b35e03b1de4df3e74a60da1a777ed (diff)
downloadsncontinue-85ce06beb9b2d587b0b572117a98318d226bed61.tar.gz
sncontinue-85ce06beb9b2d587b0b572117a98318d226bed61.tar.bz2
sncontinue-85ce06beb9b2d587b0b572117a98318d226bed61.zip
Merge branch 'main' into show-react-immediately
-rw-r--r--.gitignore2
-rw-r--r--.vscode/launch.json17
-rw-r--r--CONTRIBUTING.md147
-rw-r--r--README.md10
-rw-r--r--continuedev/README.md22
-rw-r--r--continuedev/poetry.lock225
-rw-r--r--continuedev/pyproject.toml5
-rw-r--r--continuedev/src/continuedev/core/autopilot.py43
-rw-r--r--continuedev/src/continuedev/core/config.py21
-rw-r--r--continuedev/src/continuedev/core/main.py7
-rw-r--r--continuedev/src/continuedev/core/policy.py37
-rw-r--r--continuedev/src/continuedev/core/sdk.py127
-rw-r--r--continuedev/src/continuedev/libs/llm/__init__.py6
-rw-r--r--continuedev/src/continuedev/libs/llm/anthropic.py97
-rw-r--r--continuedev/src/continuedev/libs/llm/ggml.py86
-rw-r--r--continuedev/src/continuedev/libs/llm/hf_inference_api.py9
-rw-r--r--continuedev/src/continuedev/libs/llm/openai.py68
-rw-r--r--continuedev/src/continuedev/libs/llm/proxy_server.py75
-rw-r--r--continuedev/src/continuedev/libs/util/commonregex.py138
-rw-r--r--continuedev/src/continuedev/libs/util/count_tokens.py99
-rw-r--r--continuedev/src/continuedev/libs/util/step_name_to_steps.py28
-rw-r--r--continuedev/src/continuedev/libs/util/strings.py (renamed from continuedev/src/continuedev/libs/util/dedent.py)24
-rw-r--r--continuedev/src/continuedev/libs/util/telemetry.py7
-rw-r--r--continuedev/src/continuedev/libs/util/templating.py39
-rw-r--r--continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/README.md (renamed from continuedev/src/continuedev/recipes/AddTransformRecipe/README.md)0
-rw-r--r--continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/dlt_transform_docs.md (renamed from continuedev/src/continuedev/recipes/AddTransformRecipe/dlt_transform_docs.md)0
-rw-r--r--continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/main.py (renamed from continuedev/src/continuedev/recipes/AddTransformRecipe/main.py)8
-rw-r--r--continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/steps.py (renamed from continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py)12
-rw-r--r--continuedev/src/continuedev/plugins/recipes/ContinueRecipeRecipe/README.md (renamed from continuedev/src/continuedev/recipes/ContinueRecipeRecipe/README.md)0
-rw-r--r--continuedev/src/continuedev/plugins/recipes/ContinueRecipeRecipe/main.py (renamed from continuedev/src/continuedev/recipes/ContinueRecipeRecipe/main.py)7
-rw-r--r--continuedev/src/continuedev/plugins/recipes/CreatePipelineRecipe/README.md (renamed from continuedev/src/continuedev/recipes/CreatePipelineRecipe/README.md)0
-rw-r--r--continuedev/src/continuedev/plugins/recipes/CreatePipelineRecipe/main.py (renamed from continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py)8
-rw-r--r--continuedev/src/continuedev/plugins/recipes/CreatePipelineRecipe/steps.py (renamed from continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py)14
-rw-r--r--continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/README.md (renamed from continuedev/src/continuedev/recipes/DDtoBQRecipe/README.md)0
-rw-r--r--continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/dlt_duckdb_to_bigquery_docs.md (renamed from continuedev/src/continuedev/recipes/DDtoBQRecipe/dlt_duckdb_to_bigquery_docs.md)0
-rw-r--r--continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/main.py (renamed from continuedev/src/continuedev/recipes/DDtoBQRecipe/main.py)7
-rw-r--r--continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/steps.py (renamed from continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py)18
-rw-r--r--continuedev/src/continuedev/plugins/recipes/DeployPipelineAirflowRecipe/README.md (renamed from continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/README.md)0
-rw-r--r--continuedev/src/continuedev/plugins/recipes/DeployPipelineAirflowRecipe/main.py (renamed from continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/main.py)9
-rw-r--r--continuedev/src/continuedev/plugins/recipes/DeployPipelineAirflowRecipe/steps.py (renamed from continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py)21
-rw-r--r--continuedev/src/continuedev/plugins/recipes/README.md (renamed from continuedev/src/continuedev/recipes/README.md)2
-rw-r--r--continuedev/src/continuedev/plugins/recipes/TemplateRecipe/README.md (renamed from continuedev/src/continuedev/recipes/TemplateRecipe/README.md)0
-rw-r--r--continuedev/src/continuedev/plugins/recipes/TemplateRecipe/main.py (renamed from continuedev/src/continuedev/recipes/TemplateRecipe/main.py)4
-rw-r--r--continuedev/src/continuedev/plugins/recipes/WritePytestsRecipe/README.md (renamed from continuedev/src/continuedev/recipes/WritePytestsRecipe/README.md)0
-rw-r--r--continuedev/src/continuedev/plugins/recipes/WritePytestsRecipe/main.py (renamed from continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py)5
-rw-r--r--continuedev/src/continuedev/plugins/steps/README.md50
-rw-r--r--continuedev/src/continuedev/plugins/steps/__init__.py (renamed from continuedev/src/continuedev/steps/__init__.py)0
-rw-r--r--continuedev/src/continuedev/plugins/steps/chat.py (renamed from continuedev/src/continuedev/steps/chat.py)32
-rw-r--r--continuedev/src/continuedev/plugins/steps/chroma.py (renamed from continuedev/src/continuedev/steps/chroma.py)8
-rw-r--r--continuedev/src/continuedev/plugins/steps/clear_history.py (renamed from continuedev/src/continuedev/steps/clear_history.py)4
-rw-r--r--continuedev/src/continuedev/plugins/steps/comment_code.py (renamed from continuedev/src/continuedev/steps/comment_code.py)2
-rw-r--r--continuedev/src/continuedev/plugins/steps/core/core.py (renamed from continuedev/src/continuedev/steps/core/core.py)179
-rw-r--r--continuedev/src/continuedev/plugins/steps/custom_command.py (renamed from continuedev/src/continuedev/steps/custom_command.py)12
-rw-r--r--continuedev/src/continuedev/plugins/steps/draft/abstract_method.py (renamed from continuedev/src/continuedev/steps/draft/abstract_method.py)0
-rw-r--r--continuedev/src/continuedev/plugins/steps/draft/migration.py (renamed from continuedev/src/continuedev/steps/draft/migration.py)2
-rw-r--r--continuedev/src/continuedev/plugins/steps/draft/redux.py (renamed from continuedev/src/continuedev/steps/draft/redux.py)8
-rw-r--r--continuedev/src/continuedev/plugins/steps/draft/typeorm.py (renamed from continuedev/src/continuedev/steps/draft/typeorm.py)4
-rw-r--r--continuedev/src/continuedev/plugins/steps/feedback.py (renamed from continuedev/src/continuedev/steps/feedback.py)8
-rw-r--r--continuedev/src/continuedev/plugins/steps/find_and_replace.py (renamed from continuedev/src/continuedev/steps/find_and_replace.py)6
-rw-r--r--continuedev/src/continuedev/plugins/steps/help.py59
-rw-r--r--continuedev/src/continuedev/plugins/steps/input/nl_multiselect.py (renamed from continuedev/src/continuedev/steps/input/nl_multiselect.py)4
-rw-r--r--continuedev/src/continuedev/plugins/steps/main.py (renamed from continuedev/src/continuedev/steps/main.py)26
-rw-r--r--continuedev/src/continuedev/plugins/steps/on_traceback.py (renamed from continuedev/src/continuedev/steps/on_traceback.py)4
-rw-r--r--continuedev/src/continuedev/plugins/steps/open_config.py (renamed from continuedev/src/continuedev/steps/open_config.py)8
-rw-r--r--continuedev/src/continuedev/plugins/steps/react.py (renamed from continuedev/src/continuedev/steps/react.py)5
-rw-r--r--continuedev/src/continuedev/plugins/steps/search_directory.py (renamed from continuedev/src/continuedev/steps/search_directory.py)10
-rw-r--r--continuedev/src/continuedev/plugins/steps/steps_on_startup.py17
-rw-r--r--continuedev/src/continuedev/plugins/steps/welcome.py (renamed from continuedev/src/continuedev/steps/welcome.py)7
-rw-r--r--continuedev/src/continuedev/server/gui.py20
-rw-r--r--continuedev/src/continuedev/server/ide.py84
-rw-r--r--continuedev/src/continuedev/server/ide_protocol.py9
-rw-r--r--continuedev/src/continuedev/server/main.py25
-rw-r--r--continuedev/src/continuedev/server/session_manager.py12
-rw-r--r--continuedev/src/continuedev/steps/steps_on_startup.py23
-rw-r--r--docs/docs/customization.md121
-rw-r--r--docs/docs/getting-started.md4
-rw-r--r--docs/sidebars.js10
-rw-r--r--extension/DEV_README.md13
-rw-r--r--extension/README.md10
-rw-r--r--extension/package-lock.json4
-rw-r--r--extension/package.json13
-rw-r--r--extension/react-app/package-lock.json1473
-rw-r--r--extension/react-app/package.json2
-rw-r--r--extension/react-app/src/App.tsx8
-rw-r--r--extension/react-app/src/components/ComboBox.tsx102
-rw-r--r--extension/react-app/src/components/InputAndButton.tsx10
-rw-r--r--extension/react-app/src/components/Onboarding.tsx19
-rw-r--r--extension/react-app/src/components/PillButton.tsx172
-rw-r--r--extension/react-app/src/components/StepContainer.tsx116
-rw-r--r--extension/react-app/src/components/TextDialog.tsx71
-rw-r--r--extension/react-app/src/components/index.ts23
-rw-r--r--extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts35
-rw-r--r--extension/react-app/src/hooks/ContinueGUIClientProtocol.ts93
-rw-r--r--extension/react-app/src/hooks/useContinueGUIProtocol.ts91
-rw-r--r--extension/react-app/src/hooks/useWebsocket.ts2
-rw-r--r--extension/react-app/src/index.css4
-rw-r--r--extension/react-app/src/pages/gui.tsx84
-rw-r--r--extension/react-app/src/util/index.ts43
-rw-r--r--extension/schema/FullState.d.ts2
-rw-r--r--extension/schema/History.d.ts2
-rw-r--r--extension/schema/HistoryNode.d.ts2
-rw-r--r--extension/src/activation/activate.ts58
-rw-r--r--extension/src/activation/environmentSetup.ts147
-rw-r--r--extension/src/commands.ts32
-rw-r--r--extension/src/continueIdeClient.ts157
-rw-r--r--extension/src/debugPanel.ts5
-rw-r--r--extension/src/diffs.ts137
-rw-r--r--extension/src/extension.ts12
-rw-r--r--extension/src/lang-server/codeActions.ts58
-rw-r--r--extension/src/lang-server/codeLens.ts15
-rw-r--r--extension/src/suggestions.ts63
-rw-r--r--extension/src/telemetry.ts53
-rw-r--r--extension/src/util/messenger.ts2
-rw-r--r--extension/src/util/util.ts29
-rw-r--r--media/help.gifbin0 -> 1046474 bytes
-rw-r--r--media/highlight.gifbin0 -> 4103271 bytes
-rw-r--r--media/question.gifbin0 -> 2293656 bytes
-rw-r--r--schema/json/FullState.json8
-rw-r--r--schema/json/History.json8
-rw-r--r--schema/json/HistoryNode.json8
120 files changed, 4295 insertions, 1108 deletions
diff --git a/.gitignore b/.gitignore
index 48f60e7f..8f403dfe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -131,7 +131,7 @@ dmypy.json
**/node_modules
**/out
-**/.vscode
+**/.vscode/settings.json
notes.txt
cached_embeddings.pkl
.ruff_cache
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..cc7b1ce4
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,17 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: Module",
+ "type": "python",
+ "request": "launch",
+ "module": "continuedev.src.continuedev.server.main",
+ "args": ["--port", "8001"],
+ "justMyCode": false,
+ "subProcess": false
+ }
+ ]
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..f7166411
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,147 @@
+# Contributing to Continue
+
+## Table of Contents
+
+- [❤️ Ways to Contribute](#❤️-ways-to-contribute)
+ - [🐛 Report Bugs](#🐛-report-bugs)
+ - [✨ Suggest Enhancements](#✨-suggest-enhancements)
+ - [📖 Updating / Improving Documentation](#📖-updating--improving-documentation)
+ - [🧑‍💻 Contributing Code](#🧑‍💻-contributing-code)
+ - [Setup Development Environment](#setting-up-the-development-environment)
+ - [Writing Steps](#writing-steps)
+ - [Writing Context Providers](#writing-context-providers)
+- [📐 Continue Architecture](#📐-continue-architecture)
+ - [Continue VS Code Client](#continue-vs-code-client)
+ - [Continue IDE Websockets Protocol](#continue-ide-websockets-protocol)
+ - [Continue GUI Websockets Protocol](#continue-gui-websockets-protocol)
+- [❇️ Core Concepts](#❇️-core-concepts)
+ - [Step](#step)
+ - [Autopilot](#autopilot)
+ - [Observation](#observation)
+ - [Policy](#policy)
+
+# ❤️ Ways to Contribute
+
+## 🐛 Report Bugs
+
+If you find a bug, please [create an issue](https://github.com/continuedev/continue/issues) to report it! A great bug report includes:
+
+- A description of the bug
+- Steps to reproduce
+- What you expected to happen
+- What actually happened
+- Screenshots or videos
+
+## ✨ Suggest Enhancements
+
+Continue is quickly adding features, and we'd love to hear which are the most important to you. The best ways to suggest an enhancement are
+
+- Create an issue
+
+ - First, check whether a similar proposal has already been made
+ - If not, [create an issue](https://github.com/continuedev/continue/issues)
+ - Please describe the enhancement in as much detail as you can, and why it would be useful
+
+- Join the [Continue Discord](https://discord.gg/NWtdYexhMs) and tell us about your idea in the `#feedback` channel
+
+## 📖 Updating / Improving Documentation
+
+Continue is continuously improving, but a feature isn't complete until it is reflected in the documentation! If you see something out-of-date or missing, you can help by clicking "Edit this page" at the bottom of any page on [continue.dev/docs](https://continue.dev/docs).
+
+## 🧑‍💻 Contributing Code
+
+### Setting up the Development Environment
+
+There are different levels of setup necessary depending on which part of Continue you are developing. For all of them, first clone the repo:
+
+```bash
+git clone https://github.com/continuedev/continue
+```
+
+If editing only the server (`/continuedev` directory), see the directions in [continuedev/README.md](./continuedev/README.md) to set up the Python server. Once it is running on localhost:8001, you can connect your existing VS Code extension by going to VS Code settings, searching for "Continue: Server URL", and setting it to "http://localhost:8001".
+
+If editing the VS Code extension (`/extension` directory) or GUI (`/extension/react-app`), you can follow the instructions in [`extension/DEV_README.md`](./extension/DEV_README.md) to set up the VS Code extension and GUI in development mode.
+
+### Writing Steps
+
+A Step can be used as a custom slash command, or called otherwise in a `Policy`. See the [steps README](./continuedev/src/continuedev/steps/README.md) to learn how to write a Step.
+
+### Writing Context Providers
+
+A `ContextProvider` is a Continue plugin that lets type '@' to quickly select documents as context for the language model. The simplest way to create a `ContextProvider` is to implement the `provide_context_items` method. You can find a great example of this in [GitHubIssuesContextProvider](./continuedev/src/continuedev/libs/context_providers/github_issues.py), which allows you to search GitHub Issues in a repo.
+
+## 📐 Continue Architecture
+
+Continue consists of 3 components, designed so that Continue can easily be extended to work in any IDE:
+
+1. **Continue Server** - The Continue Server is responsible for keeping state, running the autopilot loop which takes actions, and communicating between the IDE and GUI.
+
+2. **Continue IDE Client** - The Continue IDE Client is a plugin for the IDE which implements the Continue IDE Protocol. This allows the server to request actions to be taken within the IDE, for example if `sdk.ide.setFileOpen("main.py")` is called on the server, it will communicate over websocketes with the IDE, which will open the file `main.py`. The first IDE Client we have built is for VS Code, but we plan to build clients for other IDEs in the future. The IDE Client must 1. implement the websockets protocol, as is done [here](./extension/src/continueIdeClient.ts) for VS Code and 2. launch the Continue Server, like [here](./extension/src/activation/environmentSetup.ts), and 3. display the Continue GUI in a sidebar, like [here](./extension/src/debugPanel.ts).
+
+3. **Continue GUI** - The Continue GUI is a React application that gives the user control over Continue. It displays the history of Steps, shows what context is included in the current Step, and lets the users enter natural language or slash commands to initiate new Steps. The GUI communicates with the Continue Server over its own websocket connection
+
+It is important that the IDE Client and GUI never communicate except when the IDE Client initially sets up the GUI. This ensures that the server is the source-of-truth for state, and that we can easily extend Continue to work in other IDEs.
+
+![Continue Architecture](https://continue.dev/docs/assets/images/continue-architecture-146a90742e25f6524452c74fe44fa2a0.png)
+
+### Continue VS Code Client
+
+The starting point for the VS Code extension is [activate.ts](./extension/src/activation/activate.ts). The `activateExtension` function here will:
+
+1. Check whether the current version of the extension is up-to-date and, if not, display a notification
+
+2. Initialize the Continue IDE Client and establish a connection with the Continue Server
+
+3. Load the Continue GUI in the sidebar of the IDE and begin a new session
+
+### Continue IDE Websockets Protocol
+
+On the IDE side, this is implemented in [continueIdeClient.ts](./extension/src/continueIdeClient.ts). On the server side, this is implemented in [ide.py](./continuedev/src/continuedev/server/ide.py). You can see [ide_protocol.py](./continuedev/src/continuedev/server/ide_protocol.py) for the protocol definition.
+
+### Continue GUI Websockets Protocol
+
+On the GUI side, this is implemented in [ContinueGUIClientProtocol.ts](./extension/react-app/src/hooks/ContinueGUIClientProtocol.ts). On the server side, this is implemented in [gui.py](./continuedev/src/continuedev/server/gui.py). You can see [gui_protocol.py](./continuedev/src/continuedev/server/gui_protocol.py) or [AbstractContinueGUIClientProtocol.ts](./extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts) for the protocol definition.
+
+When state is updated on the server, we currently send the entirety of the object over websockets to the GUI. This will of course have to be improved soon. The state object, `FullState`, is defined in [core/main.py](./continuedev/src/continuedev/core/main.py) and includes:
+
+- `history`, a record of previously run Steps. Displayed in order in the sidebar.
+- `active`, whether the autopilot is currently running a step. Displayed as a loader while step is running.
+- `user_input_queue`, the queue of user inputs that have not yet been processed due to waiting for previous Steps to complete. Displayed below the `active` loader until popped from the queue.
+- `default_model`, the default model used for completions. Displayed as a toggleable button on the bottom of the GUI.
+- `selected_context_items`, the ranges of code and other items (like GitHub Issues, files, etc...) that have been selected to include as context. Displayed just above the main text input.
+- `slash_commands`, the list of available slash commands. Displayed in the main text input dropdown.
+- `adding_highlighted_code`, whether highlighting of new code for context is locked. Displayed as a button adjacent to `highlighted_ranges`.
+
+Updates are sent with `await sdk.update_ui()` when needed explicitly or `await autopilot.update_subscribers()` automatically between each Step. The GUI can listen for state updates with `ContinueGUIClientProtocol.onStateUpdate()`.
+
+## ❇️ Core Concepts
+
+All of Continue's logic happens inside of the server, and it is built around a few core concepts. Most of these are Pydantic Models defined in [core/main.py](./continuedev/src/continuedev/core/main.py).
+
+### `Step`
+
+Everything in Continue is a "Step". The `Step` class defines 2 methods:
+
+1. `async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]` - This method defines what happens when the Step is run. It has access to the Continue SDK, which lets you take actions in the IDE, call LLMs, run nested Steps, and more. Optionally, a Step can return an `Observation` object, which a `Policy` can use to make decisions about what to do next.
+
+2. `async def describe(self, models: Models) -> Coroutine[str, None, None]` - After each Step is run, this method is called to asynchronously generate a summary title for the step. A `Models` object is passed so that you have access to LLMs to summarize for you.
+
+Steps are designed to be composable, so that you can easily build new Steps by combining existing ones. And because they are Pydantic models, they can instantly be used as tools useable by an LLM, for example with OpenAI's function-calling functionality (see [ChatWithFunctions](./continuedev/src/continuedev/steps/chat.py) for an example of this).
+
+Some of the most commonly used Steps are:
+
+- [`SimpleChatStep`](./continuedev/src/continuedev/steps/chat.py) - This is the default Step that is run when the user enters natural language input. It takes the user's input and runs it through the default LLM, then displays the result in the GUI.
+
+- [`EditHighlightedCodeStep`](./continuedev/src/continuedev/steps/core/core.py) - This is the Step run when a user highlights code, enters natural language, and presses CMD/CTRL+ENTER, or uses the slash command '/edit'. It opens a side-by-side diff editor, where updated code is streamed to fulfil the user's request.
+
+### `Autopilot`
+
+In [autopilot.py](./continuedev/src/continuedev/core/autopilot.py), we define the `Autopilot` class, which is the central entity responsible for keeping track of state and running the input/action loop.
+
+### `Observation`
+
+An `Observation` is a simple Pydantic model that can be used as a trigger to run a `Step`. For example, if running one `Step` results in an error, this can be returned as an `Observation` that can be used to trigger a `Step` that fixes the error. This is not being used frequently in the codebase right now, but we plan to use it as the basis of various "hooks" that will aid in the development of agents acting within the IDE.
+
+### `Policy`
+
+A `Policy` implements a method `def next(self, config: ContinueConfig, history: History) -> Step`, which decides which `Step` the `Autopilot` should run next. The default policy is defined in [policy.py](./continuedev/src/continuedev/core/policy.py) and runs `SimpleChatStep` by default, or a slash command when the input begins with '/'. It also displays a welcome message at the beginning of each session. If interested in developing agents that autonomously take longer sequences of actions in the IDE, the `Policy` class is the place to start.
diff --git a/README.md b/README.md
index 01462247..4337c591 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,16 @@ Let Continue build the scaffolding of Python scripts, React components, and more
### [Download for VS Code](https://marketplace.visualstudio.com/items?itemName=Continue.continue)
+## Install
+
+Continue requires that you have Python 3.8 or greater. If you do not, please [install](https://python.org) it
+
+If your Continue server is not setting up, please check the console logs:
+1. `cmd+shift+p` (MacOS) / `ctrl+shift+p` (Windows)
+2. Search for and then select "Developer: Toggle Developer Tools"
+3. Select `Console`
+4. Read the console logs
+
## OpenAI API Key
New users can try out Continue with GPT-4 using a proxy server that securely makes calls to OpenAI using our API key. Continue should just work the first time you install the extension in VS Code.
diff --git a/continuedev/README.md b/continuedev/README.md
index 528cf75a..d3ead8ec 100644
--- a/continuedev/README.md
+++ b/continuedev/README.md
@@ -1,19 +1,29 @@
# Continue PyPI Package
-This package contains the [Continue](https://github.com/continuedev.com/continue) server and core classes needed to build your own recipes.
+This package contains the [Continue](https://github.com/continuedev/continue) server and core classes needed to build your own recipes.
Continue is a Python library for automating repetitive sequences of software development tasks using language models. Using our VS Code extension, you can build, run, and refine these recipes as they natively interact with your codebase. Read the docs [here](https://continue.dev/docs) or download the VS Code extension [here](https://marketplace.visualstudio.com/items?itemName=Continue.continue).
## Continue Server
-The Continue server acts as a bridge between the Continue React app and your IDE, running your recipes and acting on the codebase.
+The Continue server acts as a bridge between the Continue React app and your IDE, running your recipes and acting on the codebase.
Start it by running the following commands:
+
1. `cd continuedev`
2. Make sure packages are installed with `poetry install`
-3. `poetry shell`
+ - If poetry is not installed, you can install with
+ ```bash
+ curl -sSL https://install.python-poetry.org | python3 -
+ ```
+ (official instructions [here](https://python-poetry.org/docs/#installing-with-the-official-installer))
+3. `poetry shell` to activate the virtual environment
4. `cd ..`
-5. `python3 -m continuedev.src.continuedev.server.main`
+5. `python3 -m continuedev.src.continuedev.server.main` to start the server
+
+Once you've validated that this works, you'll often want to use a debugger, in which case we've provided a launch configuration for VS Code in `.vscode/launch.json`. To start the debugger in VS Code, ensure that the workspace directory is the root of the `continue` repo, then press F5.
+
+> Note: To start the debugger, you'll have to select the poetry Python interpreter (`/path-to-poetry-venv/bin/python3`) in the bottom right of the VS Code window. If you don't see this, you may have to install the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python).
## Scripts
@@ -29,6 +39,8 @@ See the `src/continuedev/libs/steps` folder for examples of writing a Continue s
Open a [new GitHub Issue](https://github.com/continuedev/continue/issues/new) or comment on [an existing one](https://github.com/continuedev/continue/issues). Let us know what you would like to contribute, and we will help you make it happen!
+For more a more detailed contributing guide, see [CONTRIBUTING.md](../CONTRIBUTING.md).
+
## Install from source
#### 1. Clone this repo
@@ -60,4 +72,4 @@ cd continue/extension/scripts && python3 install_from_source.py
- [Continue GUI README](./extension/react-app/): learn about the React app that lets users interact with the server and is placed adjacent to the text editor in any suppported IDE
- [Schema README](./schema): learn about the JSON Schema types generated from Pydantic models, which we use across the `continuedev/` and `extension/` directories
- [Continue Docs README](./docs): learn how our [docs](https://continue.dev/docs) are written and built
-- [How to debug the VS Code Extension README](./extension/src/README.md): learn how to set up the VS Code extension, so you can debug it \ No newline at end of file
+- [How to debug the VS Code Extension README](./extension/src/README.md): learn how to set up the VS Code extension, so you can debug it
diff --git a/continuedev/poetry.lock b/continuedev/poetry.lock
index a49a570f..1cd4a591 100644
--- a/continuedev/poetry.lock
+++ b/continuedev/poetry.lock
@@ -125,6 +125,26 @@ files = [
frozenlist = ">=1.1.0"
[[package]]
+name = "anthropic"
+version = "0.3.4"
+description = "Client library for the anthropic API"
+category = "main"
+optional = false
+python-versions = ">=3.7,<4.0"
+files = [
+ {file = "anthropic-0.3.4-py3-none-any.whl", hash = "sha256:7b0396f663b0e4eaaf485ae59a0be014cddfc0f0b8f4dad79bb35d8f28439097"},
+ {file = "anthropic-0.3.4.tar.gz", hash = "sha256:36184840bd33184697666d4f1ec951d78ef5da22e87d936cd3c04b611d84e93c"},
+]
+
+[package.dependencies]
+anyio = ">=3.5.0,<4"
+distro = ">=1.7.0,<2"
+httpx = ">=0.23.0,<1"
+pydantic = ">=1.9.0,<2.0.0"
+tokenizers = ">=0.13.0"
+typing-extensions = ">=4.1.1,<5"
+
+[[package]]
name = "anyio"
version = "3.6.2"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
@@ -298,6 +318,18 @@ files = [
]
[[package]]
+name = "chevron"
+version = "0.14.0"
+description = "Mustache templating language renderer"
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+ {file = "chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443"},
+ {file = "chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf"},
+]
+
+[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
@@ -375,6 +407,18 @@ files = [
dev = ["pytest (>=3.7)"]
[[package]]
+name = "distro"
+version = "1.8.0"
+description = "Distro - an OS platform information API"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "distro-1.8.0-py3-none-any.whl", hash = "sha256:99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff"},
+ {file = "distro-1.8.0.tar.gz", hash = "sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8"},
+]
+
+[[package]]
name = "fastapi"
version = "0.95.1"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
@@ -589,6 +633,52 @@ files = [
]
[[package]]
+name = "httpcore"
+version = "0.17.3"
+description = "A minimal low-level HTTP client."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"},
+ {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"},
+]
+
+[package.dependencies]
+anyio = ">=3.0,<5.0"
+certifi = "*"
+h11 = ">=0.13,<0.15"
+sniffio = ">=1.0.0,<2.0.0"
+
+[package.extras]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (>=1.0.0,<2.0.0)"]
+
+[[package]]
+name = "httpx"
+version = "0.24.1"
+description = "The next generation HTTP client."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"},
+ {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"},
+]
+
+[package.dependencies]
+certifi = "*"
+httpcore = ">=0.15.0,<0.18.0"
+idna = "*"
+sniffio = "*"
+
+[package.extras]
+brotli = ["brotli", "brotlicffi"]
+cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (>=1.0.0,<2.0.0)"]
+
+[[package]]
name = "idna"
version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
@@ -601,6 +691,25 @@ files = [
]
[[package]]
+name = "importlib-resources"
+version = "6.0.0"
+description = "Read resources from Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "importlib_resources-6.0.0-py3-none-any.whl", hash = "sha256:d952faee11004c045f785bb5636e8f885bed30dc3c940d5d42798a2a4541c185"},
+ {file = "importlib_resources-6.0.0.tar.gz", hash = "sha256:4cf94875a8368bd89531a756df9a9ebe1f150e0f885030b461237bc7f2d905f2"},
+]
+
+[package.dependencies]
+zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
+
+[[package]]
name = "jsonref"
version = "1.1.0"
description = "jsonref is a library for automatic dereferencing of JSON Reference objects for Python."
@@ -626,6 +735,8 @@ files = [
[package.dependencies]
attrs = ">=17.4.0"
+importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""}
+pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""}
pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2"
[package.extras]
@@ -1025,6 +1136,18 @@ test = ["hypothesis (>=6.34.2)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.17.0)"
xml = ["lxml (>=4.6.3)"]
[[package]]
+name = "pkgutil-resolve-name"
+version = "1.3.10"
+description = "Resolve a name to an object."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"},
+ {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"},
+]
+
+[[package]]
name = "posthog"
version = "3.0.1"
description = "Integrate PostHog into any python application."
@@ -1049,6 +1172,33 @@ sentry = ["django", "sentry-sdk"]
test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint", "pytest"]
[[package]]
+name = "psutil"
+version = "5.9.5"
+description = "Cross-platform lib for process and system monitoring in Python."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"},
+ {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"},
+ {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"},
+ {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"},
+ {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"},
+ {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"},
+ {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"},
+ {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"},
+ {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"},
+ {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"},
+ {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"},
+ {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"},
+ {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"},
+ {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"},
+]
+
+[package.extras]
+test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
+
+[[package]]
name = "pydantic"
version = "1.10.7"
description = "Data validation and settings management using python type hints"
@@ -1533,6 +1683,61 @@ requests = ">=2.26.0"
blobfile = ["blobfile (>=2)"]
[[package]]
+name = "tokenizers"
+version = "0.13.3"
+description = "Fast and Customizable Tokenizers"
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+ {file = "tokenizers-0.13.3-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:f3835c5be51de8c0a092058a4d4380cb9244fb34681fd0a295fbf0a52a5fdf33"},
+ {file = "tokenizers-0.13.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4ef4c3e821730f2692489e926b184321e887f34fb8a6b80b8096b966ba663d07"},
+ {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5fd1a6a25353e9aa762e2aae5a1e63883cad9f4e997c447ec39d071020459bc"},
+ {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee0b1b311d65beab83d7a41c56a1e46ab732a9eed4460648e8eb0bd69fc2d059"},
+ {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ef4215284df1277dadbcc5e17d4882bda19f770d02348e73523f7e7d8b8d396"},
+ {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4d53976079cff8a033f778fb9adca2d9d69d009c02fa2d71a878b5f3963ed30"},
+ {file = "tokenizers-0.13.3-cp310-cp310-win32.whl", hash = "sha256:1f0e3b4c2ea2cd13238ce43548959c118069db7579e5d40ec270ad77da5833ce"},
+ {file = "tokenizers-0.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:89649c00d0d7211e8186f7a75dfa1db6996f65edce4b84821817eadcc2d3c79e"},
+ {file = "tokenizers-0.13.3-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:56b726e0d2bbc9243872b0144515ba684af5b8d8cd112fb83ee1365e26ec74c8"},
+ {file = "tokenizers-0.13.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc5c022ce692e1f499d745af293ab9ee6f5d92538ed2faf73f9708c89ee59ce6"},
+ {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55c981ac44ba87c93e847c333e58c12abcbb377a0c2f2ef96e1a266e4184ff2"},
+ {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f247eae99800ef821a91f47c5280e9e9afaeed9980fc444208d5aa6ba69ff148"},
+ {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b3e3215d048e94f40f1c95802e45dcc37c5b05eb46280fc2ccc8cd351bff839"},
+ {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ba2b0bf01777c9b9bc94b53764d6684554ce98551fec496f71bc5be3a03e98b"},
+ {file = "tokenizers-0.13.3-cp311-cp311-win32.whl", hash = "sha256:cc78d77f597d1c458bf0ea7c2a64b6aa06941c7a99cb135b5969b0278824d808"},
+ {file = "tokenizers-0.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:ecf182bf59bd541a8876deccf0360f5ae60496fd50b58510048020751cf1724c"},
+ {file = "tokenizers-0.13.3-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:0527dc5436a1f6bf2c0327da3145687d3bcfbeab91fed8458920093de3901b44"},
+ {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07cbb2c307627dc99b44b22ef05ff4473aa7c7cc1fec8f0a8b37d8a64b1a16d2"},
+ {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4560dbdeaae5b7ee0d4e493027e3de6d53c991b5002d7ff95083c99e11dd5ac0"},
+ {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64064bd0322405c9374305ab9b4c07152a1474370327499911937fd4a76d004b"},
+ {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8c6e2ab0f2e3d939ca66aa1d596602105fe33b505cd2854a4c1717f704c51de"},
+ {file = "tokenizers-0.13.3-cp37-cp37m-win32.whl", hash = "sha256:6cc29d410768f960db8677221e497226e545eaaea01aa3613fa0fdf2cc96cff4"},
+ {file = "tokenizers-0.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fc2a7fdf864554a0dacf09d32e17c0caa9afe72baf9dd7ddedc61973bae352d8"},
+ {file = "tokenizers-0.13.3-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:8791dedba834c1fc55e5f1521be325ea3dafb381964be20684b92fdac95d79b7"},
+ {file = "tokenizers-0.13.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:d607a6a13718aeb20507bdf2b96162ead5145bbbfa26788d6b833f98b31b26e1"},
+ {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3791338f809cd1bf8e4fee6b540b36822434d0c6c6bc47162448deee3f77d425"},
+ {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2f35f30e39e6aab8716f07790f646bdc6e4a853816cc49a95ef2a9016bf9ce6"},
+ {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310204dfed5aa797128b65d63538a9837cbdd15da2a29a77d67eefa489edda26"},
+ {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0f9b92ea052305166559f38498b3b0cae159caea712646648aaa272f7160963"},
+ {file = "tokenizers-0.13.3-cp38-cp38-win32.whl", hash = "sha256:9a3fa134896c3c1f0da6e762d15141fbff30d094067c8f1157b9fdca593b5806"},
+ {file = "tokenizers-0.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:8e7b0cdeace87fa9e760e6a605e0ae8fc14b7d72e9fc19c578116f7287bb873d"},
+ {file = "tokenizers-0.13.3-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:00cee1e0859d55507e693a48fa4aef07060c4bb6bd93d80120e18fea9371c66d"},
+ {file = "tokenizers-0.13.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:a23ff602d0797cea1d0506ce69b27523b07e70f6dda982ab8cf82402de839088"},
+ {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ce07445050b537d2696022dafb115307abdffd2a5c106f029490f84501ef97"},
+ {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:280ffe95f50eaaf655b3a1dc7ff1d9cf4777029dbbc3e63a74e65a056594abc3"},
+ {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97acfcec592f7e9de8cadcdcda50a7134423ac8455c0166b28c9ff04d227b371"},
+ {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7730c98a3010cd4f523465867ff95cd9d6430db46676ce79358f65ae39797b"},
+ {file = "tokenizers-0.13.3-cp39-cp39-win32.whl", hash = "sha256:48625a108029cb1ddf42e17a81b5a3230ba6888a70c9dc14e81bc319e812652d"},
+ {file = "tokenizers-0.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:bc0a6f1ba036e482db6453571c9e3e60ecd5489980ffd95d11dc9f960483d783"},
+ {file = "tokenizers-0.13.3.tar.gz", hash = "sha256:2e546dbb68b623008a5442353137fbb0123d311a6d7ba52f2667c8862a75af2e"},
+]
+
+[package.extras]
+dev = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"]
+docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"]
+testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"]
+
+[[package]]
name = "tqdm"
version = "4.65.0"
description = "Fast, Extensible Progress Meter"
@@ -1818,7 +2023,23 @@ files = [
idna = ">=2.0"
multidict = ">=4.0"
+[[package]]
+name = "zipp"
+version = "3.16.2"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"},
+ {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
+
[metadata]
lock-version = "2.0"
-python-versions = "^3.9"
-content-hash = "3ba2a7278fda36a059d76e227be94b0cb5e2efc9396b47a9642b916680214d9f"
+python-versions = "^3.8.1"
+content-hash = "3fcd19c11b9c338a181e591b56e21d59c7834abff431fb9f40cc1ea874b64557"
diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml
index 6727e29a..0abc9504 100644
--- a/continuedev/pyproject.toml
+++ b/continuedev/pyproject.toml
@@ -6,7 +6,7 @@ authors = ["Nate Sesti <sestinj@gmail.com>"]
readme = "README.md"
[tool.poetry.dependencies]
-python = "^3.8"
+python = "^3.8.1"
diff-match-patch = "^20230430"
fastapi = "^0.95.1"
typer = "^0.7.0"
@@ -24,6 +24,9 @@ tiktoken = "^0.4.0"
jsonref = "^1.1.0"
jsonschema = "^4.17.3"
directory-tree = "^0.0.3.1"
+anthropic = "^0.3.4"
+chevron = "^0.14.0"
+psutil = "^5.9.5"
[tool.poetry.scripts]
typegen = "src.continuedev.models.generate_json_schema:main"
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index e1c8a076..9dbced32 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -13,7 +13,7 @@ from ..server.ide_protocol import AbstractIdeProtocolServer
from ..libs.util.queue import AsyncSubscriptionQueue
from ..models.main import ContinueBaseModel
from .main import Context, ContinueCustomException, HighlightedRangeContext, Policy, History, FullState, Step, HistoryNode
-from ..steps.core.core import ReversibleStep, ManualEditStep, UserInputStep
+from ..plugins.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep
from ..libs.util.telemetry import capture_event
from .sdk import ContinueSDK
from ..libs.util.step_name_to_steps import get_step_from_name
@@ -36,7 +36,11 @@ def get_error_title(e: Exception) -> str:
elif isinstance(e, openai_errors.APIConnectionError):
return "The request failed. Please check your internet connection and try again. If this issue persists, you can use our API key for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to \"\""
elif isinstance(e, openai_errors.InvalidRequestError):
- return 'Your API key does not have access to GPT-4. You can use ours for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to ""'
+ return 'Invalid request sent to OpenAI. Please try again.'
+ elif "rate_limit_ip_middleware" in e.__str__():
+ return 'You have reached your limit for free usage of our token. You can continue using Continue by entering your own OpenAI API key in VS Code settings.'
+ elif e.__str__().startswith("Cannot connect to host"):
+ return "The request failed. Please check your internet connection and try again."
return e.__str__() or e.__repr__()
@@ -48,6 +52,8 @@ class Autopilot(ContinueBaseModel):
full_state: Union[FullState, None] = None
_on_update_callbacks: List[Callable[[FullState], None]] = []
+ continue_sdk: ContinueSDK = None
+
_active: bool = False
_should_halt: bool = False
_main_user_input_queue: List[str] = []
@@ -55,9 +61,11 @@ class Autopilot(ContinueBaseModel):
_user_input_queue = AsyncSubscriptionQueue()
_retry_queue = AsyncSubscriptionQueue()
- @cached_property
- def continue_sdk(self) -> ContinueSDK:
- return ContinueSDK(self)
+ @classmethod
+ async def create(cls, policy: Policy, ide: AbstractIdeProtocolServer, full_state: FullState) -> "Autopilot":
+ autopilot = cls(ide=ide, policy=policy)
+ autopilot.continue_sdk = await ContinueSDK.create(autopilot)
+ return autopilot
class Config:
arbitrary_types_allowed = True
@@ -94,9 +102,14 @@ class Autopilot(ContinueBaseModel):
self.continue_sdk.update_default_model(model)
async def clear_history(self):
+ # Reset history
self.history = History.from_empty()
self._main_user_input_queue = []
self._active = False
+
+ # Also remove all context
+ self._highlighted_ranges = []
+
await self.update_subscribers()
def on_update(self, callback: Coroutine["FullState", None, None]):
@@ -160,6 +173,25 @@ class Autopilot(ContinueBaseModel):
if not any(map(lambda x: x.editing, self._highlighted_ranges)):
self._highlighted_ranges[0].editing = True
+ def _disambiguate_highlighted_ranges(self):
+ """If any files have the same name, also display their folder name"""
+ name_status: Dict[str, set] = {
+ } # basename -> set of full paths with that basename
+ for rif in self._highlighted_ranges:
+ basename = os.path.basename(rif.range.filepath)
+ if basename in name_status:
+ name_status[basename].add(rif.range.filepath)
+ else:
+ name_status[basename] = {rif.range.filepath}
+
+ for rif in self._highlighted_ranges:
+ basename = os.path.basename(rif.range.filepath)
+ if len(name_status[basename]) > 1:
+ rif.display_name = os.path.join(
+ os.path.basename(os.path.dirname(rif.range.filepath)), basename)
+ else:
+ rif.display_name = basename
+
async def handle_highlighted_code(self, range_in_files: List[RangeInFileWithContents]):
# Filter out rifs from ~/.continue/diffs folder
range_in_files = [
@@ -205,6 +237,7 @@ class Autopilot(ContinueBaseModel):
) for rif in range_in_files]
self._make_sure_is_editing_range()
+ self._disambiguate_highlighted_ranges()
await self.update_subscribers()
diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py
index f6167638..70c4876e 100644
--- a/continuedev/src/continuedev/core/config.py
+++ b/continuedev/src/continuedev/core/config.py
@@ -45,6 +45,11 @@ DEFAULT_SLASH_COMMANDS = [
step_name="OpenConfigStep",
),
SlashCommand(
+ name="help",
+ description="Ask a question like '/help what is given to the llm as context?'",
+ step_name="HelpStep",
+ ),
+ SlashCommand(
name="comment",
description="Write comments for the current file or highlighted code",
step_name="CommentCodeStep",
@@ -62,16 +67,22 @@ DEFAULT_SLASH_COMMANDS = [
]
+class AzureInfo(BaseModel):
+ endpoint: str
+ engine: str
+ api_version: str
+
+
class ContinueConfig(BaseModel):
"""
A pydantic class for the continue config file.
"""
steps_on_startup: Optional[Dict[str, Dict]] = {}
disallowed_steps: Optional[List[str]] = []
- server_url: Optional[str] = None
allow_anonymous_telemetry: Optional[bool] = True
default_model: Literal["gpt-3.5-turbo", "gpt-3.5-turbo-16k",
- "gpt-4"] = 'gpt-4'
+ "gpt-4", "claude-2", "ggml"] = 'gpt-4'
+ temperature: Optional[float] = 0.5
custom_commands: Optional[List[CustomCommand]] = [CustomCommand(
name="test",
description="This is an example custom command. Use /config to edit it and create more",
@@ -80,12 +91,18 @@ class ContinueConfig(BaseModel):
slash_commands: Optional[List[SlashCommand]] = DEFAULT_SLASH_COMMANDS
on_traceback: Optional[List[OnTracebackSteps]] = [
OnTracebackSteps(step_name="DefaultOnTracebackStep")]
+ system_message: Optional[str] = None
+ azure_openai_info: Optional[AzureInfo] = None
# Want to force these to be the slash commands for now
@validator('slash_commands', pre=True)
def default_slash_commands_validator(cls, v):
return DEFAULT_SLASH_COMMANDS
+ @validator('temperature', pre=True)
+ def temperature_validator(cls, v):
+ return max(0.0, min(1.0, v))
+
def load_config(config_file: str) -> ContinueConfig:
"""
diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py
index 88690c83..50d01f8d 100644
--- a/continuedev/src/continuedev/core/main.py
+++ b/continuedev/src/continuedev/core/main.py
@@ -102,6 +102,7 @@ class HistoryNode(ContinueBaseModel):
depth: int
deleted: bool = False
active: bool = True
+ logs: List[str] = []
def to_chat_messages(self) -> List[ChatMessage]:
if self.step.description is None or self.step.manage_own_chat_context:
@@ -258,10 +259,8 @@ class Step(ContinueBaseModel):
def dict(self, *args, **kwargs):
d = super().dict(*args, **kwargs)
- if self.description is not None:
- d["description"] = self.description
- else:
- d["description"] = ""
+ # Make sure description is always a string
+ d["description"] = self.description or ""
return d
@validator("name", pre=True, always=True)
diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index b8363df2..dfa0e7f9 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -1,25 +1,15 @@
from textwrap import dedent
-from typing import List, Tuple, Type, Union
+from typing import Union
-from ..steps.welcome import WelcomeStep
+from ..plugins.steps.chat import SimpleChatStep
+from ..plugins.steps.welcome import WelcomeStep
from .config import ContinueConfig
-from ..steps.chroma import AnswerQuestionChroma, EditFileChroma, CreateCodebaseIndexChroma
-from ..steps.steps_on_startup import StepsOnStartupStep
-from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
-from ..recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe
-from ..recipes.AddTransformRecipe.main import AddTransformRecipe
-from .main import Step, Validator, History, Policy
-from .observation import Observation, TracebackObservation, UserInputObservation
-from ..steps.main import EditHighlightedCodeStep, SolveTracebackStep
-from ..recipes.WritePytestsRecipe.main import WritePytestsRecipe
-from ..recipes.ContinueRecipeRecipe.main import ContinueStepStep
-from ..steps.comment_code import CommentCodeStep
-from ..steps.react import NLDecisionStep
-from ..steps.chat import SimpleChatStep, ChatWithFunctions, EditFileStep, AddFileStep
-from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe
-from ..steps.core.core import MessageStep
+from ..plugins.steps.steps_on_startup import StepsOnStartupStep
+from .main import Step, History, Policy
+from .observation import UserInputObservation
+from ..plugins.steps.core.core import MessageStep
from ..libs.util.step_name_to_steps import get_step_from_name
-from ..steps.custom_command import CustomCommandStep
+from ..plugins.steps.custom_command import CustomCommandStep
def parse_slash_command(inp: str, config: ContinueConfig) -> Union[None, Step]:
@@ -50,7 +40,7 @@ def parse_custom_command(inp: str, config: ContinueConfig) -> Union[None, Step]:
return None
-class DemoPolicy(Policy):
+class DefaultPolicy(Policy):
ran_code_last: bool = False
def next(self, config: ContinueConfig, history: History) -> Step:
@@ -58,12 +48,9 @@ class DemoPolicy(Policy):
if history.get_current() is None:
return (
MessageStep(name="Welcome to Continue", message=dedent("""\
- - Highlight code and ask a question or give instructions
- - Use `cmd+k` (Mac) / `ctrl+k` (Windows) to open Continue
- - Use `cmd+shift+e` / `ctrl+shift+e` to open file Explorer
- - Add your own OpenAI API key to VS Code Settings with `cmd+,`
- - Use slash commands when you want fine-grained control
- - Past steps are included as part of the context by default""")) >>
+ - Highlight code section and ask a question or give instructions
+ - Use `cmd+m` (Mac) / `ctrl+m` (Windows) to open Continue
+ - Use `/help` to ask questions about how to use Continue""")) >>
WelcomeStep() >>
# SetupContinueWorkspaceStep() >>
# CreateCodebaseIndexChroma() >>
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index aa2d8892..9d1025e3 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -1,9 +1,9 @@
import asyncio
from functools import cached_property
-from typing import Coroutine, Union
+from typing import Coroutine, Dict, Union
import os
-from ..steps.core.core import DefaultModelEditCodeStep
+from ..plugins.steps.core.core import DefaultModelEditCodeStep
from ..models.main import Range
from .abstract_sdk import AbstractContinueSDK
from .config import ContinueConfig, load_config, load_global_config, update_global_config
@@ -11,10 +11,12 @@ from ..models.filesystem_edit import FileEdit, FileSystemEdit, AddFile, DeleteFi
from ..models.filesystem import RangeInFile
from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI
from ..libs.llm.openai import OpenAI
+from ..libs.llm.anthropic import AnthropicLLM
+from ..libs.llm.ggml import GGML
from .observation import Observation
from ..server.ide_protocol import AbstractIdeProtocolServer
-from .main import Context, ContinueCustomException, HighlightedRangeContext, History, Step, ChatMessage, ChatMessageRole
-from ..steps.core.core import *
+from .main import Context, ContinueCustomException, History, HistoryNode, Step, ChatMessage
+from ..plugins.steps.core.core import *
from ..libs.llm.proxy_server import ProxyServer
@@ -22,26 +24,78 @@ class Autopilot:
pass
+ModelProvider = Literal["openai", "hf_inference_api", "ggml", "anthropic"]
+MODEL_PROVIDER_TO_ENV_VAR = {
+ "openai": "OPENAI_API_KEY",
+ "hf_inference_api": "HUGGING_FACE_TOKEN",
+ "anthropic": "ANTHROPIC_API_KEY",
+}
+
+
class Models:
- def __init__(self, sdk: "ContinueSDK"):
+ provider_keys: Dict[ModelProvider, str] = {}
+ model_providers: List[ModelProvider]
+ system_message: str
+
+ """
+ Better to have sdk.llm.stream_chat(messages, model="claude-2").
+ Then you also don't care that it' async.
+ And it's easier to add more models.
+ And intermediate shared code is easier to add.
+ And you can make constants like ContinueModels.GPT35 = "gpt-3.5-turbo"
+ PromptTransformer would be a good concept: You pass a prompt or list of messages and a model, then it outputs the prompt for that model.
+ Easy to reason about, can place anywhere.
+ And you can even pass a Prompt object to sdk.llm.stream_chat maybe, and it'll automatically be transformed for the given model.
+ This can all happen inside of Models?
+
+ class Prompt:
+ def __init__(self, ...info):
+ '''take whatever info is needed to describe the prompt'''
+
+ def to_string(self, model: str) -> str:
+ '''depending on the model, return the single prompt string'''
+ """
+
+ def __init__(self, sdk: "ContinueSDK", model_providers: List[ModelProvider]):
self.sdk = sdk
+ self.model_providers = model_providers
+ self.system_message = sdk.config.system_message
+
+ @classmethod
+ async def create(cls, sdk: "ContinueSDK", with_providers: List[ModelProvider] = ["openai"]) -> "Models":
+ if sdk.config.default_model == "claude-2":
+ with_providers.append("anthropic")
+
+ models = Models(sdk, with_providers)
+ for provider in with_providers:
+ if provider in MODEL_PROVIDER_TO_ENV_VAR:
+ env_var = MODEL_PROVIDER_TO_ENV_VAR[provider]
+ models.provider_keys[provider] = await sdk.get_user_secret(
+ env_var, f'Please add your {env_var} to the .env file')
+
+ return models
def __load_openai_model(self, model: str) -> OpenAI:
- async def load_openai_model():
- api_key = await self.sdk.get_user_secret(
- 'OPENAI_API_KEY', 'Enter your OpenAI API key or press enter to try for free')
- if api_key == "":
- return ProxyServer(self.sdk.ide.unique_id, model)
- return OpenAI(api_key=api_key, default_model=model)
- return asyncio.get_event_loop().run_until_complete(load_openai_model())
+ api_key = self.provider_keys["openai"]
+ if api_key == "":
+ return ProxyServer(self.sdk.ide.unique_id, model, system_message=self.system_message, write_log=self.sdk.write_log)
+ return OpenAI(api_key=api_key, default_model=model, system_message=self.system_message, azure_info=self.sdk.config.azure_openai_info, write_log=self.sdk.write_log)
+
+ def __load_hf_inference_api_model(self, model: str) -> HuggingFaceInferenceAPI:
+ api_key = self.provider_keys["hf_inference_api"]
+ return HuggingFaceInferenceAPI(api_key=api_key, model=model, system_message=self.system_message)
+
+ def __load_anthropic_model(self, model: str) -> AnthropicLLM:
+ api_key = self.provider_keys["anthropic"]
+ return AnthropicLLM(api_key, model, self.system_message)
+
+ @cached_property
+ def claude2(self):
+ return self.__load_anthropic_model("claude-2")
@cached_property
def starcoder(self):
- async def load_starcoder():
- api_key = await self.sdk.get_user_secret(
- 'HUGGING_FACE_TOKEN', 'Please add your Hugging Face token to the .env file')
- return HuggingFaceInferenceAPI(api_key=api_key)
- return asyncio.get_event_loop().run_until_complete(load_starcoder())
+ return self.__load_hf_inference_api_model("bigcode/starcoder")
@cached_property
def gpt35(self):
@@ -59,6 +113,10 @@ class Models:
def gpt4(self):
return self.__load_openai_model("gpt-4")
+ @cached_property
+ def ggml(self):
+ return GGML(system_message=self.system_message)
+
def __model_from_name(self, model_name: str):
if model_name == "starcoder":
return self.starcoder
@@ -68,13 +126,17 @@ class Models:
return self.gpt3516k
elif model_name == "gpt-4":
return self.gpt4
+ elif model_name == "claude-2":
+ return self.claude2
+ elif model_name == "ggml":
+ return self.ggml
else:
raise Exception(f"Unknown model {model_name}")
@property
def default(self):
default_model = self.sdk.config.default_model
- return self.__model_from_name(default_model) if default_model is not None else self.gpt35
+ return self.__model_from_name(default_model) if default_model is not None else self.gpt4
class ContinueSDK(AbstractContinueSDK):
@@ -87,10 +149,32 @@ class ContinueSDK(AbstractContinueSDK):
def __init__(self, autopilot: Autopilot):
self.ide = autopilot.ide
self.__autopilot = autopilot
- self.models = Models(self)
self.context = autopilot.context
self.config = self._load_config()
+ @classmethod
+ async def create(cls, autopilot: Autopilot) -> "ContinueSDK":
+ sdk = ContinueSDK(autopilot)
+
+ try:
+ config = sdk._load_config()
+ sdk.config = config
+ except Exception as e:
+ print(e)
+ sdk.config = ContinueConfig()
+ msg_step = MessageStep(
+ name="Invalid Continue Config File", message=e.__repr__())
+ msg_step.description = e.__repr__()
+ sdk.history.add_node(HistoryNode(
+ step=msg_step,
+ observation=None,
+ depth=0,
+ active=False
+ ))
+
+ sdk.models = await Models.create(sdk)
+ return sdk
+
config: ContinueConfig
def _load_config(self) -> ContinueConfig:
@@ -108,6 +192,9 @@ class ContinueSDK(AbstractContinueSDK):
def history(self) -> History:
return self.__autopilot.history
+ def write_log(self, message: str):
+ self.history.timeline[self.history.current_index].logs.append(message)
+
async def _ensure_absolute_path(self, path: str) -> str:
if os.path.isabs(path):
return path
@@ -215,7 +302,7 @@ class ContinueSDK(AbstractContinueSDK):
for rif in highlighted_code:
msg = ChatMessage(content=f"{preface} ({rif.filepath}):\n```\n{rif.contents}\n```",
- role="system", summary=f"{preface}: {rif.filepath}")
+ role="user", summary=f"{preface}: {rif.filepath}")
# Don't insert after latest user message or function call
i = -1
diff --git a/continuedev/src/continuedev/libs/llm/__init__.py b/continuedev/src/continuedev/libs/llm/__init__.py
index 4c4de213..2766db4b 100644
--- a/continuedev/src/continuedev/libs/llm/__init__.py
+++ b/continuedev/src/continuedev/libs/llm/__init__.py
@@ -9,15 +9,15 @@ from pydantic import BaseModel
class LLM(ABC):
system_message: Union[str, None] = None
- async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
+ async def complete(self, prompt: str, with_history: List[ChatMessage] = None, **kwargs) -> Coroutine[Any, Any, str]:
"""Return the completion of the text with the given temperature."""
raise NotImplementedError
- def stream_complete(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ def stream_complete(self, prompt, with_history: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
"""Stream the completion through generator."""
raise NotImplementedError
- async def stream_chat(self, messages: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ async def stream_chat(self, messages: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
"""Stream the chat through generator."""
raise NotImplementedError
diff --git a/continuedev/src/continuedev/libs/llm/anthropic.py b/continuedev/src/continuedev/libs/llm/anthropic.py
new file mode 100644
index 00000000..625d4e57
--- /dev/null
+++ b/continuedev/src/continuedev/libs/llm/anthropic.py
@@ -0,0 +1,97 @@
+
+from functools import cached_property
+import time
+from typing import Any, Coroutine, Dict, Generator, List, Union
+from ...core.main import ChatMessage
+from anthropic import HUMAN_PROMPT, AI_PROMPT, AsyncAnthropic
+from ..llm import LLM
+from ..util.count_tokens import DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_MODELS, DEFAULT_ARGS, count_tokens, prune_raw_prompt_from_top
+
+
+class AnthropicLLM(LLM):
+ api_key: str
+ default_model: str
+ async_client: AsyncAnthropic
+
+ def __init__(self, api_key: str, default_model: str, system_message: str = None):
+ self.api_key = api_key
+ self.default_model = default_model
+ self.system_message = system_message
+
+ self.async_client = AsyncAnthropic(api_key=api_key)
+
+ @cached_property
+ def name(self):
+ return self.default_model
+
+ @property
+ def default_args(self):
+ return {**DEFAULT_ARGS, "model": self.default_model}
+
+ def _transform_args(self, args: Dict[str, Any]) -> Dict[str, Any]:
+ args = args.copy()
+ if "max_tokens" in args:
+ args["max_tokens_to_sample"] = args["max_tokens"]
+ del args["max_tokens"]
+ if "frequency_penalty" in args:
+ del args["frequency_penalty"]
+ if "presence_penalty" in args:
+ del args["presence_penalty"]
+ return args
+
+ def count_tokens(self, text: str):
+ return count_tokens(self.default_model, text)
+
+ def __messages_to_prompt(self, messages: List[Dict[str, str]]) -> str:
+ prompt = ""
+
+ # Anthropic prompt must start with a Human turn
+ if len(messages) > 0 and messages[0]["role"] != "user" and messages[0]["role"] != "system":
+ prompt += f"{HUMAN_PROMPT} Hello."
+ for msg in messages:
+ prompt += f"{HUMAN_PROMPT if (msg['role'] == 'user' or msg['role'] == 'system') else AI_PROMPT} {msg['content']} "
+
+ prompt += AI_PROMPT
+ return prompt
+
+ async def stream_complete(self, prompt, with_history: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ args = self.default_args.copy()
+ args.update(kwargs)
+ args["stream"] = True
+ args = self._transform_args(args)
+
+ async for chunk in await self.async_client.completions.create(
+ prompt=f"{HUMAN_PROMPT} {prompt} {AI_PROMPT}",
+ **args
+ ):
+ yield chunk.completion
+
+ async def stream_chat(self, messages: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ args = self.default_args.copy()
+ args.update(kwargs)
+ args["stream"] = True
+ args = self._transform_args(args)
+
+ messages = compile_chat_messages(
+ args["model"], messages, args["max_tokens_to_sample"], functions=args.get("functions", None), system_message=self.system_message)
+ async for chunk in await self.async_client.completions.create(
+ prompt=self.__messages_to_prompt(messages),
+ **args
+ ):
+ yield {
+ "role": "assistant",
+ "content": chunk.completion
+ }
+
+ async def complete(self, prompt: str, with_history: List[ChatMessage] = None, **kwargs) -> Coroutine[Any, Any, str]:
+ args = {**self.default_args, **kwargs}
+ args = self._transform_args(args)
+
+ messages = compile_chat_messages(
+ args["model"], with_history, args["max_tokens_to_sample"], prompt, functions=None, system_message=self.system_message)
+ resp = (await self.async_client.completions.create(
+ prompt=self.__messages_to_prompt(messages),
+ **args
+ )).completion
+
+ return resp
diff --git a/continuedev/src/continuedev/libs/llm/ggml.py b/continuedev/src/continuedev/libs/llm/ggml.py
new file mode 100644
index 00000000..4889a556
--- /dev/null
+++ b/continuedev/src/continuedev/libs/llm/ggml.py
@@ -0,0 +1,86 @@
+from functools import cached_property
+import json
+from typing import Any, Coroutine, Dict, Generator, List, Union
+
+import aiohttp
+from ...core.main import ChatMessage
+from ..llm import LLM
+from ..util.count_tokens import compile_chat_messages, DEFAULT_ARGS, count_tokens
+
+SERVER_URL = "http://localhost:8000"
+
+
+class GGML(LLM):
+
+ def __init__(self, system_message: str = None):
+ self.system_message = system_message
+
+ @cached_property
+ def name(self):
+ return "ggml"
+
+ @property
+ def default_args(self):
+ return {**DEFAULT_ARGS, "model": self.name, "max_tokens": 1024}
+
+ def count_tokens(self, text: str):
+ return count_tokens(self.name, text)
+
+ async def stream_complete(self, prompt, with_history: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ args = self.default_args.copy()
+ args.update(kwargs)
+ args["stream"] = True
+
+ args = {**self.default_args, **kwargs}
+ messages = compile_chat_messages(
+ self.name, with_history, args["max_tokens"], prompt, functions=args.get("functions", None), system_message=self.system_message)
+
+ async with aiohttp.ClientSession() as session:
+ async with session.post(f"{SERVER_URL}/v1/completions", json={
+ "messages": messages,
+ **args
+ }) as resp:
+ async for line in resp.content.iter_any():
+ if line:
+ try:
+ yield line.decode("utf-8")
+ except:
+ raise Exception(str(line))
+
+ async def stream_chat(self, messages: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ args = {**self.default_args, **kwargs}
+ messages = compile_chat_messages(
+ self.name, messages, args["max_tokens"], None, functions=args.get("functions", None), system_message=self.system_message)
+ args["stream"] = True
+
+ async with aiohttp.ClientSession() as session:
+ async with session.post(f"{SERVER_URL}/v1/chat/completions", json={
+ "messages": messages,
+ **args
+ }) as resp:
+ # This is streaming application/json instaed of text/event-stream
+ async for line in resp.content.iter_chunks():
+ if line[1]:
+ try:
+ json_chunk = line[0].decode("utf-8")
+ if json_chunk.startswith(": ping - ") or json_chunk.startswith("data: [DONE]"):
+ continue
+ chunks = json_chunk.split("\n")
+ for chunk in chunks:
+ if chunk.strip() != "":
+ yield json.loads(chunk[6:])["choices"][0]["delta"]
+ except:
+ raise Exception(str(line[0]))
+
+ async def complete(self, prompt: str, with_history: List[ChatMessage] = None, **kwargs) -> Coroutine[Any, Any, str]:
+ args = {**self.default_args, **kwargs}
+
+ async with aiohttp.ClientSession() as session:
+ async with session.post(f"{SERVER_URL}/v1/completions", json={
+ "messages": compile_chat_messages(args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message),
+ **args
+ }) as resp:
+ try:
+ return await resp.text()
+ except:
+ raise Exception(await resp.text())
diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py
index 1586c620..36f03270 100644
--- a/continuedev/src/continuedev/libs/llm/hf_inference_api.py
+++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py
@@ -9,9 +9,14 @@ DEFAULT_MAX_TIME = 120.
class HuggingFaceInferenceAPI(LLM):
api_key: str
- model: str = "bigcode/starcoder"
+ model: str
- def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs):
+ def __init__(self, api_key: str, model: str, system_message: str = None):
+ self.api_key = api_key
+ self.model = model
+ self.system_message = system_message # TODO: Nothing being done with this
+
+ def complete(self, prompt: str, with_history: List[ChatMessage] = None, **kwargs):
"""Return the completion of the text with the given temperature."""
API_URL = f"https://api-inference.huggingface.co/models/{self.model}"
headers = {
diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py
index f0877d90..a0773c1d 100644
--- a/continuedev/src/continuedev/libs/llm/openai.py
+++ b/continuedev/src/continuedev/libs/llm/openai.py
@@ -1,54 +1,78 @@
from functools import cached_property
-import time
-from typing import Any, Coroutine, Dict, Generator, List, Union
+import json
+from typing import Any, Callable, Coroutine, Dict, Generator, List, Union
+
from ...core.main import ChatMessage
import openai
from ..llm import LLM
-from ..util.count_tokens import DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_MODELS, DEFAULT_ARGS, count_tokens, prune_raw_prompt_from_top
+from ..util.count_tokens import compile_chat_messages, CHAT_MODELS, DEFAULT_ARGS, count_tokens, format_chat_messages, prune_raw_prompt_from_top
+from ...core.config import AzureInfo
class OpenAI(LLM):
api_key: str
default_model: str
- def __init__(self, api_key: str, default_model: str, system_message: str = None):
+ def __init__(self, api_key: str, default_model: str, system_message: str = None, azure_info: AzureInfo = None, write_log: Callable[[str], None] = None):
self.api_key = api_key
self.default_model = default_model
self.system_message = system_message
+ self.azure_info = azure_info
+ self.write_log = write_log
openai.api_key = api_key
+ # Using an Azure OpenAI deployment
+ if azure_info is not None:
+ openai.api_type = "azure"
+ openai.api_base = azure_info.endpoint
+ openai.api_version = azure_info.api_version
+
@cached_property
def name(self):
return self.default_model
@property
def default_args(self):
- return {**DEFAULT_ARGS, "model": self.default_model}
+ args = {**DEFAULT_ARGS, "model": self.default_model}
+ if self.azure_info is not None:
+ args["engine"] = self.azure_info.engine
+ return args
def count_tokens(self, text: str):
return count_tokens(self.default_model, text)
- async def stream_complete(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ async def stream_complete(self, prompt, with_history: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
args = self.default_args.copy()
args.update(kwargs)
args["stream"] = True
if args["model"] in CHAT_MODELS:
+ messages = compile_chat_messages(
+ args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message)
+ self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
+ completion = ""
async for chunk in await openai.ChatCompletion.acreate(
- messages=compile_chat_messages(
- args["model"], with_history, args["max_tokens"], prompt, functions=None),
+ messages=messages,
**args,
):
if "content" in chunk.choices[0].delta:
yield chunk.choices[0].delta.content
+ completion += chunk.choices[0].delta.content
else:
continue
+
+ self.write_log(f"Completion: \n\n{completion}")
else:
+ self.write_log(f"Prompt:\n\n{prompt}")
+ completion = ""
async for chunk in await openai.Completion.acreate(prompt=prompt, **args):
yield chunk.choices[0].text
+ completion += chunk.choices[0].text
+
+ self.write_log(f"Completion:\n\n{completion}")
- async def stream_chat(self, messages: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ async def stream_chat(self, messages: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
args = self.default_args.copy()
args.update(kwargs)
args["stream"] = True
@@ -56,27 +80,39 @@ class OpenAI(LLM):
if not args["model"].endswith("0613") and "functions" in args:
del args["functions"]
+ messages = compile_chat_messages(
+ args["model"], messages, args["max_tokens"], None, functions=args.get("functions", None), system_message=self.system_message)
+ self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
+ completion = ""
async for chunk in await openai.ChatCompletion.acreate(
- messages=compile_chat_messages(
- args["model"], messages, args["max_tokens"], functions=args.get("functions", None)),
+ messages=messages,
**args,
):
yield chunk.choices[0].delta
+ if "content" in chunk.choices[0].delta:
+ completion += chunk.choices[0].delta.content
+ self.write_log(f"Completion: \n\n{completion}")
- async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
+ async def complete(self, prompt: str, with_history: List[ChatMessage] = None, **kwargs) -> Coroutine[Any, Any, str]:
args = {**self.default_args, **kwargs}
if args["model"] in CHAT_MODELS:
+ messages = compile_chat_messages(
+ args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message)
+ self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
resp = (await openai.ChatCompletion.acreate(
- messages=compile_chat_messages(
- args["model"], with_history, args["max_tokens"], prompt, functions=None),
+ messages=messages,
**args,
)).choices[0].message.content
+ self.write_log(f"Completion: \n\n{resp}")
else:
+ prompt = prune_raw_prompt_from_top(
+ args["model"], prompt, args["max_tokens"])
+ self.write_log(f"Prompt:\n\n{prompt}")
resp = (await openai.Completion.acreate(
- prompt=prune_raw_prompt_from_top(
- args["model"], prompt, args["max_tokens"]),
+ prompt=prompt,
**args,
)).choices[0].text
+ self.write_log(f"Completion:\n\n{resp}")
return resp
diff --git a/continuedev/src/continuedev/libs/llm/proxy_server.py b/continuedev/src/continuedev/libs/llm/proxy_server.py
index eab6e441..75c91c4e 100644
--- a/continuedev/src/continuedev/libs/llm/proxy_server.py
+++ b/continuedev/src/continuedev/libs/llm/proxy_server.py
@@ -1,10 +1,12 @@
-from functools import cached_property
+
import json
-from typing import Any, Coroutine, Dict, Generator, List, Literal, Union
+import traceback
+from typing import Any, Callable, Coroutine, Dict, Generator, List, Literal, Union
import aiohttp
+from ..util.telemetry import capture_event
from ...core.main import ChatMessage
from ..llm import LLM
-from ..util.count_tokens import DEFAULT_ARGS, DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_MODELS, count_tokens
+from ..util.count_tokens import DEFAULT_ARGS, DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_MODELS, count_tokens, format_chat_messages
import certifi
import ssl
@@ -19,12 +21,14 @@ class ProxyServer(LLM):
unique_id: str
name: str
default_model: Literal["gpt-3.5-turbo", "gpt-4"]
+ write_log: Callable[[str], None]
- def __init__(self, unique_id: str, default_model: Literal["gpt-3.5-turbo", "gpt-4"], system_message: str = None):
+ def __init__(self, unique_id: str, default_model: Literal["gpt-3.5-turbo", "gpt-4"], system_message: str = None, write_log: Callable[[str], None] = None):
self.unique_id = unique_id
self.default_model = default_model
self.system_message = system_message
self.name = default_model
+ self.write_log = write_log
@property
def default_args(self):
@@ -32,33 +36,44 @@ class ProxyServer(LLM):
def count_tokens(self, text: str):
return count_tokens(self.default_model, text)
+
+ def get_headers(self):
+ # headers with unique id
+ return {"unique_id": self.unique_id}
- async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
+ async def complete(self, prompt: str, with_history: List[ChatMessage] = None, **kwargs) -> Coroutine[Any, Any, str]:
args = {**self.default_args, **kwargs}
+ messages = compile_chat_messages(
+ args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message)
+ self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session:
async with session.post(f"{SERVER_URL}/complete", json={
- "messages": compile_chat_messages(args["model"], with_history, args["max_tokens"], prompt, functions=None),
- "unique_id": self.unique_id,
+ "messages": messages,
**args
- }) as resp:
- try:
- return await resp.text()
- except:
+ }, headers=self.get_headers()) as resp:
+ if resp.status != 200:
raise Exception(await resp.text())
- async def stream_chat(self, messages: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, Generator[Union[Any, List, Dict], None, None]]:
+ response_text = await resp.text()
+ self.write_log(f"Completion: \n\n{response_text}")
+ return response_text
+
+ async def stream_chat(self, messages: List[ChatMessage] = None, **kwargs) -> Coroutine[Any, Any, Generator[Union[Any, List, Dict], None, None]]:
args = {**self.default_args, **kwargs}
messages = compile_chat_messages(
- self.default_model, messages, args["max_tokens"], None, functions=args.get("functions", None))
+ args["model"], messages, args["max_tokens"], None, functions=args.get("functions", None), system_message=self.system_message)
+ self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session:
async with session.post(f"{SERVER_URL}/stream_chat", json={
"messages": messages,
- "unique_id": self.unique_id,
**args
- }) as resp:
+ }, headers=self.get_headers()) as resp:
# This is streaming application/json instaed of text/event-stream
+ completion = ""
+ if resp.status != 200:
+ raise Exception(await resp.text())
async for line in resp.content.iter_chunks():
if line[1]:
try:
@@ -67,24 +82,38 @@ class ProxyServer(LLM):
chunks = json_chunk.split("\n")
for chunk in chunks:
if chunk.strip() != "":
- yield json.loads(chunk)
- except:
- raise Exception(str(line[0]))
+ loaded_chunk = json.loads(chunk)
+ yield loaded_chunk
+ if "content" in loaded_chunk:
+ completion += loaded_chunk["content"]
+ except Exception as e:
+ capture_event(self.unique_id, "proxy_server_parse_error", {
+ "error_title": "Proxy server stream_chat parsing failed", "error_message": '\n'.join(traceback.format_exception(e))})
+ else:
+ break
- async def stream_complete(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ self.write_log(f"Completion: \n\n{completion}")
+
+ async def stream_complete(self, prompt, with_history: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
args = {**self.default_args, **kwargs}
messages = compile_chat_messages(
- self.default_model, with_history, args["max_tokens"], prompt, functions=args.get("functions", None))
+ self.default_model, with_history, args["max_tokens"], prompt, functions=args.get("functions", None), system_message=self.system_message)
+ self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}")
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session:
async with session.post(f"{SERVER_URL}/stream_complete", json={
"messages": messages,
- "unique_id": self.unique_id,
**args
- }) as resp:
+ }, headers=self.get_headers()) as resp:
+ completion = ""
+ if resp.status != 200:
+ raise Exception(await resp.text())
async for line in resp.content.iter_any():
if line:
try:
- yield line.decode("utf-8")
+ decoded_line = line.decode("utf-8")
+ yield decoded_line
+ completion += decoded_line
except:
raise Exception(str(line))
+ self.write_log(f"Completion: \n\n{completion}")
diff --git a/continuedev/src/continuedev/libs/util/commonregex.py b/continuedev/src/continuedev/libs/util/commonregex.py
new file mode 100644
index 00000000..55da7fc0
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/commonregex.py
@@ -0,0 +1,138 @@
+# coding: utf-8
+import json
+import re
+from typing import Any, Dict
+
+date = re.compile(
+ '(?:(?<!\:)(?<!\:\d)[0-3]?\d(?:st|nd|rd|th)?\s+(?:of\s+)?(?:jan\.?|january|feb\.?|february|mar\.?|march|apr\.?|april|may|jun\.?|june|jul\.?|july|aug\.?|august|sep\.?|september|oct\.?|october|nov\.?|november|dec\.?|december)|(?:jan\.?|january|feb\.?|february|mar\.?|march|apr\.?|april|may|jun\.?|june|jul\.?|july|aug\.?|august|sep\.?|september|oct\.?|october|nov\.?|november|dec\.?|december)\s+(?<!\:)(?<!\:\d)[0-3]?\d(?:st|nd|rd|th)?)(?:\,)?\s*(?:\d{4})?|[0-3]?\d[-\./][0-3]?\d[-\./]\d{2,4}', re.IGNORECASE)
+time = re.compile(
+ '\d{1,2}:\d{2} ?(?:[ap]\.?m\.?)?|\d[ap]\.?m\.?', re.IGNORECASE)
+phone = re.compile(
+ '''((?:(?<![\d-])(?:\+?\d{1,3}[-.\s*]?)?(?:\(?\d{3}\)?[-.\s*]?)?\d{3}[-.\s*]?\d{4}(?![\d-]))|(?:(?<![\d-])(?:(?:\(\+?\d{2}\))|(?:\+?\d{2}))\s*\d{2}\s*\d{3}\s*\d{4}(?![\d-])))''')
+phones_with_exts = re.compile(
+ '((?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*(?:[2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|(?:[2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?(?:[2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?(?:[0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(?:\d+)?))', re.IGNORECASE)
+link = re.compile('(?i)((?:https?://|www\d{0,3}[.])?[a-z0-9.\-]+[.](?:(?:international)|(?:construction)|(?:contractors)|(?:enterprises)|(?:photography)|(?:immobilien)|(?:management)|(?:technology)|(?:directory)|(?:education)|(?:equipment)|(?:institute)|(?:marketing)|(?:solutions)|(?:builders)|(?:clothing)|(?:computer)|(?:democrat)|(?:diamonds)|(?:graphics)|(?:holdings)|(?:lighting)|(?:plumbing)|(?:training)|(?:ventures)|(?:academy)|(?:careers)|(?:company)|(?:domains)|(?:florist)|(?:gallery)|(?:guitars)|(?:holiday)|(?:kitchen)|(?:recipes)|(?:shiksha)|(?:singles)|(?:support)|(?:systems)|(?:agency)|(?:berlin)|(?:camera)|(?:center)|(?:coffee)|(?:estate)|(?:kaufen)|(?:luxury)|(?:monash)|(?:museum)|(?:photos)|(?:repair)|(?:social)|(?:tattoo)|(?:travel)|(?:viajes)|(?:voyage)|(?:build)|(?:cheap)|(?:codes)|(?:dance)|(?:email)|(?:glass)|(?:house)|(?:ninja)|(?:photo)|(?:shoes)|(?:solar)|(?:today)|(?:aero)|(?:arpa)|(?:asia)|(?:bike)|(?:buzz)|(?:camp)|(?:club)|(?:coop)|(?:farm)|(?:gift)|(?:guru)|(?:info)|(?:jobs)|(?:kiwi)|(?:land)|(?:limo)|(?:link)|(?:menu)|(?:mobi)|(?:moda)|(?:name)|(?:pics)|(?:pink)|(?:post)|(?:rich)|(?:ruhr)|(?:sexy)|(?:tips)|(?:wang)|(?:wien)|(?:zone)|(?:biz)|(?:cab)|(?:cat)|(?:ceo)|(?:com)|(?:edu)|(?:gov)|(?:int)|(?:mil)|(?:net)|(?:onl)|(?:org)|(?:pro)|(?:red)|(?:tel)|(?:uno)|(?:xxx)|(?:ac)|(?:ad)|(?:ae)|(?:af)|(?:ag)|(?:ai)|(?:al)|(?:am)|(?:an)|(?:ao)|(?:aq)|(?:ar)|(?:as)|(?:at)|(?:au)|(?:aw)|(?:ax)|(?:az)|(?:ba)|(?:bb)|(?:bd)|(?:be)|(?:bf)|(?:bg)|(?:bh)|(?:bi)|(?:bj)|(?:bm)|(?:bn)|(?:bo)|(?:br)|(?:bs)|(?:bt)|(?:bv)|(?:bw)|(?:by)|(?:bz)|(?:ca)|(?:cc)|(?:cd)|(?:cf)|(?:cg)|(?:ch)|(?:ci)|(?:ck)|(?:cl)|(?:cm)|(?:cn)|(?:co)|(?:cr)|(?:cu)|(?:cv)|(?:cw)|(?:cx)|(?:cy)|(?:cz)|(?:de)|(?:dj)|(?:dk)|(?:dm)|(?:do)|(?:dz)|(?:ec)|(?:ee)|(?:eg)|(?:er)|(?:es)|(?:et)|(?:eu)|(?:fi)|(?:fj)|(?:fk)|(?:fm)|(?:fo)|(?:fr)|(?:ga)|(?:gb)|(?:gd)|(?:ge)|(?:gf)|(?:gg)|(?:gh)|(?:gi)|(?:gl)|(?:gm)|(?:gn)|(?:gp)|(?:gq)|(?:gr)|(?:gs)|(?:gt)|(?:gu)|(?:gw)|(?:gy)|(?:hk)|(?:hm)|(?:hn)|(?:hr)|(?:ht)|(?:hu)|(?:id)|(?:ie)|(?:il)|(?:im)|(?:in)|(?:io)|(?:iq)|(?:ir)|(?:is)|(?:it)|(?:je)|(?:jm)|(?:jo)|(?:jp)|(?:ke)|(?:kg)|(?:kh)|(?:ki)|(?:km)|(?:kn)|(?:kp)|(?:kr)|(?:kw)|(?:ky)|(?:kz)|(?:la)|(?:lb)|(?:lc)|(?:li)|(?:lk)|(?:lr)|(?:ls)|(?:lt)|(?:lu)|(?:lv)|(?:ly)|(?:ma)|(?:mc)|(?:md)|(?:me)|(?:mg)|(?:mh)|(?:mk)|(?:ml)|(?:mm)|(?:mn)|(?:mo)|(?:mp)|(?:mq)|(?:mr)|(?:ms)|(?:mt)|(?:mu)|(?:mv)|(?:mw)|(?:mx)|(?:my)|(?:mz)|(?:na)|(?:nc)|(?:ne)|(?:nf)|(?:ng)|(?:ni)|(?:nl)|(?:no)|(?:np)|(?:nr)|(?:nu)|(?:nz)|(?:om)|(?:pa)|(?:pe)|(?:pf)|(?:pg)|(?:ph)|(?:pk)|(?:pl)|(?:pm)|(?:pn)|(?:pr)|(?:ps)|(?:pt)|(?:pw)|(?:py)|(?:qa)|(?:re)|(?:ro)|(?:rs)|(?:ru)|(?:rw)|(?:sa)|(?:sb)|(?:sc)|(?:sd)|(?:se)|(?:sg)|(?:sh)|(?:si)|(?:sj)|(?:sk)|(?:sl)|(?:sm)|(?:sn)|(?:so)|(?:sr)|(?:st)|(?:su)|(?:sv)|(?:sx)|(?:sy)|(?:sz)|(?:tc)|(?:td)|(?:tf)|(?:tg)|(?:th)|(?:tj)|(?:tk)|(?:tl)|(?:tm)|(?:tn)|(?:to)|(?:tp)|(?:tr)|(?:tt)|(?:tv)|(?:tw)|(?:tz)|(?:ua)|(?:ug)|(?:uk)|(?:us)|(?:uy)|(?:uz)|(?:va)|(?:vc)|(?:ve)|(?:vg)|(?:vi)|(?:vn)|(?:vu)|(?:wf)|(?:ws)|(?:ye)|(?:yt)|(?:za)|(?:zm)|(?:zw))(?:/[^\s()<>]+[^\s`!()\[\]{};:\'".,<>?\xab\xbb\u201c\u201d\u2018\u2019])?)', re.IGNORECASE)
+email = re.compile(
+ "([a-z0-9!#$%&'*+\/=?^_`{|.}~-]+@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)", re.IGNORECASE)
+ip = re.compile('(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)', re.IGNORECASE)
+ipv6 = re.compile(
+ '\s*(?!.*::.*::)(?:(?!:)|:(?=:))(?:[0-9a-f]{0,4}(?:(?<=::)|(?<!::):)){6}(?:[0-9a-f]{0,4}(?:(?<=::)|(?<!::):)[0-9a-f]{0,4}(?:(?<=::)|(?<!:)|(?<=:)(?<!::):)|(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)){3})\s*', re.VERBOSE | re.IGNORECASE | re.DOTALL)
+price = re.compile('[$]\s?[+-]?[0-9]{1,3}(?:(?:,?[0-9]{3}))*(?:\.[0-9]{1,2})?')
+hex_color = re.compile('(#(?:[0-9a-fA-F]{8})|#(?:[0-9a-fA-F]{3}){1,2})\\b')
+credit_card = re.compile('((?:(?:\\d{4}[- ]?){3}\\d{4}|\\d{15,16}))(?![\\d])')
+btc_address = re.compile(
+ '(?<![a-km-zA-HJ-NP-Z0-9])[13][a-km-zA-HJ-NP-Z0-9]{26,33}(?![a-km-zA-HJ-NP-Z0-9])')
+street_address = re.compile(
+ '\d{1,4} [\w\s]{1,20}(?:street|st|avenue|ave|road|rd|highway|hwy|square|sq|trail|trl|drive|dr|court|ct|park|parkway|pkwy|circle|cir|boulevard|blvd)\W?(?=\s|$)', re.IGNORECASE)
+zip_code = re.compile(r'\b\d{5}(?:[-\s]\d{4})?\b')
+po_box = re.compile(r'P\.? ?O\.? Box \d+', re.IGNORECASE)
+ssn = re.compile(
+ '(?!000|666|333)0*(?:[0-6][0-9][0-9]|[0-7][0-6][0-9]|[0-7][0-7][0-2])[- ](?!00)[0-9]{2}[- ](?!0000)[0-9]{4}')
+win_absolute_filepath = re.compile(
+ r'^(?:[a-zA-Z]\:|\\\\[\w\.]+\\[\w.$]+)\\(?:[\w]+\\)*\w([\w.])+', re.IGNORECASE)
+unix_absolute_filepath = re.compile(
+ r'^\/(?:[\/\w]+\/)*\w([\w.])+', re.IGNORECASE)
+
+regexes = {
+ "win_absolute_filepath": win_absolute_filepath,
+ "unix_absolute_filepath": unix_absolute_filepath,
+ "dates": date,
+ "times": time,
+ "phones": phone,
+ "phones_with_exts": phones_with_exts,
+ "links": link,
+ "emails": email,
+ "ips": ip,
+ "ipv6s": ipv6,
+ "prices": price,
+ "hex_colors": hex_color,
+ "credit_cards": credit_card,
+ "btc_addresses": btc_address,
+ "street_addresses": street_address,
+ "zip_codes": zip_code,
+ "po_boxes": po_box,
+ "ssn_number": ssn,
+}
+
+placeholders = {
+ "win_absolute_filepath": "<FILEPATH>",
+ "unix_absolute_filepath": "<FILEPATH>",
+ "dates": "<DATE>",
+ "times": "<TIME>",
+ "phones": "<PHONE>",
+ "phones_with_exts": "<PHONE_WITH_EXT>",
+ "links": "<LINK>",
+ "emails": "<EMAIL>",
+ "ips": "<IP>",
+ "ipv6s": "<IPV6>",
+ "prices": "<PRICE>",
+ "hex_colors": "<HEX_COLOR>",
+ "credit_cards": "<CREDIT_CARD>",
+ "btc_addresses": "<BTC_ADDRESS>",
+ "street_addresses": "<STREET_ADDRESS>",
+ "zip_codes": "<ZIP_CODE>",
+ "po_boxes": "<PO_BOX>",
+ "ssn_number": "<SSN>",
+}
+
+
+class regex:
+
+ def __init__(self, obj, regex):
+ self.obj = obj
+ self.regex = regex
+
+ def __call__(self, *args):
+ def regex_method(text=None):
+ return [x.strip() for x in self.regex.findall(text or self.obj.text)]
+ return regex_method
+
+
+class CommonRegex(object):
+
+ def __init__(self, text=""):
+ self.text = text
+
+ for k, v in list(regexes.items()):
+ setattr(self, k, regex(self, v)(self))
+
+ if text:
+ for key in list(regexes.keys()):
+ method = getattr(self, key)
+ setattr(self, key, method())
+
+
+pii_parser = CommonRegex()
+
+
+def clean_pii_from_str(text: str):
+ """Replace personally identifiable information (PII) with placeholders."""
+ for regex_name, regex in list(regexes.items()):
+ placeholder = placeholders[regex_name]
+ text = regex.sub(placeholder, text)
+
+ return text
+
+
+def clean_pii_from_any(v: Any) -> Any:
+ """Replace personally identifiable information (PII) with placeholders. Not guaranteed to return same type as input."""
+ if isinstance(v, str):
+ return clean_pii_from_str(v)
+ elif isinstance(v, dict):
+ cleaned_dict = {}
+ for key, value in v.items():
+ cleaned_dict[key] = clean_pii_from_any(value)
+ return cleaned_dict
+ elif isinstance(v, list):
+ return [clean_pii_from_any(x) for x in v]
+ else:
+ # Try to convert to string
+ try:
+ orig_text = str(v)
+ cleaned_text = clean_pii_from_str(orig_text)
+ if orig_text != cleaned_text:
+ return cleaned_text
+ else:
+ return v
+ except:
+ return v
diff --git a/continuedev/src/continuedev/libs/util/count_tokens.py b/continuedev/src/continuedev/libs/util/count_tokens.py
index 73be0717..c58ae499 100644
--- a/continuedev/src/continuedev/libs/util/count_tokens.py
+++ b/continuedev/src/continuedev/libs/util/count_tokens.py
@@ -1,15 +1,21 @@
import json
from typing import Dict, List, Union
from ...core.main import ChatMessage
+from .templating import render_templated_string
import tiktoken
-aliases = {}
+aliases = {
+ "ggml": "gpt-3.5-turbo",
+ "claude-2": "gpt-3.5-turbo",
+}
DEFAULT_MAX_TOKENS = 2048
MAX_TOKENS_FOR_MODEL = {
"gpt-3.5-turbo": 4096,
"gpt-3.5-turbo-0613": 4096,
"gpt-3.5-turbo-16k": 16384,
- "gpt-4": 8192
+ "gpt-4": 8192,
+ "ggml": 2048,
+ "claude-2": 100000
}
CHAT_MODELS = {
"gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4", "gpt-3.5-turbo-0613"
@@ -40,9 +46,17 @@ def prune_raw_prompt_from_top(model: str, prompt: str, tokens_for_completion: in
return encoding.decode(tokens[-max_tokens:])
+def count_chat_message_tokens(model: str, chat_message: ChatMessage) -> int:
+ # Doing simpler, safer version of what is here:
+ # https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
+ # every message follows <|start|>{role/name}\n{content}<|end|>\n
+ TOKENS_PER_MESSAGE = 4
+ return count_tokens(model, chat_message.content) + TOKENS_PER_MESSAGE
+
+
def prune_chat_history(model: str, chat_history: List[ChatMessage], max_tokens: int, tokens_for_completion: int):
total_tokens = tokens_for_completion + \
- sum(count_tokens(model, message.content)
+ sum(count_chat_message_tokens(model, message)
for message in chat_history)
# 1. Replace beyond last 5 messages with summary
@@ -59,43 +73,76 @@ def prune_chat_history(model: str, chat_history: List[ChatMessage], max_tokens:
message = chat_history.pop(0)
total_tokens -= count_tokens(model, message.content)
- # 3. Truncate message in the last 5
+ # 3. Truncate message in the last 5, except last 1
i = 0
- while total_tokens > max_tokens and len(chat_history) > 0 and i < len(chat_history):
+ while total_tokens > max_tokens and len(chat_history) > 0 and i < len(chat_history) - 1:
message = chat_history[i]
total_tokens -= count_tokens(model, message.content)
total_tokens += count_tokens(model, message.summary)
message.content = message.summary
i += 1
- # 4. Remove entire messages in the last 5
- while total_tokens > max_tokens and len(chat_history) > 0:
+ # 4. Remove entire messages in the last 5, except last 1
+ while total_tokens > max_tokens and len(chat_history) > 1:
message = chat_history.pop(0)
total_tokens -= count_tokens(model, message.content)
+ # 5. Truncate last message
+ if total_tokens > max_tokens and len(chat_history) > 0:
+ message = chat_history[0]
+ message.content = prune_raw_prompt_from_top(
+ model, message.content, tokens_for_completion)
+ total_tokens = max_tokens
+
return chat_history
-def compile_chat_messages(model: str, msgs: List[ChatMessage], max_tokens: int, prompt: Union[str, None] = None, functions: Union[List, None] = None, system_message: Union[str, None] = None) -> List[Dict]:
- prompt_tokens = count_tokens(model, prompt)
+# In case we've missed weird edge cases
+TOKEN_BUFFER_FOR_SAFETY = 100
+
+
+def compile_chat_messages(model: str, msgs: Union[List[ChatMessage], None], max_tokens: int, prompt: Union[str, None] = None, functions: Union[List, None] = None, system_message: Union[str, None] = None) -> List[Dict]:
+ """
+ The total number of tokens is system_message + sum(msgs) + functions + prompt after it is converted to a message
+ """
+ msgs_copy = [msg.copy(deep=True)
+ for msg in msgs] if msgs is not None else []
+
+ if prompt is not None:
+ prompt_msg = ChatMessage(role="user", content=prompt, summary=prompt)
+ msgs_copy += [prompt_msg]
+
+ if system_message is not None:
+ # NOTE: System message takes second precedence to user prompt, so it is placed just before
+ # but move back to start after processing
+ rendered_system_message = render_templated_string(system_message)
+ system_chat_msg = ChatMessage(
+ role="system", content=rendered_system_message, summary=rendered_system_message)
+ # insert at second-to-last position
+ msgs_copy.insert(-1, system_chat_msg)
+
+ # Add tokens from functions
+ function_tokens = 0
if functions is not None:
for function in functions:
- prompt_tokens += count_tokens(model, json.dumps(function))
-
- msgs = prune_chat_history(model,
- msgs, MAX_TOKENS_FOR_MODEL[model], prompt_tokens + max_tokens + count_tokens(model, system_message))
- history = []
- if system_message:
- history.append({
- "role": "system",
- "content": system_message
- })
- history += [msg.to_dict(with_functions=functions is not None)
- for msg in msgs]
- if prompt:
- history.append({
- "role": "user",
- "content": prompt
- })
+ function_tokens += count_tokens(model, json.dumps(function))
+
+ msgs_copy = prune_chat_history(
+ model, msgs_copy, MAX_TOKENS_FOR_MODEL[model], function_tokens + max_tokens + TOKEN_BUFFER_FOR_SAFETY)
+
+ history = [msg.to_dict(with_functions=functions is not None)
+ for msg in msgs_copy]
+
+ # Move system message back to start
+ if system_message is not None and len(history) >= 2 and history[-2]["role"] == "system":
+ system_message_dict = history.pop(-2)
+ history.insert(0, system_message_dict)
return history
+
+
+def format_chat_messages(messages: List[ChatMessage]) -> str:
+ formatted = ""
+ for msg in messages:
+ formatted += f"<{msg['role'].capitalize()}>\n{msg['content']}\n\n"
+ return formatted
diff --git a/continuedev/src/continuedev/libs/util/step_name_to_steps.py b/continuedev/src/continuedev/libs/util/step_name_to_steps.py
index d329e110..baa25da6 100644
--- a/continuedev/src/continuedev/libs/util/step_name_to_steps.py
+++ b/continuedev/src/continuedev/libs/util/step_name_to_steps.py
@@ -1,18 +1,19 @@
from typing import Dict
from ...core.main import Step
-from ...steps.core.core import UserInputStep
-from ...steps.main import EditHighlightedCodeStep
-from ...steps.chat import SimpleChatStep
-from ...steps.comment_code import CommentCodeStep
-from ...steps.feedback import FeedbackStep
-from ...recipes.AddTransformRecipe.main import AddTransformRecipe
-from ...recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
-from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe
-from ...recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe
-from ...steps.on_traceback import DefaultOnTracebackStep
-from ...steps.clear_history import ClearHistoryStep
-from ...steps.open_config import OpenConfigStep
+from ...plugins.steps.core.core import UserInputStep
+from ...plugins.steps.main import EditHighlightedCodeStep
+from ...plugins.steps.chat import SimpleChatStep
+from ...plugins.steps.comment_code import CommentCodeStep
+from ...plugins.steps.feedback import FeedbackStep
+from ...plugins.recipes.AddTransformRecipe.main import AddTransformRecipe
+from ...plugins.recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
+from ...plugins.recipes.DDtoBQRecipe.main import DDtoBQRecipe
+from ...plugins.recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe
+from ...plugins.steps.on_traceback import DefaultOnTracebackStep
+from ...plugins.steps.clear_history import ClearHistoryStep
+from ...plugins.steps.open_config import OpenConfigStep
+from ...plugins.steps.help import HelpStep
# This mapping is used to convert from string in ContinueConfig json to corresponding Step class.
# Used for example in slash_commands and steps_on_startup
@@ -28,7 +29,8 @@ step_name_to_step_class = {
"DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe,
"DefaultOnTracebackStep": DefaultOnTracebackStep,
"ClearHistoryStep": ClearHistoryStep,
- "OpenConfigStep": OpenConfigStep
+ "OpenConfigStep": OpenConfigStep,
+ "HelpStep": HelpStep,
}
diff --git a/continuedev/src/continuedev/libs/util/dedent.py b/continuedev/src/continuedev/libs/util/strings.py
index e59c2e97..f1fb8d0b 100644
--- a/continuedev/src/continuedev/libs/util/dedent.py
+++ b/continuedev/src/continuedev/libs/util/strings.py
@@ -23,3 +23,27 @@ def dedent_and_get_common_whitespace(s: str) -> Tuple[str, str]:
break
return "\n".join(map(lambda x: x.lstrip(lcp), lines)), lcp
+
+
+def remove_quotes_and_escapes(output: str) -> str:
+ """
+ Clean up the output of the completion API, removing unnecessary escapes and quotes
+ """
+ output = output.strip()
+
+ # Replace smart quotes
+ output = output.replace("“", '"')
+ output = output.replace("”", '"')
+ output = output.replace("‘", "'")
+ output = output.replace("’", "'")
+
+ # Remove escapes
+ output = output.replace('\\"', '"')
+ output = output.replace("\\'", "'")
+ output = output.replace("\\n", "\n")
+ output = output.replace("\\t", "\t")
+ output = output.replace("\\\\", "\\")
+ if (output.startswith('"') and output.endswith('"')) or (output.startswith("'") and output.endswith("'")):
+ output = output[1:-1]
+
+ return output
diff --git a/continuedev/src/continuedev/libs/util/telemetry.py b/continuedev/src/continuedev/libs/util/telemetry.py
index bd9fde9d..17735dce 100644
--- a/continuedev/src/continuedev/libs/util/telemetry.py
+++ b/continuedev/src/continuedev/libs/util/telemetry.py
@@ -3,6 +3,7 @@ from posthog import Posthog
from ...core.config import load_config
import os
from dotenv import load_dotenv
+from .commonregex import clean_pii_from_any
load_dotenv()
in_codespaces = os.getenv("CODESPACES") == "true"
@@ -13,10 +14,14 @@ posthog = Posthog('phc_JS6XFROuNbhJtVCEdTSYk6gl5ArRrTNMpCcguAXlSPs',
def capture_event(unique_id: str, event_name: str, event_properties: Any):
+ # Return early if telemetry is disabled
config = load_config('.continue/config.json')
if not config.allow_anonymous_telemetry:
return
if in_codespaces:
event_properties['codespaces'] = True
- posthog.capture(unique_id, event_name, event_properties)
+
+ # Send event to PostHog
+ posthog.capture(unique_id, event_name,
+ clean_pii_from_any(event_properties))
diff --git a/continuedev/src/continuedev/libs/util/templating.py b/continuedev/src/continuedev/libs/util/templating.py
new file mode 100644
index 00000000..bb922ad7
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/templating.py
@@ -0,0 +1,39 @@
+import os
+import chevron
+
+
+def get_vars_in_template(template):
+ """
+ Get the variables in a template
+ """
+ return [token[1] for token in chevron.tokenizer.tokenize(template) if token[0] == 'variable']
+
+
+def escape_var(var: str) -> str:
+ """
+ Escape a variable so it can be used in a template
+ """
+ return var.replace(os.path.sep, '').replace('.', '')
+
+
+def render_templated_string(template: str) -> str:
+ """
+ Render system message or other templated string with mustache syntax.
+ Right now it only supports rendering absolute file paths as their contents.
+ """
+ vars = get_vars_in_template(template)
+
+ args = {}
+ for var in vars:
+ if var.startswith(os.path.sep):
+ # Escape vars which are filenames, because mustache doesn't allow / in variable names
+ escaped_var = escape_var(var)
+ template = template.replace(
+ var, escaped_var)
+
+ if os.path.exists(var):
+ args[escaped_var] = open(var, 'r').read()
+ else:
+ args[escaped_var] = ''
+
+ return chevron.render(template, args)
diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md b/continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/README.md
index d735e0cd..d735e0cd 100644
--- a/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md
+++ b/continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/README.md
diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/dlt_transform_docs.md b/continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/dlt_transform_docs.md
index 658b285f..658b285f 100644
--- a/continuedev/src/continuedev/recipes/AddTransformRecipe/dlt_transform_docs.md
+++ b/continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/dlt_transform_docs.md
diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py b/continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/main.py
index fdd343f5..5d242f7c 100644
--- a/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py
+++ b/continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/main.py
@@ -1,9 +1,9 @@
from textwrap import dedent
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
-from ...steps.core.core import WaitForUserInputStep
-from ...steps.core.core import MessageStep
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
+from ....plugins.steps.core.core import WaitForUserInputStep
+from ....plugins.steps.core.core import MessageStep
from .steps import SetUpChessPipelineStep, AddTransformStep
diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py b/continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/steps.py
index 9744146c..8c6446da 100644
--- a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py
+++ b/continuedev/src/continuedev/plugins/recipes/AddTransformRecipe/steps.py
@@ -1,14 +1,10 @@
import os
from textwrap import dedent
-from ...models.main import Range
-from ...models.filesystem import RangeInFile
-from ...steps.core.core import MessageStep
-from ...core.sdk import Models
-from ...core.observation import DictObservation
-from ...models.filesystem_edit import AddFile
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
+from ....plugins.steps.core.core import MessageStep
+from ....core.sdk import Models
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
AI_ASSISTED_STRING = "(✨ AI-Assisted ✨)"
diff --git a/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/README.md b/continuedev/src/continuedev/plugins/recipes/ContinueRecipeRecipe/README.md
index df66104f..df66104f 100644
--- a/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/README.md
+++ b/continuedev/src/continuedev/plugins/recipes/ContinueRecipeRecipe/README.md
diff --git a/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/main.py b/continuedev/src/continuedev/plugins/recipes/ContinueRecipeRecipe/main.py
index 953fb0c2..c0f9e7e3 100644
--- a/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/main.py
+++ b/continuedev/src/continuedev/plugins/recipes/ContinueRecipeRecipe/main.py
@@ -1,8 +1,7 @@
from textwrap import dedent
-from ...models.filesystem import RangeInFile
-from ...steps.main import EditHighlightedCodeStep
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
+from ....plugins.steps.main import EditHighlightedCodeStep
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
class ContinueStepStep(Step):
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/README.md b/continuedev/src/continuedev/plugins/recipes/CreatePipelineRecipe/README.md
index e69de29b..e69de29b 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/README.md
+++ b/continuedev/src/continuedev/plugins/recipes/CreatePipelineRecipe/README.md
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/plugins/recipes/CreatePipelineRecipe/main.py
index 55ef107b..84363e02 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
+++ b/continuedev/src/continuedev/plugins/recipes/CreatePipelineRecipe/main.py
@@ -1,9 +1,9 @@
from textwrap import dedent
-from ...core.sdk import ContinueSDK
-from ...core.main import Step
-from ...steps.core.core import WaitForUserInputStep
-from ...steps.core.core import MessageStep
+from ....core.sdk import ContinueSDK
+from ....core.main import Step
+from ....plugins.steps.core.core import WaitForUserInputStep
+from ....plugins.steps.core.core import MessageStep
from .steps import SetupPipelineStep, ValidatePipelineStep, RunQueryStep
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/plugins/recipes/CreatePipelineRecipe/steps.py
index 60218ef9..433e309e 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
+++ b/continuedev/src/continuedev/plugins/recipes/CreatePipelineRecipe/steps.py
@@ -1,15 +1,13 @@
import os
-import subprocess
from textwrap import dedent
import time
-from ...models.main import Range
-from ...models.filesystem import RangeInFile
-from ...steps.core.core import MessageStep
-from ...core.observation import DictObservation, InternalErrorObservation
-from ...models.filesystem_edit import AddFile, FileEdit
-from ...core.main import Step
-from ...core.sdk import ContinueSDK, Models
+from ....models.main import Range
+from ....models.filesystem import RangeInFile
+from ....plugins.steps.core.core import MessageStep
+from ....models.filesystem_edit import AddFile, FileEdit
+from ....core.main import Step
+from ....core.sdk import ContinueSDK, Models
AI_ASSISTED_STRING = "(✨ AI-Assisted ✨)"
diff --git a/continuedev/src/continuedev/recipes/DDtoBQRecipe/README.md b/continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/README.md
index c4981e56..c4981e56 100644
--- a/continuedev/src/continuedev/recipes/DDtoBQRecipe/README.md
+++ b/continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/README.md
diff --git a/continuedev/src/continuedev/recipes/DDtoBQRecipe/dlt_duckdb_to_bigquery_docs.md b/continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/dlt_duckdb_to_bigquery_docs.md
index eb68e117..eb68e117 100644
--- a/continuedev/src/continuedev/recipes/DDtoBQRecipe/dlt_duckdb_to_bigquery_docs.md
+++ b/continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/dlt_duckdb_to_bigquery_docs.md
diff --git a/continuedev/src/continuedev/recipes/DDtoBQRecipe/main.py b/continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/main.py
index 1ae84310..5b6aa8f0 100644
--- a/continuedev/src/continuedev/recipes/DDtoBQRecipe/main.py
+++ b/continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/main.py
@@ -1,9 +1,8 @@
from textwrap import dedent
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
-from ...steps.core.core import WaitForUserInputStep
-from ...steps.core.core import MessageStep
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
+from ....plugins.steps.core.core import MessageStep
from .steps import SetUpChessPipelineStep, SwitchDestinationStep, LoadDataStep
# Based on the following guide:
diff --git a/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py b/continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/steps.py
index df414e2e..767936b8 100644
--- a/continuedev/src/continuedev/recipes/DDtoBQRecipe/steps.py
+++ b/continuedev/src/continuedev/plugins/recipes/DDtoBQRecipe/steps.py
@@ -1,17 +1,11 @@
import os
-import subprocess
from textwrap import dedent
-import time
-
-from ...steps.find_and_replace import FindAndReplaceStep
-from ...models.main import Range
-from ...models.filesystem import RangeInFile
-from ...steps.core.core import MessageStep
-from ...core.sdk import Models
-from ...core.observation import DictObservation, InternalErrorObservation
-from ...models.filesystem_edit import AddFile, FileEdit
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
+
+from ....plugins.steps.find_and_replace import FindAndReplaceStep
+from ....plugins.steps.core.core import MessageStep
+from ....core.sdk import Models
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
AI_ASSISTED_STRING = "(✨ AI-Assisted ✨)"
diff --git a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/README.md b/continuedev/src/continuedev/plugins/recipes/DeployPipelineAirflowRecipe/README.md
index e69de29b..e69de29b 100644
--- a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/README.md
+++ b/continuedev/src/continuedev/plugins/recipes/DeployPipelineAirflowRecipe/README.md
diff --git a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/main.py b/continuedev/src/continuedev/plugins/recipes/DeployPipelineAirflowRecipe/main.py
index 2a3e3566..54cba45f 100644
--- a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/main.py
+++ b/continuedev/src/continuedev/plugins/recipes/DeployPipelineAirflowRecipe/main.py
@@ -1,10 +1,9 @@
from textwrap import dedent
-from ...steps.input.nl_multiselect import NLMultiselectStep
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
-from ...steps.core.core import WaitForUserInputStep
-from ...steps.core.core import MessageStep
+from ....plugins.steps.input.nl_multiselect import NLMultiselectStep
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
+from ....plugins.steps.core.core import MessageStep
from .steps import SetupPipelineStep, DeployAirflowStep, RunPipelineStep
diff --git a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py b/continuedev/src/continuedev/plugins/recipes/DeployPipelineAirflowRecipe/steps.py
index d9bdbc0a..83067d52 100644
--- a/continuedev/src/continuedev/recipes/DeployPipelineAirflowRecipe/steps.py
+++ b/continuedev/src/continuedev/plugins/recipes/DeployPipelineAirflowRecipe/steps.py
@@ -1,18 +1,11 @@
import os
-import subprocess
from textwrap import dedent
-import time
-
-from ...steps.core.core import WaitForUserInputStep
-from ...models.main import Range
-from ...models.filesystem import RangeInFile
-from ...steps.core.core import MessageStep
-from ...core.sdk import Models
-from ...core.observation import DictObservation, InternalErrorObservation
-from ...models.filesystem_edit import AddFile, FileEdit
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
-from ...steps.find_and_replace import FindAndReplaceStep
+
+from ....plugins.steps.core.core import MessageStep
+from ....core.sdk import Models
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
+from ....plugins.steps.find_and_replace import FindAndReplaceStep
AI_ASSISTED_STRING = "(✨ AI-Assisted ✨)"
@@ -93,5 +86,3 @@ class DeployAirflowStep(Step):
# Tell the user to check the schedule and fill in owner, email, other default_args
await sdk.run_step(MessageStep(message="Fill in the owner, email, and other default_args in the DAG file with your own personal information. Then the DAG will be ready to run!", name="Fill in default_args"))
-
- # Run the DAG locally ??
diff --git a/continuedev/src/continuedev/recipes/README.md b/continuedev/src/continuedev/plugins/recipes/README.md
index d5a006fb..9860b0e2 100644
--- a/continuedev/src/continuedev/recipes/README.md
+++ b/continuedev/src/continuedev/plugins/recipes/README.md
@@ -1,5 +1,7 @@
# This is a collaborative collection of Continue recipes
+A recipe is technically just a [Step](../steps/README.md), but is intended to be more complex, composed of multiple sub-steps.
+
Recipes here will automatically be made available in the [Continue VS Code extension](https://marketplace.visualstudio.com/items?itemName=Continue.continue).
The `recipes` folder contains all recipes, each with the same structure. **If you wish to create your own recipe, please do the following:**
diff --git a/continuedev/src/continuedev/recipes/TemplateRecipe/README.md b/continuedev/src/continuedev/plugins/recipes/TemplateRecipe/README.md
index 91d1123b..91d1123b 100644
--- a/continuedev/src/continuedev/recipes/TemplateRecipe/README.md
+++ b/continuedev/src/continuedev/plugins/recipes/TemplateRecipe/README.md
diff --git a/continuedev/src/continuedev/recipes/TemplateRecipe/main.py b/continuedev/src/continuedev/plugins/recipes/TemplateRecipe/main.py
index 16132cfd..197abe85 100644
--- a/continuedev/src/continuedev/recipes/TemplateRecipe/main.py
+++ b/continuedev/src/continuedev/plugins/recipes/TemplateRecipe/main.py
@@ -1,5 +1,7 @@
from typing import Coroutine
-from continuedev.core import Step, ContinueSDK, Observation, Models
+from ....core.main import Step, Observation
+from ....core.sdk import ContinueSDK
+from ....core.sdk import Models
class TemplateRecipe(Step):
diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/README.md b/continuedev/src/continuedev/plugins/recipes/WritePytestsRecipe/README.md
index 5ce33ecb..5ce33ecb 100644
--- a/continuedev/src/continuedev/recipes/WritePytestsRecipe/README.md
+++ b/continuedev/src/continuedev/plugins/recipes/WritePytestsRecipe/README.md
diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py b/continuedev/src/continuedev/plugins/recipes/WritePytestsRecipe/main.py
index c7a65fa6..6ef5ffd6 100644
--- a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py
+++ b/continuedev/src/continuedev/plugins/recipes/WritePytestsRecipe/main.py
@@ -1,7 +1,8 @@
from textwrap import dedent
from typing import Union
-from ...models.filesystem_edit import AddDirectory, AddFile
-from ...core.main import Step, ContinueSDK
+from ....models.filesystem_edit import AddDirectory, AddFile
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
import os
diff --git a/continuedev/src/continuedev/plugins/steps/README.md b/continuedev/src/continuedev/plugins/steps/README.md
new file mode 100644
index 00000000..12073835
--- /dev/null
+++ b/continuedev/src/continuedev/plugins/steps/README.md
@@ -0,0 +1,50 @@
+# Steps
+
+Steps are the composable unit of action in Continue. They define a `run` method which has access to the entire `ContinueSDK`, allowing you to take actions inside the IDE, call language models, and more. In this folder you can find a number of good examples.
+
+## How to write a step
+
+a. Start by creating a subclass of `Step`
+
+You should first consider what will be the parameters of your recipe. These are defined as attributes in the Pydantic class. For example, if you wanted a "filepath" attribute that would look like this:
+
+```python
+class HelloWorldStep(Step):
+ filepath: str
+ ...
+```
+
+b. Next, write the `run` method
+
+This method takes the ContinueSDK as a parameter, giving you all the tools you need to write your steps (if it's missing something, let us know, we'll add it!). You can write any code inside the run method; this is what will happen when your step is run, line for line. As an example, here's a step that will open a file and append "Hello World!":
+
+```python
+class HelloWorldStep(Step):
+ filepath: str
+
+ async def run(self, sdk: ContinueSDK):
+ await sdk.ide.setFileOpen(self.filepath)
+ await sdk.append_to_file(self.filepath, "Hello World!")
+```
+
+c. Finally, every Step is displayed with a description of what it has done
+
+If you'd like to override the default description of your step, which is just the class name, then implement the `describe` method. You can:
+
+- Return a static string
+- Store state in a class attribute (prepend with a double underscore, which signifies (through Pydantic) that this is not a parameter for the Step, just internal state) during the run method, and then grab this in the describe method.
+- Use state in conjunction with the `models` parameter of the describe method to autogenerate a description with a language model. For example, if you'd used an attribute called `__code_written` to store a string representing some code that was written, you could implement describe as `return models.gpt35.complete(f"{self.\_\_code_written}\n\nSummarize the changes made in the above code.")`.
+
+Here's an example:
+
+```python
+class HelloWorldStep(Step):
+ filepath: str
+
+ async def run(self, sdk: ContinueSDK):
+ await sdk.ide.setFileOpen(self.filepath)
+ await sdk.append_to_file(self.filepath, "Hello World!")
+
+ def describe(self, models: Models):
+ return f"Appended 'Hello World!' to {self.filepath}"
+```
diff --git a/continuedev/src/continuedev/steps/__init__.py b/continuedev/src/continuedev/plugins/steps/__init__.py
index 8b137891..8b137891 100644
--- a/continuedev/src/continuedev/steps/__init__.py
+++ b/continuedev/src/continuedev/plugins/steps/__init__.py
diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/plugins/steps/chat.py
index 14a1cd41..2c662459 100644
--- a/continuedev/src/continuedev/steps/chat.py
+++ b/continuedev/src/continuedev/plugins/steps/chat.py
@@ -3,11 +3,12 @@ from typing import Any, Coroutine, List
from pydantic import Field
+from ...libs.util.strings import remove_quotes_and_escapes
from .main import EditHighlightedCodeStep
from .core.core import MessageStep
-from ..core.main import FunctionCall, Models
-from ..core.main import ChatMessage, Step, step_to_json_schema
-from ..core.sdk import ContinueSDK
+from ...core.main import FunctionCall, Models
+from ...core.main import ChatMessage, Step, step_to_json_schema
+from ...core.sdk import ContinueSDK
import openai
import os
from dotenv import load_dotenv
@@ -28,32 +29,31 @@ class SimpleChatStep(Step):
completion = ""
messages = self.messages or await sdk.get_chat_context()
- generator = sdk.models.gpt4.stream_chat(messages, temperature=0.5)
+ generator = sdk.models.default.stream_chat(
+ messages, temperature=sdk.config.temperature)
try:
async for chunk in generator:
if sdk.current_step_was_deleted():
# So that the message doesn't disappear
self.hide = False
- return
+ break
if "content" in chunk:
self.description += chunk["content"]
completion += chunk["content"]
await sdk.update_ui()
finally:
- await generator.aclose()
-
- self.name = (await sdk.models.gpt35.complete(
- f"Write a short title for the following chat message: {self.description}")).strip()
+ self.name = remove_quotes_and_escapes(await sdk.models.gpt35.complete(
+ f"Write a short title for the following chat message: {self.description}"))
- if self.name.startswith('"') and self.name.endswith('"'):
- self.name = self.name[1:-1]
+ self.chat_context.append(ChatMessage(
+ role="assistant",
+ content=completion,
+ summary=self.name
+ ))
- self.chat_context.append(ChatMessage(
- role="assistant",
- content=completion,
- summary=self.name
- ))
+ # TODO: Never actually closing.
+ await generator.aclose()
class AddFileStep(Step):
diff --git a/continuedev/src/continuedev/steps/chroma.py b/continuedev/src/continuedev/plugins/steps/chroma.py
index 9d085981..dbe8363e 100644
--- a/continuedev/src/continuedev/steps/chroma.py
+++ b/continuedev/src/continuedev/plugins/steps/chroma.py
@@ -1,10 +1,10 @@
from textwrap import dedent
from typing import Coroutine, Union
-from ..core.observation import Observation, TextObservation
-from ..core.main import Step
-from ..core.sdk import ContinueSDK
+from ...core.observation import Observation, TextObservation
+from ...core.main import Step
+from ...core.sdk import ContinueSDK
from .core.core import EditFileStep
-from ..libs.chroma.query import ChromaIndexManager
+from ...libs.chroma.query import ChromaIndexManager
from .core.core import EditFileStep
diff --git a/continuedev/src/continuedev/steps/clear_history.py b/continuedev/src/continuedev/plugins/steps/clear_history.py
index a875c6d3..8f21518b 100644
--- a/continuedev/src/continuedev/steps/clear_history.py
+++ b/continuedev/src/continuedev/plugins/steps/clear_history.py
@@ -1,5 +1,5 @@
-from ..core.main import Step
-from ..core.sdk import ContinueSDK
+from ...core.main import Step
+from ...core.sdk import ContinueSDK
class ClearHistoryStep(Step):
diff --git a/continuedev/src/continuedev/steps/comment_code.py b/continuedev/src/continuedev/plugins/steps/comment_code.py
index aa17e62c..3e34ab52 100644
--- a/continuedev/src/continuedev/steps/comment_code.py
+++ b/continuedev/src/continuedev/plugins/steps/comment_code.py
@@ -1,4 +1,4 @@
-from ..core.main import ContinueSDK, Models, Step
+from ...core.main import ContinueSDK, Models, Step
from .main import EditHighlightedCodeStep
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/plugins/steps/core/core.py
index 5ea95104..5a81e5ee 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/plugins/steps/core/core.py
@@ -1,17 +1,19 @@
# These steps are depended upon by ContinueSDK
import os
import subprocess
+import difflib
from textwrap import dedent
from typing import Coroutine, List, Literal, Union
-from ...models.main import Range
-from ...libs.llm.prompt_utils import MarkdownStyleEncoderDecoder
-from ...models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit
-from ...models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents
-from ...core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation
-from ...core.main import ChatMessage, Step, SequentialStep
-from ...libs.util.count_tokens import MAX_TOKENS_FOR_MODEL, DEFAULT_MAX_TOKENS
-from ...libs.util.dedent import dedent_and_get_common_whitespace
+from ....libs.llm.ggml import GGML
+from ....models.main import Range
+from ....libs.llm.prompt_utils import MarkdownStyleEncoderDecoder
+from ....models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit
+from ....models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents
+from ....core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation
+from ....core.main import ChatMessage, ContinueCustomException, Step, SequentialStep
+from ....libs.util.count_tokens import MAX_TOKENS_FOR_MODEL, DEFAULT_MAX_TOKENS
+from ....libs.util.strings import dedent_and_get_common_whitespace, remove_quotes_and_escapes
import difflib
@@ -156,42 +158,32 @@ class DefaultModelEditCodeStep(Step):
_new_contents: str = ""
_prompt_and_completion: str = ""
- def _cleanup_output(self, output: str) -> str:
- output = output.replace('\\"', '"')
- output = output.replace("\\'", "'")
- output = output.replace("\\n", "\n")
- output = output.replace("\\t", "\t")
- output = output.replace("\\\\", "\\")
- if output.startswith('"') and output.endswith('"'):
- output = output[1:-1]
-
- return output
-
async def describe(self, models: Models) -> Coroutine[str, None, None]:
if self._previous_contents.strip() == self._new_contents.strip():
description = "No edits were made"
else:
+ changes = '\n'.join(difflib.ndiff(
+ self._previous_contents.splitlines(), self._new_contents.splitlines()))
description = await models.gpt3516k.complete(dedent(f"""\
- ```original
- {self._previous_contents}
- ```
+ Diff summary: "{self.user_input}"
- ```new
- {self._new_contents}
+ ```diff
+ {changes}
```
Please give brief a description of the changes made above using markdown bullet points. Be concise:"""))
name = await models.gpt3516k.complete(f"Write a very short title to describe this requested change (no quotes): '{self.user_input}'. This is the title:")
- self.name = self._cleanup_output(name)
+ self.name = remove_quotes_and_escapes(name)
- return f"{self._cleanup_output(description)}"
+ return f"{remove_quotes_and_escapes(description)}"
async def get_prompt_parts(self, rif: RangeInFileWithContents, sdk: ContinueSDK, full_file_contents: str):
# We don't know here all of the functions being passed in.
# We care because if this prompt itself goes over the limit, then the entire message will have to be cut from the completion.
# Overflow won't happen, but prune_chat_messages in count_tokens.py will cut out this whole thing, instead of us cutting out only as many lines as we need.
- model_to_use = sdk.models.gpt4
- max_tokens = DEFAULT_MAX_TOKENS
+ model_to_use = sdk.models.default
+ max_tokens = int(MAX_TOKENS_FOR_MODEL.get(
+ model_to_use.name, DEFAULT_MAX_TOKENS) / 2)
TOKENS_TO_BE_CONSIDERED_LARGE_RANGE = 1200
if model_to_use.count_tokens(rif.contents) > TOKENS_TO_BE_CONSIDERED_LARGE_RANGE:
@@ -228,13 +220,13 @@ class DefaultModelEditCodeStep(Step):
if total_tokens < MAX_TOKENS_FOR_MODEL[model_to_use.name]:
break
- if total_tokens > MAX_TOKENS_FOR_MODEL[model_to_use.name]:
- while cur_start_line < max_start_line:
- cur_start_line += 1
- total_tokens -= model_to_use.count_tokens(
- full_file_contents_lst[cur_end_line])
- if total_tokens < MAX_TOKENS_FOR_MODEL[model_to_use.name]:
- break
+ if total_tokens > MAX_TOKENS_FOR_MODEL[model_to_use.name]:
+ while cur_start_line < max_start_line:
+ cur_start_line += 1
+ total_tokens -= model_to_use.count_tokens(
+ full_file_contents_lst[cur_start_line])
+ if total_tokens < MAX_TOKENS_FOR_MODEL[model_to_use.name]:
+ break
# Now use the found start/end lines to get the prefix and suffix strings
file_prefix = "\n".join(
@@ -474,6 +466,14 @@ Please output the code to be inserted at the cursor in order to fulfill the user
current_block_lines.append(line)
messages = await sdk.get_chat_context()
+ # Delete the last user and assistant messages
+ i = len(messages) - 1
+ deleted = 0
+ while i >= 0 and deleted < 2:
+ if messages[i].role == "user" or messages[i].role == "assistant":
+ messages.pop(i)
+ deleted += 1
+ i -= 1
messages.append(ChatMessage(
role="user",
content=prompt,
@@ -486,58 +486,68 @@ Please output the code to be inserted at the cursor in order to fulfill the user
completion_lines_covered = 0
repeating_file_suffix = False
line_below_highlighted_range = file_suffix.lstrip().split("\n")[0]
- async for chunk in model_to_use.stream_chat(messages, temperature=0, max_tokens=max_tokens):
- # Stop early if it is repeating the file_suffix or the step was deleted
- if repeating_file_suffix:
- break
- if sdk.current_step_was_deleted():
- return
- # Accumulate lines
- if "content" not in chunk:
- continue
- chunk = chunk["content"]
- chunk_lines = chunk.split("\n")
- chunk_lines[0] = unfinished_line + chunk_lines[0]
- if chunk.endswith("\n"):
- unfinished_line = ""
- chunk_lines.pop() # because this will be an empty string
- else:
- unfinished_line = chunk_lines.pop()
-
- # Deal with newly accumulated lines
- for i in range(len(chunk_lines)):
- # Trailing whitespace doesn't matter
- chunk_lines[i] = chunk_lines[i].rstrip()
- chunk_lines[i] = common_whitespace + chunk_lines[i]
-
- # Lines that should signify the end of generation
- if self.is_end_line(chunk_lines[i]):
- break
- # Lines that should be ignored, like the <> tags
- elif self.line_to_be_ignored(chunk_lines[i], completion_lines_covered == 0):
- continue
- # Check if we are currently just copying the prefix
- elif (lines_of_prefix_copied > 0 or completion_lines_covered == 0) and lines_of_prefix_copied < len(file_prefix.splitlines()) and chunk_lines[i] == full_file_contents_lines[lines_of_prefix_copied]:
- # This is a sketchy way of stopping it from repeating the file_prefix. Is a bug if output happens to have a matching line
- lines_of_prefix_copied += 1
- continue
- # Because really short lines might be expected to be repeated, this is only a !heuristic!
- # Stop when it starts copying the file_suffix
- elif chunk_lines[i].strip() == line_below_highlighted_range.strip() and len(chunk_lines[i].strip()) > 4 and not (len(original_lines_below_previous_blocks) > 0 and chunk_lines[i].strip() == original_lines_below_previous_blocks[0].strip()):
- repeating_file_suffix = True
+ if isinstance(model_to_use, GGML):
+ messages = [ChatMessage(
+ role="user", content=f"```\n{rif.contents}\n```\n\nUser request: \"{self.user_input}\"\n\nThis is the code after changing to perfectly comply with the user request. It does not include any placeholder code, only real implementations:\n\n```\n", summary=self.user_input)]
+
+ generator = model_to_use.stream_chat(
+ messages, temperature=sdk.config.temperature, max_tokens=max_tokens)
+
+ try:
+ async for chunk in generator:
+ # Stop early if it is repeating the file_suffix or the step was deleted
+ if repeating_file_suffix:
break
+ if sdk.current_step_was_deleted():
+ return
- # If none of the above, insert the line!
- if False:
- await handle_generated_line(chunk_lines[i])
+ # Accumulate lines
+ if "content" not in chunk:
+ continue
+ chunk = chunk["content"]
+ chunk_lines = chunk.split("\n")
+ chunk_lines[0] = unfinished_line + chunk_lines[0]
+ if chunk.endswith("\n"):
+ unfinished_line = ""
+ chunk_lines.pop() # because this will be an empty string
+ else:
+ unfinished_line = chunk_lines.pop()
+
+ # Deal with newly accumulated lines
+ for i in range(len(chunk_lines)):
+ # Trailing whitespace doesn't matter
+ chunk_lines[i] = chunk_lines[i].rstrip()
+ chunk_lines[i] = common_whitespace + chunk_lines[i]
+
+ # Lines that should signify the end of generation
+ if self.is_end_line(chunk_lines[i]):
+ break
+ # Lines that should be ignored, like the <> tags
+ elif self.line_to_be_ignored(chunk_lines[i], completion_lines_covered == 0):
+ continue
+ # Check if we are currently just copying the prefix
+ elif (lines_of_prefix_copied > 0 or completion_lines_covered == 0) and lines_of_prefix_copied < len(file_prefix.splitlines()) and chunk_lines[i] == full_file_contents_lines[lines_of_prefix_copied]:
+ # This is a sketchy way of stopping it from repeating the file_prefix. Is a bug if output happens to have a matching line
+ lines_of_prefix_copied += 1
+ continue
+ # Because really short lines might be expected to be repeated, this is only a !heuristic!
+ # Stop when it starts copying the file_suffix
+ elif chunk_lines[i].strip() == line_below_highlighted_range.strip() and len(chunk_lines[i].strip()) > 4 and not (len(original_lines_below_previous_blocks) > 0 and chunk_lines[i].strip() == original_lines_below_previous_blocks[0].strip()):
+ repeating_file_suffix = True
+ break
- lines.append(chunk_lines[i])
- completion_lines_covered += 1
- current_line_in_file += 1
+ # If none of the above, insert the line!
+ if False:
+ await handle_generated_line(chunk_lines[i])
- await sendDiffUpdate(lines + [common_whitespace if unfinished_line.startswith("<") else (common_whitespace + unfinished_line)], sdk)
+ lines.append(chunk_lines[i])
+ completion_lines_covered += 1
+ current_line_in_file += 1
+ await sendDiffUpdate(lines + [common_whitespace if unfinished_line.startswith("<") else (common_whitespace + unfinished_line)], sdk)
+ finally:
+ await generator.aclose()
# Add the unfinished line
if unfinished_line != "" and not self.line_to_be_ignored(unfinished_line, completion_lines_covered == 0) and not self.is_end_line(unfinished_line):
unfinished_line = common_whitespace + unfinished_line
@@ -602,6 +612,13 @@ Please output the code to be inserted at the cursor in order to fulfill the user
rif_dict[rif.filepath] = rif.contents
for rif in rif_with_contents:
+ # If the file doesn't exist, ask them to save it first
+ if not os.path.exists(rif.filepath):
+ message = f"The file {rif.filepath} does not exist. Please save it first."
+ raise ContinueCustomException(
+ title=message, message=message
+ )
+
await sdk.ide.setFileOpen(rif.filepath)
await sdk.ide.setSuggestionsLocked(rif.filepath, True)
await self.stream_rif(rif, sdk)
diff --git a/continuedev/src/continuedev/steps/custom_command.py b/continuedev/src/continuedev/plugins/steps/custom_command.py
index 5a56efb0..d5b6e48b 100644
--- a/continuedev/src/continuedev/steps/custom_command.py
+++ b/continuedev/src/continuedev/plugins/steps/custom_command.py
@@ -1,7 +1,7 @@
-from ..core.main import Step
-from ..core.sdk import ContinueSDK
-from ..steps.core.core import UserInputStep
-from ..steps.chat import ChatWithFunctions, SimpleChatStep
+from ...libs.util.templating import render_templated_string
+from ...core.main import Step
+from ...core.sdk import ContinueSDK
+from ..steps.chat import SimpleChatStep
class CustomCommandStep(Step):
@@ -15,7 +15,9 @@ class CustomCommandStep(Step):
return self.prompt
async def run(self, sdk: ContinueSDK):
- prompt_user_input = f"Task: {self.prompt}. Additional info: {self.user_input}"
+ task = render_templated_string(self.prompt)
+
+ prompt_user_input = f"Task: {task}. Additional info: {self.user_input}"
messages = await sdk.get_chat_context()
# Find the last chat message with this slash command and replace it with the user input
for i in range(len(messages) - 1, -1, -1):
diff --git a/continuedev/src/continuedev/steps/draft/abstract_method.py b/continuedev/src/continuedev/plugins/steps/draft/abstract_method.py
index f3131c4b..f3131c4b 100644
--- a/continuedev/src/continuedev/steps/draft/abstract_method.py
+++ b/continuedev/src/continuedev/plugins/steps/draft/abstract_method.py
diff --git a/continuedev/src/continuedev/steps/draft/migration.py b/continuedev/src/continuedev/plugins/steps/draft/migration.py
index f3b36b5e..a76d491b 100644
--- a/continuedev/src/continuedev/steps/draft/migration.py
+++ b/continuedev/src/continuedev/plugins/steps/draft/migration.py
@@ -1,7 +1,7 @@
# When an edit is made to an existing class or a new sqlalchemy class is created,
# this should be kicked off.
-from ...core.main import Step
+from ....core.main import Step
class MigrationStep(Step):
diff --git a/continuedev/src/continuedev/steps/draft/redux.py b/continuedev/src/continuedev/plugins/steps/draft/redux.py
index 17506316..30c8fdbb 100644
--- a/continuedev/src/continuedev/steps/draft/redux.py
+++ b/continuedev/src/continuedev/plugins/steps/draft/redux.py
@@ -1,5 +1,5 @@
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
from ..core.core import EditFileStep
@@ -25,14 +25,14 @@ class EditReduxStateStep(Step):
sdk.run_step(EditFileStep(
filepath=selector_filename,
prompt=f"Edit the selector to add a new property for {self.description}. The store looks like this: {store_file_contents}"
- )
+ ))
# Reducer
reducer_filename = ""
sdk.run_step(EditFileStep(
filepath=reducer_filename,
prompt=f"Edit the reducer to add a new property for {self.description}. The store looks like this: {store_file_contents}"
-
+ ))
"""
Starts with implementing selector
1. RootStore
diff --git a/continuedev/src/continuedev/steps/draft/typeorm.py b/continuedev/src/continuedev/plugins/steps/draft/typeorm.py
index 153c855f..d06a6fb4 100644
--- a/continuedev/src/continuedev/steps/draft/typeorm.py
+++ b/continuedev/src/continuedev/plugins/steps/draft/typeorm.py
@@ -1,6 +1,6 @@
from textwrap import dedent
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
class CreateTableStep(Step):
diff --git a/continuedev/src/continuedev/steps/feedback.py b/continuedev/src/continuedev/plugins/steps/feedback.py
index 6f6a9b15..119e3112 100644
--- a/continuedev/src/continuedev/steps/feedback.py
+++ b/continuedev/src/continuedev/plugins/steps/feedback.py
@@ -1,8 +1,8 @@
from typing import Coroutine
-from ..core.main import Models
-from ..core.main import Step
-from ..core.sdk import ContinueSDK
-from ..libs.util.telemetry import capture_event
+from ...core.main import Models
+from ...core.main import Step
+from ...core.sdk import ContinueSDK
+from ...libs.util.telemetry import capture_event
class FeedbackStep(Step):
diff --git a/continuedev/src/continuedev/steps/find_and_replace.py b/continuedev/src/continuedev/plugins/steps/find_and_replace.py
index 690872c0..a2c9c44e 100644
--- a/continuedev/src/continuedev/steps/find_and_replace.py
+++ b/continuedev/src/continuedev/plugins/steps/find_and_replace.py
@@ -1,6 +1,6 @@
-from ..models.filesystem_edit import FileEdit, Range
-from ..core.main import Models, Step
-from ..core.sdk import ContinueSDK
+from ...models.filesystem_edit import FileEdit, Range
+from ...core.main import Models, Step
+from ...core.sdk import ContinueSDK
class FindAndReplaceStep(Step):
diff --git a/continuedev/src/continuedev/plugins/steps/help.py b/continuedev/src/continuedev/plugins/steps/help.py
new file mode 100644
index 00000000..5111c7cf
--- /dev/null
+++ b/continuedev/src/continuedev/plugins/steps/help.py
@@ -0,0 +1,59 @@
+from textwrap import dedent
+from ...core.main import ChatMessage, Step
+from ...core.sdk import ContinueSDK
+from ...libs.util.telemetry import capture_event
+
+help = dedent("""\
+ Continue is an open-source coding autopilot. It is a VS Code extension that brings the power of ChatGPT to your IDE.
+
+ It gathers context for you and stores your interactions automatically, so that you can avoid copy/paste now and benefit from a customized Large Language Model (LLM) later.
+
+ Continue can be used to...
+ 1. Edit chunks of code with specific instructions (e.g. "/edit migrate this digital ocean terraform file into one that works for GCP")
+ 2. Get answers to questions without switching windows (e.g. "how do I find running process on port 8000?")
+ 3. Generate files from scratch (e.g. "/edit Create a Python CLI tool that uses the posthog api to get events from DAUs")
+
+ You tell Continue to edit a specific section of code by highlighting it. If you highlight multiple code sections, then it will only edit the one with the purple glow around it. You can switch which one has the purple glow by clicking the paint brush.
+
+ If you don't highlight any code, then Continue will insert at the location of your cursor.
+
+ Continue passes all of the sections of code you highlight, the code above and below the to-be edited highlighted code section, and all previous steps above input box as context to the LLM.
+
+ You can use cmd+m (Mac) / ctrl+m (Windows) to open Continue. You can use cmd+shift+e / ctrl+shift+e to open file Explorer. You can add your own OpenAI API key to VS Code Settings with `cmd+,`
+
+ If Continue is stuck loading, try using `cmd+shift+p` to open the command palette, search "Reload Window", and then select it. This will reload VS Code and Continue and often fixes issues.
+
+ If you have feedback, please use /feedback to let us know how you would like to use Continue. We are excited to hear from you!""")
+
+
+class HelpStep(Step):
+
+ name: str = "Help"
+ user_input: str
+ manage_own_chat_context: bool = True
+ description: str = ""
+
+ async def run(self, sdk: ContinueSDK):
+
+ question = self.user_input
+
+ prompt = dedent(f"""Please us the information below to provide a succinct answer to the following quesiton: {question}
+
+ Information:
+
+ {help}""")
+
+ self.chat_context.append(ChatMessage(
+ role="user",
+ content=prompt,
+ summary="Help"
+ ))
+ messages = await sdk.get_chat_context()
+ generator = sdk.models.gpt4.stream_chat(messages)
+ async for chunk in generator:
+ if "content" in chunk:
+ self.description += chunk["content"]
+ await sdk.update_ui()
+
+ capture_event(sdk.ide.unique_id, "help", {
+ "question": question, "answer": self.description})
diff --git a/continuedev/src/continuedev/steps/input/nl_multiselect.py b/continuedev/src/continuedev/plugins/steps/input/nl_multiselect.py
index aee22866..b54d394a 100644
--- a/continuedev/src/continuedev/steps/input/nl_multiselect.py
+++ b/continuedev/src/continuedev/plugins/steps/input/nl_multiselect.py
@@ -1,7 +1,7 @@
from typing import List, Union
from ..core.core import WaitForUserInputStep
-from ...core.main import Step
-from ...core.sdk import ContinueSDK
+from ....core.main import Step
+from ....core.sdk import ContinueSDK
class NLMultiselectStep(Step):
diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/plugins/steps/main.py
index ce7cbc60..30117c55 100644
--- a/continuedev/src/continuedev/steps/main.py
+++ b/continuedev/src/continuedev/plugins/steps/main.py
@@ -1,21 +1,18 @@
import os
from typing import Coroutine, List, Union
-
+from textwrap import dedent
from pydantic import BaseModel, Field
-from ..libs.llm import LLM
-from ..models.main import Traceback, Range
-from ..models.filesystem_edit import EditDiff, FileEdit
-from ..models.filesystem import RangeInFile, RangeInFileWithContents
-from ..core.observation import Observation, TextObservation, TracebackObservation
-from ..libs.llm.prompt_utils import MarkdownStyleEncoderDecoder
-from textwrap import dedent
-from ..core.main import ContinueCustomException, Step
-from ..core.sdk import ContinueSDK, Models
-from ..core.observation import Observation
-import subprocess
+from ...models.main import Traceback, Range
+from ...models.filesystem_edit import EditDiff, FileEdit
+from ...models.filesystem import RangeInFile, RangeInFileWithContents
+from ...core.observation import Observation
+from ...libs.llm.prompt_utils import MarkdownStyleEncoderDecoder
+from ...core.main import ContinueCustomException, Step
+from ...core.sdk import ContinueSDK, Models
+from ...core.observation import Observation
from .core.core import DefaultModelEditCodeStep
-from ..libs.util.calculate_diff import calculate_diff2
+from ...libs.util.calculate_diff import calculate_diff2
class SetupContinueWorkspaceStep(Step):
@@ -303,8 +300,7 @@ class SolveTracebackStep(Step):
range_in_files.append(
RangeInFile.from_entire_file(frame.filepath, content))
- await sdk.run_step(EditCodeStep(
- range_in_files=range_in_files, prompt=prompt))
+ await sdk.run_step(DefaultModelEditCodeStep(range_in_files=range_in_files, user_input=prompt))
return None
diff --git a/continuedev/src/continuedev/steps/on_traceback.py b/continuedev/src/continuedev/plugins/steps/on_traceback.py
index efb4c703..e99f212d 100644
--- a/continuedev/src/continuedev/steps/on_traceback.py
+++ b/continuedev/src/continuedev/plugins/steps/on_traceback.py
@@ -1,8 +1,8 @@
import os
from .core.core import UserInputStep
-from ..core.main import ChatMessage, Step
-from ..core.sdk import ContinueSDK
+from ...core.main import ChatMessage, Step
+from ...core.sdk import ContinueSDK
from .chat import SimpleChatStep
diff --git a/continuedev/src/continuedev/steps/open_config.py b/continuedev/src/continuedev/plugins/steps/open_config.py
index 87f03e9f..d950c26f 100644
--- a/continuedev/src/continuedev/steps/open_config.py
+++ b/continuedev/src/continuedev/plugins/steps/open_config.py
@@ -1,6 +1,6 @@
from textwrap import dedent
-from ..core.main import Step
-from ..core.sdk import ContinueSDK
+from ...core.main import Step
+from ...core.sdk import ContinueSDK
import os
@@ -14,10 +14,10 @@ class OpenConfigStep(Step):
"custom_commands": [
{
"name": "test",
- "description": "Write unit tests like I do for the highlighted code"
+ "description": "Write unit tests like I do for the highlighted code",
"prompt": "Write a comprehensive set of unit tests for the selected code. It should setup, run tests that check for correctness including important edge cases, and teardown. Ensure that the tests are complete and sophisticated."
}
- ],
+ ]
```
`"name"` is the command you will type.
`"description"` is the description displayed in the slash command menu.
diff --git a/continuedev/src/continuedev/steps/react.py b/continuedev/src/continuedev/plugins/steps/react.py
index cddb8b42..8b2e7c2e 100644
--- a/continuedev/src/continuedev/steps/react.py
+++ b/continuedev/src/continuedev/plugins/steps/react.py
@@ -1,8 +1,7 @@
from textwrap import dedent
from typing import List, Union, Tuple
-from ..core.main import Step
-from ..core.sdk import ContinueSDK
-from .core.core import MessageStep
+from ...core.main import Step
+from ...core.sdk import ContinueSDK
class NLDecisionStep(Step):
diff --git a/continuedev/src/continuedev/steps/search_directory.py b/continuedev/src/continuedev/plugins/steps/search_directory.py
index bfb97630..7d02d6fa 100644
--- a/continuedev/src/continuedev/steps/search_directory.py
+++ b/continuedev/src/continuedev/plugins/steps/search_directory.py
@@ -2,11 +2,11 @@ import asyncio
from textwrap import dedent
from typing import List, Union
-from ..models.filesystem import RangeInFile
-from ..models.main import Range
-from ..core.main import Step
-from ..core.sdk import ContinueSDK
-from ..libs.util.create_async_task import create_async_task
+from ...models.filesystem import RangeInFile
+from ...models.main import Range
+from ...core.main import Step
+from ...core.sdk import ContinueSDK
+from ...libs.util.create_async_task import create_async_task
import os
import re
diff --git a/continuedev/src/continuedev/plugins/steps/steps_on_startup.py b/continuedev/src/continuedev/plugins/steps/steps_on_startup.py
new file mode 100644
index 00000000..19d62d30
--- /dev/null
+++ b/continuedev/src/continuedev/plugins/steps/steps_on_startup.py
@@ -0,0 +1,17 @@
+from ...core.main import Step
+from ...core.sdk import Models, ContinueSDK
+from ...libs.util.step_name_to_steps import get_step_from_name
+
+
+class StepsOnStartupStep(Step):
+ hide: bool = True
+
+ async def describe(self, models: Models):
+ return "Running steps on startup"
+
+ async def run(self, sdk: ContinueSDK):
+ steps_on_startup = sdk.config.steps_on_startup
+
+ for step_name, step_params in steps_on_startup.items():
+ step = get_step_from_name(step_name, step_params)
+ await sdk.run_step(step)
diff --git a/continuedev/src/continuedev/steps/welcome.py b/continuedev/src/continuedev/plugins/steps/welcome.py
index 2dece649..df3e9a8a 100644
--- a/continuedev/src/continuedev/steps/welcome.py
+++ b/continuedev/src/continuedev/plugins/steps/welcome.py
@@ -1,9 +1,10 @@
from textwrap import dedent
-from ..models.filesystem_edit import AddFile
-from ..core.main import Step
-from ..core.sdk import ContinueSDK, Models
import os
+from ...models.filesystem_edit import AddFile
+from ...core.main import Step
+from ...core.sdk import ContinueSDK, Models
+
class WelcomeStep(Step):
name: str = "Welcome to Continue!"
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index 238273b2..ae57c0b6 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -1,3 +1,4 @@
+import asyncio
import json
from fastapi import Depends, Header, WebSocket, APIRouter
from starlette.websockets import WebSocketState, WebSocketDisconnect
@@ -53,15 +54,19 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
self.session = session
async def _send_json(self, message_type: str, data: Any):
- if self.websocket.client_state == WebSocketState.DISCONNECTED:
+ if self.websocket.application_state == WebSocketState.DISCONNECTED:
return
await self.websocket.send_json({
"messageType": message_type,
"data": data
})
- async def _receive_json(self, message_type: str) -> Any:
- return await self.sub_queue.get(message_type)
+ async def _receive_json(self, message_type: str, timeout: int = 5) -> Any:
+ try:
+ return await asyncio.wait_for(self.sub_queue.get(message_type), timeout=timeout)
+ except asyncio.TimeoutError:
+ raise Exception(
+ "GUI Protocol _receive_json timed out after 5 seconds")
async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T:
await self._send_json(message_type, data)
@@ -94,6 +99,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
self.on_set_editing_at_indices(data["indices"])
elif message_type == "set_pinned_at_indices":
self.on_set_pinned_at_indices(data["indices"])
+ elif message_type == "show_logs_at_index":
+ self.on_show_logs_at_index(data["index"])
except Exception as e:
print(e)
@@ -161,6 +168,13 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
indices), self.session.autopilot.continue_sdk.ide.unique_id
)
+ def on_show_logs_at_index(self, index: int):
+ name = f"continue_logs.txt"
+ logs = "\n\n############################################\n\n".join(
+ ["This is a log of the exact prompt/completion pairs sent/received from the LLM during this step"] + self.session.autopilot.continue_sdk.history.timeline[index].logs)
+ create_async_task(
+ self.session.autopilot.ide.showVirtualFile(name, logs))
+
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)):
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index 12a21f19..aeff5623 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -123,10 +123,13 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
self.websocket = websocket
self.session_manager = session_manager
- workspace_directory: str
+ workspace_directory: str = None
+ unique_id: str = None
- async def initialize(self) -> List[str]:
+ async def initialize(self, session_id: str) -> List[str]:
+ self.session_id = session_id
await self._send_json("workspaceDirectory", {})
+ await self._send_json("uniqueId", {})
other_msgs = []
while True:
msg_string = await self.websocket.receive_text()
@@ -137,21 +140,29 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
data = message["data"]
if message_type == "workspaceDirectory":
self.workspace_directory = data["workspaceDirectory"]
- break
+ elif message_type == "uniqueId":
+ self.unique_id = data["uniqueId"]
else:
other_msgs.append(msg_string)
+
+ if self.workspace_directory is not None and self.unique_id is not None:
+ break
return other_msgs
async def _send_json(self, message_type: str, data: Any):
- if self.websocket.client_state == WebSocketState.DISCONNECTED:
+ if self.websocket.application_state == WebSocketState.DISCONNECTED:
return
await self.websocket.send_json({
"messageType": message_type,
"data": data
})
- async def _receive_json(self, message_type: str) -> Any:
- return await self.sub_queue.get(message_type)
+ async def _receive_json(self, message_type: str, timeout: int = 5) -> Any:
+ try:
+ return await asyncio.wait_for(self.sub_queue.get(message_type), timeout=timeout)
+ except asyncio.TimeoutError:
+ raise Exception(
+ "IDE Protocol _receive_json timed out after 5 seconds")
async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T:
await self._send_json(message_type, data)
@@ -183,10 +194,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
self.onMainUserInput(data["input"])
elif message_type == "deleteAtIndex":
self.onDeleteAtIndex(data["index"])
- elif message_type in ["highlightedCode", "openFiles", "visibleFiles", "readFile", "editFile", "getUserSecret", "runCommand", "uniqueId"]:
+ elif message_type in ["highlightedCode", "openFiles", "visibleFiles", "readFile", "editFile", "getUserSecret", "runCommand"]:
self.sub_queue.post(message_type, data)
elif message_type == "workspaceDirectory":
self.workspace_directory = data["workspaceDirectory"]
+ elif message_type == "uniqueId":
+ self.unique_id = data["uniqueId"]
else:
raise ValueError("Unknown message type", message_type)
@@ -211,6 +224,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
"open": open
})
+ async def showVirtualFile(self, name: str, contents: str):
+ await self._send_json("showVirtualFile", {
+ "name": name,
+ "contents": contents
+ })
+
async def setSuggestionsLocked(self, filepath: str, locked: bool = True):
# Lock suggestions in the file so they don't ruin the offset before others are inserted
await self._send_json("setSuggestionsLocked", {
@@ -219,8 +238,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
})
async def getSessionId(self):
- session_id = self.session_manager.new_session(
- self, self.session_id).session_id
+ session_id = (await self.session_manager.new_session(
+ self, self.session_id)).session_id
await self._send_json("getSessionId", {
"sessionId": session_id
})
@@ -274,33 +293,33 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
def onOpenGUIRequest(self):
pass
+ def __get_autopilot(self):
+ if self.session_id not in self.session_manager.sessions:
+ return None
+ return self.session_manager.sessions[self.session_id].autopilot
+
def onFileEdits(self, edits: List[FileEditWithFullContents]):
- # Send the file edits to ALL autopilots.
- # Maybe not ideal behavior
- for _, session in self.session_manager.sessions.items():
- session.autopilot.handle_manual_edits(edits)
+ if autopilot := self.__get_autopilot():
+ autopilot.handle_manual_edits(edits)
def onDeleteAtIndex(self, index: int):
- for _, session in self.session_manager.sessions.items():
- create_async_task(
- session.autopilot.delete_at_index(index), self.unique_id)
+ if autopilot := self.__get_autopilot():
+ create_async_task(autopilot.delete_at_index(index), self.unique_id)
def onCommandOutput(self, output: str):
- # Send the output to ALL autopilots.
- # Maybe not ideal behavior
- for _, session in self.session_manager.sessions.items():
+ if autopilot := self.__get_autopilot():
create_async_task(
- session.autopilot.handle_command_output(output), self.unique_id)
+ autopilot.handle_command_output(output), self.unique_id)
def onHighlightedCodeUpdate(self, range_in_files: List[RangeInFileWithContents]):
- for _, session in self.session_manager.sessions.items():
- create_async_task(
- session.autopilot.handle_highlighted_code(range_in_files), self.unique_id)
+ if autopilot := self.__get_autopilot():
+ create_async_task(autopilot.handle_highlighted_code(
+ range_in_files), self.unique_id)
def onMainUserInput(self, input: str):
- for _, session in self.session_manager.sessions.items():
+ if autopilot := self.__get_autopilot():
create_async_task(
- session.autopilot.accept_user_input(input), self.unique_id)
+ autopilot.accept_user_input(input), self.unique_id)
# Request information. Session doesn't matter.
async def getOpenFiles(self) -> List[str]:
@@ -311,14 +330,6 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
resp = await self._send_and_receive_json({}, VisibleFilesResponse, "visibleFiles")
return resp.visibleFiles
- async def get_unique_id(self) -> str:
- resp = await self._send_and_receive_json({}, UniqueIdResponse, "uniqueId")
- return resp.uniqueId
-
- @cached_property_no_none
- def unique_id(self) -> str:
- return asyncio.run(self.get_unique_id())
-
async def getHighlightedCode(self) -> List[RangeInFile]:
resp = await self._send_and_receive_json({}, HighlightedCodeResponse, "highlightedCode")
return resp.highlightedCode
@@ -436,10 +447,11 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None):
ideProtocolServer.handle_json(message_type, data))
ideProtocolServer = IdeProtocolServer(session_manager, websocket)
- ideProtocolServer.session_id = session_id
if session_id is not None:
session_manager.registered_ides[session_id] = ideProtocolServer
- other_msgs = await ideProtocolServer.initialize()
+ other_msgs = await ideProtocolServer.initialize(session_id)
+ capture_event(ideProtocolServer.unique_id, "session_started", {
+ "session_id": ideProtocolServer.session_id})
for other_msg in other_msgs:
handle_msg(other_msg)
@@ -460,4 +472,6 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None):
if websocket.client_state != WebSocketState.DISCONNECTED:
await websocket.close()
+ capture_event(ideProtocolServer.unique_id, "session_ended", {
+ "session_id": ideProtocolServer.session_id})
session_manager.registered_ides.pop(ideProtocolServer.session_id)
diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py
index 2f78cf0e..0ae7e7fa 100644
--- a/continuedev/src/continuedev/server/ide_protocol.py
+++ b/continuedev/src/continuedev/server/ide_protocol.py
@@ -24,6 +24,10 @@ class AbstractIdeProtocolServer(ABC):
"""Set whether a file is open"""
@abstractmethod
+ async def showVirtualFile(self, name: str, contents: str):
+ """Show a virtual file"""
+
+ @abstractmethod
async def setSuggestionsLocked(self, filepath: str, locked: bool = True):
"""Set whether suggestions are locked"""
@@ -108,7 +112,4 @@ class AbstractIdeProtocolServer(ABC):
"""Show a diff"""
workspace_directory: str
-
- @abstractproperty
- def unique_id(self) -> str:
- """Get a unique ID for this IDE"""
+ unique_id: str
diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py
index aa093853..42dc0cc1 100644
--- a/continuedev/src/continuedev/server/main.py
+++ b/continuedev/src/continuedev/server/main.py
@@ -1,5 +1,6 @@
+import time
+import psutil
import os
-import sys
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .ide import router as ide_router
@@ -51,9 +52,31 @@ def cleanup():
session_manager.persist_session(session_id)
+def cpu_usage_report():
+ process = psutil.Process(os.getpid())
+ # Call cpu_percent once to start measurement, but ignore the result
+ process.cpu_percent(interval=None)
+ # Wait for a short period of time
+ time.sleep(1)
+ # Call cpu_percent again to get the CPU usage over the interval
+ cpu_usage = process.cpu_percent(interval=None)
+ print(f"CPU usage: {cpu_usage}%")
+
+
atexit.register(cleanup)
+
if __name__ == "__main__":
try:
+ # import threading
+
+ # def cpu_usage_loop():
+ # while True:
+ # cpu_usage_report()
+ # time.sleep(2)
+
+ # cpu_thread = threading.Thread(target=cpu_usage_loop)
+ # cpu_thread.start()
+
run_server()
except Exception as e:
cleanup()
diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
index fb8ac386..20219273 100644
--- a/continuedev/src/continuedev/server/session_manager.py
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -7,7 +7,7 @@ import json
from ..libs.util.paths import getSessionFilePath, getSessionsFolderPath
from ..models.filesystem_edit import FileEditWithFullContents
from ..libs.constants.main import CONTINUE_SESSIONS_FOLDER
-from ..core.policy import DemoPolicy
+from ..core.policy import DefaultPolicy
from ..core.main import FullState
from ..core.autopilot import Autopilot
from .ide_protocol import AbstractIdeProtocolServer
@@ -53,19 +53,19 @@ class SessionManager:
session_files = os.listdir(sessions_folder)
if f"{session_id}.json" in session_files and session_id in self.registered_ides:
if self.registered_ides[session_id].session_id is not None:
- return self.new_session(self.registered_ides[session_id], session_id=session_id)
+ return await self.new_session(self.registered_ides[session_id], session_id=session_id)
raise KeyError("Session ID not recognized", session_id)
return self.sessions[session_id]
- def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session:
+ async def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session:
full_state = None
if session_id is not None and os.path.exists(getSessionFilePath(session_id)):
with open(getSessionFilePath(session_id), "r") as f:
full_state = FullState(**json.load(f))
- autopilot = DemoAutopilot(
- policy=DemoPolicy(), ide=ide, full_state=full_state)
+ autopilot = await DemoAutopilot.create(
+ policy=DefaultPolicy(), ide=ide, full_state=full_state)
session_id = session_id or str(uuid4())
ide.session_id = session_id
session = Session(session_id=session_id, autopilot=autopilot)
@@ -100,7 +100,7 @@ class SessionManager:
if session_id not in self.sessions:
raise SessionNotFound(f"Session {session_id} not found")
if self.sessions[session_id].ws is None:
- print(f"Session {session_id} has no websocket")
+ # print(f"Session {session_id} has no websocket")
return
await self.sessions[session_id].ws.send_json({
diff --git a/continuedev/src/continuedev/steps/steps_on_startup.py b/continuedev/src/continuedev/steps/steps_on_startup.py
deleted file mode 100644
index 365cbe1a..00000000
--- a/continuedev/src/continuedev/steps/steps_on_startup.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from ..core.main import Step
-from ..core.sdk import Models, ContinueSDK
-from .main import UserInputStep
-from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
-from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe
-from ..recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe
-from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe
-from ..recipes.AddTransformRecipe.main import AddTransformRecipe
-from ..libs.util.step_name_to_steps import get_step_from_name
-
-
-class StepsOnStartupStep(Step):
- hide: bool = True
-
- async def describe(self, models: Models):
- return "Running steps on startup"
-
- async def run(self, sdk: ContinueSDK):
- steps_on_startup = sdk.config.steps_on_startup
-
- for step_name, step_params in steps_on_startup.items():
- step = get_step_from_name(step_name, step_params)
- await sdk.run_step(step)
diff --git a/docs/docs/customization.md b/docs/docs/customization.md
new file mode 100644
index 00000000..9e04280d
--- /dev/null
+++ b/docs/docs/customization.md
@@ -0,0 +1,121 @@
+# Customization
+
+Continue can be deeply customized by editing the `ContinueConfig` object in `~/.continue/config.py` on your machine. This file is created the first time you run Continue.
+
+## Change the default LLM
+
+Change the `default_model` field to any of "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4", "claude-2", or "ggml".
+
+### claude-2 and gpt-X
+
+If you have access, simply set `default_model` to the model you would like to use, then you will be prompted for a personal API key after reloading VS Code. If using an OpenAI model, you can press enter to try with our API key for free.
+
+### Local models with ggml
+
+See our [5 minute quickstart](https://github.com/continuedev/ggml-server-example) to run any model locally with ggml. While these models don't yet perform as well, they are free, entirely private, and run offline.
+
+### Azure OpenAI Service
+
+If you'd like to use OpenAI models but are concerned about privacy, you can use the Azure OpenAI service, which is GDPR and HIPAA compliant. After applying for access [here](https://azure.microsoft.com/en-us/products/ai-services/openai-service), you will typically hear back within only a few days. Once you have access, set `default_model` to "gpt-4", and then set the `azure_openai_info` property in the `ContinueConfig` like so:
+
+```python
+config = ContinueConfig(
+ ...
+ azure_openai_info=AzureInfo(
+ endpoint="https://my-azure-openai-instance.openai.azure.com/",
+ engine="my-azure-openai-deployment",
+ api_version="2023-03-15-preview"
+ )
+)
+```
+
+The easiest way to find this information is from the chat playground in the Azure OpenAI portal. Under the "Chat Session" section, click "View Code" to see each of these parameters. Finally, find one of your Azure OpenAI keys and enter it in the VS Code settings under `continue.OPENAI_API_KEY`.
+
+## Customize System Message
+
+You can write your own system message, a set of instructions that will always be top-of-mind for the LLM, by setting the `system_message` property to any string. For example, you might request "Please make all responses as concise as possible and never repeat something you have already explained."
+
+System messages can also reference files. For example, if there is a markdown file (e.g. at `/Users/nate/Documents/docs/reference.md`) you'd like the LLM to know about, you can reference it with [Mustache](http://mustache.github.io/mustache.5.html) templating like this: "Please reference this documentation: {{ Users/nate/Documents/docs/reference.md }}". As of now, you must use an absolute path.
+
+## Custom Commands
+
+You can add custom slash commands by adding a `CustomCommand` object to the `custom_commands` property. Each `CustomCommand` has
+
+- `name`: the name of the command, which will be invoked with `/name`
+- `description`: a short description of the command, which will appear in the dropdown
+- `prompt`: a set of instructions to the LLM, which will be shown in the prompt
+
+Custom commands are great when you are frequently reusing a prompt. For example, if you've crafted a great prompt and frequently ask the LLM to check for mistakes in your code, you could add a command like this:
+
+```python
+config = ContinueConfig(
+ ...
+ custom_commands=[
+ CustomCommand(
+ name="check",
+ description="Check for mistakes in my code",
+ prompt=dedent("""\
+ Please read the highlighted code and check for any mistakes. You should look for the following, and be extremely vigilant:
+ - Syntax errors
+ - Logic errors
+ - Security vulnerabilities
+ - Performance issues
+ - Anything else that looks wrong
+
+ Once you find an error, please explain it as clearly as possible, but without using extra words. For example, instead of saying "I think there is a syntax error on line 5", you should say "Syntax error on line 5". Give your answer as one bullet point per mistake found.""")
+ )
+ ]
+)
+```
+
+## Temperature
+
+Set `temperature` to any value between 0 and 1. Higher values will make the LLM more creative, while lower values will make it more predictable. The default is 0.5.
+
+## Custom Context Providers
+
+When you type '@' in the Continue text box, it will display a dropdown of items that can be selected to include in your message as context. For example, you might want to reference a GitHub Issue, file, or Slack thread. All of these options are provided by a `ContextProvider` class, and we make it easy to write your own. As an example, here is the `GitHubIssuesContextProvider`, which lets you search all open GitHub Issues in a repo:
+
+```python
+class GitHubIssuesContextProvider(ContextProvider):
+ """
+ The GitHubIssuesContextProvider is a ContextProvider that allows you to search GitHub issues in a repo.
+ """
+
+ title = "issues"
+ repo_name: str
+ auth_token: str
+
+ async def provide_context_items(self) -> List[ContextItem]:
+ auth = Auth.Token(self.auth_token)
+ gh = Github(auth=auth)
+
+ repo = gh.get_repo(self.repo_name)
+ issues = repo.get_issues().get_page(0)
+
+ return [ContextItem(
+ content=issue.body,
+ description=ContextItemDescription(
+ name=f"Issue #{issue.number}",
+ description=issue.title,
+ id=ContextItemId(
+ provider_title=self.title,
+ item_id=issue.id
+ )
+ )
+ ) for issue in issues]
+```
+
+It can then be set in the `ContinueConfig` like so:
+
+```python
+config = ContinueConfig(
+ ...
+ context_providers=[
+ GitHubIssuesContextProvider(
+ repo_name="my-github-username-or-org/my-github-repo",
+ auth_token="my-github-auth-token"
+ )
+ ]
+)
+```
diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md
index 753c1479..fc19552e 100644
--- a/docs/docs/getting-started.md
+++ b/docs/docs/getting-started.md
@@ -2,6 +2,10 @@
## Recommended: Install in VS Code
+:::note
+Continue requires that you have Python 3.8 or greater. If you do not, please [install](https://python.org) it
+:::
+
1. Click `Install` on the **[Continue extension in the Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Continue.continue)**
2. This will open the Continue extension page in VS Code, where you will need to click `Install` again
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 9baf1b94..83b34ee8 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -13,7 +13,15 @@
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
- docsSidebar: ["intro", "getting-started", "how-to-use-continue", "how-continue-works", "telemetry", "collecting-data"],
+ docsSidebar: [
+ "intro",
+ "getting-started",
+ "how-to-use-continue",
+ "how-continue-works",
+ "telemetry",
+ "collecting-data",
+ "customization",
+ ],
};
module.exports = sidebars;
diff --git a/extension/DEV_README.md b/extension/DEV_README.md
index 87ed9334..72ea5c6a 100644
--- a/extension/DEV_README.md
+++ b/extension/DEV_README.md
@@ -6,8 +6,17 @@ This is the Continue VS Code Extension. Its primary jobs are
2. Open the Continue React app in a side panel. The React app's source code lives in the `react-app` directory. The panel is opened by the `continue.openContinueGUI` command, as defined in `src/commands.ts`.
3. Run a Continue server in the background, which connects to both the IDE protocol and the React app. The server is launched in `src/activation/environmentSetup.ts` by calling Python code that lives in `server/` (unless extension settings define a server URL other than localhost:65432, in which case the extension will just connect to that).
-4. Open Continue
+## Setting up for development
-# Notes
+1. Clone this repo
+2. `cd extension`
+3. `npm run full-package`
+
+ > If NPM is not installed, you can use `brew install node` on Mac, or see the [installation page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) for other platforms, or more detailed instructions.
+
+4. Open a VS Code window with `/extension` as the workspace root (_this is important, development mode will not work otherwise_)
+5. Open any `.ts` file in the workspace, then press F5 and select "VS Code Extension Development" to begin debugging.
+
+## Notes
- We require vscode engine `^1.67.0` and use `@types/vscode` version `1.67.0` because this is the earliest version that doesn't break any of the APIs we are using. If you go back to `1.66.0`, then it will break `vscode.window.tabGroups`.
diff --git a/extension/README.md b/extension/README.md
index 2d449b92..2944325b 100644
--- a/extension/README.md
+++ b/extension/README.md
@@ -25,6 +25,16 @@ Let Continue build the scaffolding of Python scripts, React components, and more
- “/edit make an IAM policy that creates a user with read-only access to S3”
- “/edit use this schema to write me a SQL query that gets recently churned users”
+## Install
+
+Continue requires that you have Python 3.8 or greater. If you do not, please [install](https://python.org) it
+
+If your Continue server is not setting up, please check the console logs:
+1. `cmd+shift+p` (MacOS) / `ctrl+shift+p` (Windows)
+2. Search for and then select "Developer: Toggle Developer Tools"
+3. Select `Console`
+4. Read the console logs
+
## OpenAI API Key
New users can try out Continue with GPT-4 using a proxy server that securely makes calls to OpenAI using our API key. Continue should just work the first time you install the extension in VS Code.
diff --git a/extension/package-lock.json b/extension/package-lock.json
index a235aa81..6e049c1e 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "continue",
- "version": "0.0.158",
+ "version": "0.0.190",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "continue",
- "version": "0.0.158",
+ "version": "0.0.190",
"license": "Apache-2.0",
"dependencies": {
"@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index 6bb6b720..884b518c 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -14,7 +14,7 @@
"displayName": "Continue",
"pricing": "Free",
"description": "The open-source coding autopilot",
- "version": "0.0.158",
+ "version": "0.0.190",
"publisher": "Continue",
"engines": {
"vscode": "^1.67.0"
@@ -106,13 +106,18 @@
"command": "continue.quickTextEntry",
"category": "Continue",
"title": "Quick Text Entry"
+ },
+ {
+ "command": "continue.quickFix",
+ "category": "Continue",
+ "title": "Quick Fix"
}
],
"keybindings": [
{
"command": "continue.focusContinueInput",
- "mac": "cmd+k",
- "key": "ctrl+k"
+ "mac": "cmd+m",
+ "key": "ctrl+m"
},
{
"command": "continue.suggestionDown",
@@ -222,7 +227,7 @@
"test": "node ./out/test/runTest.js",
"jest": "jest --config ./jest.config.js",
"package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && cp ./config/dev_config.json ./config/config.json",
- "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.2-py3-none-any.whl ../extension/server/continuedev-0.1.2-py3-none-any.whl && cd ../extension && npm install && npm run typegen && npm run clientgen && cd react-app && npm install && npm run build && cd .. && npm run package",
+ "full-package": "cd ../continuedev && poetry install && poetry build && cp ./dist/continuedev-0.1.2-py3-none-any.whl ../extension/server/continuedev-0.1.2-py3-none-any.whl && cd ../extension && npm install && npm run typegen && npm run clientgen && cd react-app && npm install && npm run build && cd .. && npm run package",
"install-extension": "code --install-extension ./build/continue-0.0.8.vsix",
"uninstall": "code --uninstall-extension .continue",
"reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension"
diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json
index 7316581d..13e02e86 100644
--- a/extension/react-app/package-lock.json
+++ b/extension/react-app/package-lock.json
@@ -11,12 +11,12 @@
"@styled-icons/heroicons-outline": "^10.47.0",
"@styled-icons/heroicons-solid": "^10.47.0",
"@types/vscode-webview": "^1.57.1",
+ "@uiw/react-markdown-preview": "^4.1.13",
"downshift": "^7.6.0",
"posthog-js": "^1.58.0",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-markdown": "^8.0.5",
"react-redux": "^8.0.5",
"react-switch": "^7.0.0",
"react-syntax-highlighter": "^15.5.0",
@@ -963,6 +963,16 @@
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
+ "node_modules/@types/parse5": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
+ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g=="
+ },
+ "node_modules/@types/prismjs": {
+ "version": "1.26.0",
+ "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz",
+ "integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ=="
+ },
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -1027,6 +1037,34 @@
"resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.1.tgz",
"integrity": "sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ=="
},
+ "node_modules/@uiw/copy-to-clipboard": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.15.tgz",
+ "integrity": "sha512-1bbGZ3T+SGmA07BoVPK4UCUDcowDN/moctviJGQexfOc9qL8TMLDQPr7mTPvDKhgJkgnlKkAQNFU8PiarIi9sQ=="
+ },
+ "node_modules/@uiw/react-markdown-preview": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-4.1.13.tgz",
+ "integrity": "sha512-fmIGvBpK6HJyDFf7EokjZSIS0713Bq5KwhOsZ8IkbCMYDcDThFlmMkTTqyzGjL3phrkP9ED5O63WSILzefqe6A==",
+ "dependencies": {
+ "@babel/runtime": "^7.17.2",
+ "@uiw/copy-to-clipboard": "~1.0.12",
+ "react-markdown": "~8.0.0",
+ "rehype-attr": "~2.1.0",
+ "rehype-autolink-headings": "~6.1.1",
+ "rehype-ignore": "^1.0.1",
+ "rehype-prism-plus": "~1.5.0",
+ "rehype-raw": "^6.1.1",
+ "rehype-rewrite": "~3.0.6",
+ "rehype-slug": "~5.1.0",
+ "remark-gfm": "~3.0.1",
+ "unist-util-visit": "^4.1.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
"node_modules/@vitejs/plugin-react-swc": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz",
@@ -1163,6 +1201,15 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/bcp-47-match": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
+ "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -1172,6 +1219,11 @@
"node": ">=8"
}
},
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
+ },
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -1245,6 +1297,15 @@
}
]
},
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -1370,6 +1431,11 @@
"node": ">=4"
}
},
+ "node_modules/css-selector-parser": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
+ "integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g=="
+ },
"node_modules/css-to-react-native": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
@@ -1473,6 +1539,18 @@
"node": ">=0.3.1"
}
},
+ "node_modules/direction": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz",
+ "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==",
+ "bin": {
+ "direction": "cli.js"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -1684,6 +1762,11 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
+ "node_modules/github-slugger": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
+ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -1729,6 +1812,86 @@
"node": ">=4"
}
},
+ "node_modules/hast-util-from-parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz",
+ "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0",
+ "hastscript": "^7.0.0",
+ "property-information": "^6.0.0",
+ "vfile": "^5.0.0",
+ "vfile-location": "^4.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
+ "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5/node_modules/hastscript": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
+ "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^3.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-has-property": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz",
+ "integrity": "sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-heading-rank": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.1.tgz",
+ "integrity": "sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-is-element": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz",
+ "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
@@ -1738,6 +1901,83 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/hast-util-raw": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz",
+ "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/parse5": "^6.0.0",
+ "hast-util-from-parse5": "^7.0.0",
+ "hast-util-to-parse5": "^7.0.0",
+ "html-void-elements": "^2.0.0",
+ "parse5": "^6.0.0",
+ "unist-util-position": "^4.0.0",
+ "unist-util-visit": "^4.0.0",
+ "vfile": "^5.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-select": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-5.0.5.tgz",
+ "integrity": "sha512-QQhWMhgTFRhCaQdgTKzZ5g31GLQ9qRb1hZtDPMqQaOhpLBziWcshUS0uCR5IJ0U1jrK/mxg35fmcq+Dp/Cy2Aw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0",
+ "bcp-47-match": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "css-selector-parser": "^1.0.0",
+ "direction": "^2.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-to-string": "^2.0.0",
+ "hast-util-whitespace": "^2.0.0",
+ "not": "^0.1.0",
+ "nth-check": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-parse5": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz",
+ "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz",
+ "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==",
+ "dependencies": {
+ "@types/hast": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-whitespace": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz",
@@ -1814,6 +2054,15 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/html-void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
+ "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/inline-style-parser": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
@@ -1995,6 +2244,15 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -2019,6 +2277,15 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/markdown-table": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
+ "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/mdast-util-definitions": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
@@ -2033,6 +2300,32 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz",
+ "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/mdast-util-from-markdown": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz",
@@ -2056,6 +2349,107 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-gfm": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz",
+ "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==",
+ "dependencies": {
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-gfm-autolink-literal": "^1.0.0",
+ "mdast-util-gfm-footnote": "^1.0.0",
+ "mdast-util-gfm-strikethrough": "^1.0.0",
+ "mdast-util-gfm-table": "^1.0.0",
+ "mdast-util-gfm-task-list-item": "^1.0.0",
+ "mdast-util-to-markdown": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz",
+ "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "ccount": "^2.0.0",
+ "mdast-util-find-and-replace": "^2.0.0",
+ "micromark-util-character": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz",
+ "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0",
+ "micromark-util-normalize-identifier": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz",
+ "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz",
+ "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz",
+ "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz",
+ "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "unist-util-is": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/mdast-util-to-hast": {
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz",
@@ -2075,6 +2469,25 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-to-markdown": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz",
+ "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^3.0.0",
+ "mdast-util-to-string": "^3.0.0",
+ "micromark-util-decode-string": "^1.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/mdast-util-to-string": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.1.tgz",
@@ -2163,6 +2576,120 @@
"uvu": "^0.5.0"
}
},
+ "node_modules/micromark-extension-gfm": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz",
+ "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^1.0.0",
+ "micromark-extension-gfm-footnote": "^1.0.0",
+ "micromark-extension-gfm-strikethrough": "^1.0.0",
+ "micromark-extension-gfm-table": "^1.0.0",
+ "micromark-extension-gfm-tagfilter": "^1.0.0",
+ "micromark-extension-gfm-task-list-item": "^1.0.0",
+ "micromark-util-combine-extensions": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz",
+ "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==",
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz",
+ "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==",
+ "dependencies": {
+ "micromark-core-commonmark": "^1.0.0",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz",
+ "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==",
+ "dependencies": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-classify-character": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz",
+ "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==",
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz",
+ "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==",
+ "dependencies": {
+ "micromark-util-types": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz",
+ "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==",
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/micromark-factory-destination": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
@@ -2589,6 +3116,22 @@
"node": ">=0.10.0"
}
},
+ "node_modules/not": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz",
+ "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA=="
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -2632,6 +3175,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/parse-numeric-range": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz",
+ "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ=="
+ },
+ "node_modules/parse5": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
+ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
+ },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -3047,6 +3600,257 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
+ "node_modules/rehype-attr": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-2.1.4.tgz",
+ "integrity": "sha512-iAeaL5JyF4XxkcvWzpi/0SAF7iV7qOTaHS56tJuEsXziQc3+PEmMn65kV8OFgbO9mRVY7J1fRC/aLvot1PsNkg==",
+ "dependencies": {
+ "unified": "~10.1.1",
+ "unist-util-visit": "~4.1.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/rehype-autolink-headings": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-6.1.1.tgz",
+ "integrity": "sha512-NMYzZIsHM3sA14nC5rAFuUPIOfg+DFmf9EY1YMhaNlB7+3kK/ZlE6kqPfuxr1tsJ1XWkTrMtMoyHosU70d35mA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "extend": "^3.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-heading-rank": "^2.0.0",
+ "hast-util-is-element": "^2.0.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-ignore": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-1.0.5.tgz",
+ "integrity": "sha512-JQXS5eDwXaYKwB8JEYFJJA/YvGi0sSNUOYuiURMtuPTg8tuWHFB91JMYLbImH1FyvyGQM4fIBqNMAPB50WR2Bw==",
+ "dependencies": {
+ "hast-util-select": "^5.0.5",
+ "unified": "^10.1.2",
+ "unist-util-visit": "^4.1.2"
+ },
+ "engines": {
+ "node": "^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/rehype-parse": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz",
+ "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "hast-util-from-parse5": "^7.0.0",
+ "parse5": "^6.0.0",
+ "unified": "^10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-prism-plus": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-1.5.1.tgz",
+ "integrity": "sha512-mowYefSfrIkMMxkb0fwuEXlvc5nA9b1vQ6mzujM81Qx28RI0mo7jCHsBZ2tJ4eIJKXdFn+EdPkZZBGB10K02vg==",
+ "dependencies": {
+ "hast-util-to-string": "^2.0.0",
+ "parse-numeric-range": "^1.3.0",
+ "refractor": "^4.7.0",
+ "rehype-parse": "^8.0.2",
+ "unist-util-filter": "^4.0.0",
+ "unist-util-visit": "^4.0.0"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/hast-util-parse-selector": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
+ "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/hastscript": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
+ "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^3.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/parse-entities": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
+ "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/refractor": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz",
+ "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/prismjs": "^1.0.0",
+ "hastscript": "^7.0.0",
+ "parse-entities": "^4.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-raw": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz",
+ "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "hast-util-raw": "^7.2.0",
+ "unified": "^10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-rewrite": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-3.0.6.tgz",
+ "integrity": "sha512-REDTNCvsKcAazy8IQWzKp66AhSUDSOIKssSCqNqCcT9sN7JCwAAm3mWGTUdUzq80ABuy8d0D6RBwbnewu1aY1g==",
+ "dependencies": {
+ "hast-util-select": "~5.0.1",
+ "unified": "~10.1.1",
+ "unist-util-visit": "~4.1.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/rehype-slug": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz",
+ "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "github-slugger": "^2.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-heading-rank": "^2.0.0",
+ "hast-util-to-string": "^2.0.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-gfm": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz",
+ "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-gfm": "^2.0.0",
+ "micromark-extension-gfm": "^2.0.0",
+ "unified": "^10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/remark-parse": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz",
@@ -3365,6 +4169,16 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unist-util-filter": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-4.0.1.tgz",
+ "integrity": "sha512-RynicUM/vbOSTSiUK+BnaK9XMfmQUh6gyi7L6taNgc7FIf84GukXVV3ucGzEN/PhUUkdP5hb1MmXc+3cvPUm5Q==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ }
+ },
"node_modules/unist-util-generated": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz",
@@ -3517,6 +4331,19 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/vfile-location": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz",
+ "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "vfile": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/vfile-message": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz",
@@ -3587,6 +4414,15 @@
"fs-extra": "^10.0.0"
}
},
+ "node_modules/web-namespaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -3603,6 +4439,15 @@
"engines": {
"node": ">= 6"
}
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
}
},
"dependencies": {
@@ -4143,6 +4988,16 @@
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
+ "@types/parse5": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
+ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g=="
+ },
+ "@types/prismjs": {
+ "version": "1.26.0",
+ "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz",
+ "integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ=="
+ },
"@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -4207,6 +5062,30 @@
"resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.1.tgz",
"integrity": "sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ=="
},
+ "@uiw/copy-to-clipboard": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.15.tgz",
+ "integrity": "sha512-1bbGZ3T+SGmA07BoVPK4UCUDcowDN/moctviJGQexfOc9qL8TMLDQPr7mTPvDKhgJkgnlKkAQNFU8PiarIi9sQ=="
+ },
+ "@uiw/react-markdown-preview": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-4.1.13.tgz",
+ "integrity": "sha512-fmIGvBpK6HJyDFf7EokjZSIS0713Bq5KwhOsZ8IkbCMYDcDThFlmMkTTqyzGjL3phrkP9ED5O63WSILzefqe6A==",
+ "requires": {
+ "@babel/runtime": "^7.17.2",
+ "@uiw/copy-to-clipboard": "~1.0.12",
+ "react-markdown": "~8.0.0",
+ "rehype-attr": "~2.1.0",
+ "rehype-autolink-headings": "~6.1.1",
+ "rehype-ignore": "^1.0.1",
+ "rehype-prism-plus": "~1.5.0",
+ "rehype-raw": "^6.1.1",
+ "rehype-rewrite": "~3.0.6",
+ "rehype-slug": "~5.1.0",
+ "remark-gfm": "~3.0.1",
+ "unist-util-visit": "^4.1.0"
+ }
+ },
"@vitejs/plugin-react-swc": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz",
@@ -4299,12 +5178,22 @@
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="
},
+ "bcp-47-match": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
+ "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="
+ },
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
+ "boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
+ },
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -4343,6 +5232,11 @@
"integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==",
"dev": true
},
+ "ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="
+ },
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -4436,6 +5330,11 @@
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="
},
+ "css-selector-parser": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
+ "integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g=="
+ },
"css-to-react-native": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
@@ -4506,6 +5405,11 @@
"resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
"integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw=="
},
+ "direction": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz",
+ "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="
+ },
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -4672,6 +5576,11 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
+ "github-slugger": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
+ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
+ },
"glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -4705,11 +5614,130 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
},
+ "hast-util-from-parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz",
+ "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0",
+ "hastscript": "^7.0.0",
+ "property-information": "^6.0.0",
+ "vfile": "^5.0.0",
+ "vfile-location": "^4.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "dependencies": {
+ "hast-util-parse-selector": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
+ "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
+ "requires": {
+ "@types/hast": "^2.0.0"
+ }
+ },
+ "hastscript": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
+ "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^3.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ }
+ }
+ }
+ },
+ "hast-util-has-property": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz",
+ "integrity": "sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg=="
+ },
+ "hast-util-heading-rank": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.1.tgz",
+ "integrity": "sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==",
+ "requires": {
+ "@types/hast": "^2.0.0"
+ }
+ },
+ "hast-util-is-element": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz",
+ "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0"
+ }
+ },
"hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="
},
+ "hast-util-raw": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz",
+ "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/parse5": "^6.0.0",
+ "hast-util-from-parse5": "^7.0.0",
+ "hast-util-to-parse5": "^7.0.0",
+ "html-void-elements": "^2.0.0",
+ "parse5": "^6.0.0",
+ "unist-util-position": "^4.0.0",
+ "unist-util-visit": "^4.0.0",
+ "vfile": "^5.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ }
+ },
+ "hast-util-select": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-5.0.5.tgz",
+ "integrity": "sha512-QQhWMhgTFRhCaQdgTKzZ5g31GLQ9qRb1hZtDPMqQaOhpLBziWcshUS0uCR5IJ0U1jrK/mxg35fmcq+Dp/Cy2Aw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0",
+ "bcp-47-match": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "css-selector-parser": "^1.0.0",
+ "direction": "^2.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-to-string": "^2.0.0",
+ "hast-util-whitespace": "^2.0.0",
+ "not": "^0.1.0",
+ "nth-check": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ }
+ },
+ "hast-util-to-parse5": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz",
+ "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ }
+ },
+ "hast-util-to-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz",
+ "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==",
+ "requires": {
+ "@types/hast": "^2.0.0"
+ }
+ },
"hast-util-whitespace": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz",
@@ -4767,6 +5795,11 @@
}
}
},
+ "html-void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
+ "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A=="
+ },
"inline-style-parser": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
@@ -4880,6 +5913,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="
+ },
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -4897,6 +5935,11 @@
"highlight.js": "~10.7.0"
}
},
+ "markdown-table": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
+ "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw=="
+ },
"mdast-util-definitions": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
@@ -4907,6 +5950,24 @@
"unist-util-visit": "^4.0.0"
}
},
+ "mdast-util-find-and-replace": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz",
+ "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
+ }
+ }
+ },
"mdast-util-from-markdown": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz",
@@ -4926,6 +5987,79 @@
"uvu": "^0.5.0"
}
},
+ "mdast-util-gfm": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz",
+ "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==",
+ "requires": {
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-gfm-autolink-literal": "^1.0.0",
+ "mdast-util-gfm-footnote": "^1.0.0",
+ "mdast-util-gfm-strikethrough": "^1.0.0",
+ "mdast-util-gfm-table": "^1.0.0",
+ "mdast-util-gfm-task-list-item": "^1.0.0",
+ "mdast-util-to-markdown": "^1.0.0"
+ }
+ },
+ "mdast-util-gfm-autolink-literal": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz",
+ "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "ccount": "^2.0.0",
+ "mdast-util-find-and-replace": "^2.0.0",
+ "micromark-util-character": "^1.0.0"
+ }
+ },
+ "mdast-util-gfm-footnote": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz",
+ "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0",
+ "micromark-util-normalize-identifier": "^1.0.0"
+ }
+ },
+ "mdast-util-gfm-strikethrough": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz",
+ "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ }
+ },
+ "mdast-util-gfm-table": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz",
+ "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ }
+ },
+ "mdast-util-gfm-task-list-item": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz",
+ "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ }
+ },
+ "mdast-util-phrasing": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz",
+ "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "unist-util-is": "^5.0.0"
+ }
+ },
"mdast-util-to-hast": {
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz",
@@ -4941,6 +6075,21 @@
"unist-util-visit": "^4.0.0"
}
},
+ "mdast-util-to-markdown": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz",
+ "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^3.0.0",
+ "mdast-util-to-string": "^3.0.0",
+ "micromark-util-decode-string": "^1.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ }
+ },
"mdast-util-to-string": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.1.tgz",
@@ -5002,6 +6151,92 @@
"uvu": "^0.5.0"
}
},
+ "micromark-extension-gfm": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz",
+ "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==",
+ "requires": {
+ "micromark-extension-gfm-autolink-literal": "^1.0.0",
+ "micromark-extension-gfm-footnote": "^1.0.0",
+ "micromark-extension-gfm-strikethrough": "^1.0.0",
+ "micromark-extension-gfm-table": "^1.0.0",
+ "micromark-extension-gfm-tagfilter": "^1.0.0",
+ "micromark-extension-gfm-task-list-item": "^1.0.0",
+ "micromark-util-combine-extensions": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-extension-gfm-autolink-literal": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz",
+ "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==",
+ "requires": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-extension-gfm-footnote": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz",
+ "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==",
+ "requires": {
+ "micromark-core-commonmark": "^1.0.0",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-strikethrough": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz",
+ "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==",
+ "requires": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-classify-character": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-table": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz",
+ "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==",
+ "requires": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-tagfilter": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz",
+ "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==",
+ "requires": {
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-extension-gfm-task-list-item": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz",
+ "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==",
+ "requires": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
"micromark-factory-destination": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
@@ -5217,6 +6452,19 @@
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true
},
+ "not": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz",
+ "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA=="
+ },
+ "nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "requires": {
+ "boolbase": "^1.0.0"
+ }
+ },
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -5248,6 +6496,16 @@
}
}
},
+ "parse-numeric-range": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz",
+ "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ=="
+ },
+ "parse5": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
+ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
+ },
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -5510,6 +6768,190 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
+ "rehype-attr": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-2.1.4.tgz",
+ "integrity": "sha512-iAeaL5JyF4XxkcvWzpi/0SAF7iV7qOTaHS56tJuEsXziQc3+PEmMn65kV8OFgbO9mRVY7J1fRC/aLvot1PsNkg==",
+ "requires": {
+ "unified": "~10.1.1",
+ "unist-util-visit": "~4.1.0"
+ }
+ },
+ "rehype-autolink-headings": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-6.1.1.tgz",
+ "integrity": "sha512-NMYzZIsHM3sA14nC5rAFuUPIOfg+DFmf9EY1YMhaNlB7+3kK/ZlE6kqPfuxr1tsJ1XWkTrMtMoyHosU70d35mA==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "extend": "^3.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-heading-rank": "^2.0.0",
+ "hast-util-is-element": "^2.0.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0"
+ }
+ },
+ "rehype-ignore": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-1.0.5.tgz",
+ "integrity": "sha512-JQXS5eDwXaYKwB8JEYFJJA/YvGi0sSNUOYuiURMtuPTg8tuWHFB91JMYLbImH1FyvyGQM4fIBqNMAPB50WR2Bw==",
+ "requires": {
+ "hast-util-select": "^5.0.5",
+ "unified": "^10.1.2",
+ "unist-util-visit": "^4.1.2"
+ }
+ },
+ "rehype-parse": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz",
+ "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "hast-util-from-parse5": "^7.0.0",
+ "parse5": "^6.0.0",
+ "unified": "^10.0.0"
+ }
+ },
+ "rehype-prism-plus": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-1.5.1.tgz",
+ "integrity": "sha512-mowYefSfrIkMMxkb0fwuEXlvc5nA9b1vQ6mzujM81Qx28RI0mo7jCHsBZ2tJ4eIJKXdFn+EdPkZZBGB10K02vg==",
+ "requires": {
+ "hast-util-to-string": "^2.0.0",
+ "parse-numeric-range": "^1.3.0",
+ "refractor": "^4.7.0",
+ "rehype-parse": "^8.0.2",
+ "unist-util-filter": "^4.0.0",
+ "unist-util-visit": "^4.0.0"
+ },
+ "dependencies": {
+ "character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="
+ },
+ "character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="
+ },
+ "hast-util-parse-selector": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
+ "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
+ "requires": {
+ "@types/hast": "^2.0.0"
+ }
+ },
+ "hastscript": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
+ "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^3.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ }
+ },
+ "is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="
+ },
+ "is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "requires": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ }
+ },
+ "is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="
+ },
+ "is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="
+ },
+ "parse-entities": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
+ "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "character-entities": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ }
+ },
+ "refractor": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz",
+ "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/prismjs": "^1.0.0",
+ "hastscript": "^7.0.0",
+ "parse-entities": "^4.0.0"
+ }
+ }
+ }
+ },
+ "rehype-raw": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz",
+ "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "hast-util-raw": "^7.2.0",
+ "unified": "^10.0.0"
+ }
+ },
+ "rehype-rewrite": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-3.0.6.tgz",
+ "integrity": "sha512-REDTNCvsKcAazy8IQWzKp66AhSUDSOIKssSCqNqCcT9sN7JCwAAm3mWGTUdUzq80ABuy8d0D6RBwbnewu1aY1g==",
+ "requires": {
+ "hast-util-select": "~5.0.1",
+ "unified": "~10.1.1",
+ "unist-util-visit": "~4.1.0"
+ }
+ },
+ "rehype-slug": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz",
+ "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "github-slugger": "^2.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-heading-rank": "^2.0.0",
+ "hast-util-to-string": "^2.0.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0"
+ }
+ },
+ "remark-gfm": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz",
+ "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-gfm": "^2.0.0",
+ "micromark-extension-gfm": "^2.0.0",
+ "unified": "^10.0.0"
+ }
+ },
"remark-parse": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz",
@@ -5722,6 +7164,16 @@
"vfile": "^5.0.0"
}
},
+ "unist-util-filter": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-4.0.1.tgz",
+ "integrity": "sha512-RynicUM/vbOSTSiUK+BnaK9XMfmQUh6gyi7L6taNgc7FIf84GukXVV3ucGzEN/PhUUkdP5hb1MmXc+3cvPUm5Q==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ }
+ },
"unist-util-generated": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz",
@@ -5819,6 +7271,15 @@
"vfile-message": "^3.0.0"
}
},
+ "vfile-location": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz",
+ "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "vfile": "^5.0.0"
+ }
+ },
"vfile-message": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz",
@@ -5849,6 +7310,11 @@
"fs-extra": "^10.0.0"
}
},
+ "web-namespaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="
+ },
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -5859,6 +7325,11 @@
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
+ },
+ "zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="
}
}
}
diff --git a/extension/react-app/package.json b/extension/react-app/package.json
index 4bedb813..704f520a 100644
--- a/extension/react-app/package.json
+++ b/extension/react-app/package.json
@@ -12,12 +12,12 @@
"@styled-icons/heroicons-outline": "^10.47.0",
"@styled-icons/heroicons-solid": "^10.47.0",
"@types/vscode-webview": "^1.57.1",
+ "@uiw/react-markdown-preview": "^4.1.13",
"downshift": "^7.6.0",
"posthog-js": "^1.58.0",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-markdown": "^8.0.5",
"react-redux": "^8.0.5",
"react-switch": "^7.0.0",
"react-syntax-highlighter": "^15.5.0",
diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx
index c9bd42e0..aa462171 100644
--- a/extension/react-app/src/App.tsx
+++ b/extension/react-app/src/App.tsx
@@ -2,7 +2,7 @@ import DebugPanel from "./components/DebugPanel";
import GUI from "./pages/gui";
import { createContext } from "react";
import useContinueGUIProtocol from "./hooks/useWebsocket";
-import ContinueGUIClientProtocol from "./hooks/useContinueGUIProtocol";
+import ContinueGUIClientProtocol from "./hooks/ContinueGUIClientProtocol";
export const GUIClientContext = createContext<
ContinueGUIClientProtocol | undefined
@@ -13,11 +13,7 @@ function App() {
return (
<GUIClientContext.Provider value={client}>
- <DebugPanel
- tabs={[
- { element: <GUI />, title: "GUI" }
- ]}
- />
+ <DebugPanel tabs={[{ element: <GUI />, title: "GUI" }]} />
</GUIClientContext.Provider>
);
}
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 7d6541c7..1e2ca135 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -1,30 +1,20 @@
-import React, {
- useCallback,
- useEffect,
- useImperativeHandle,
- useState,
-} from "react";
+import React, { useEffect, useImperativeHandle, useState } from "react";
import { useCombobox } from "downshift";
import styled from "styled-components";
import {
- buttonColor,
defaultBorderRadius,
lightGray,
secondaryDark,
vscBackground,
+ vscForeground,
} from ".";
import CodeBlock from "./CodeBlock";
-import { RangeInFile } from "../../../src/client";
import PillButton from "./PillButton";
import HeaderButtonWithText from "./HeaderButtonWithText";
-import {
- Trash,
- LockClosed,
- LockOpen,
- Plus,
- DocumentPlus,
-} from "@styled-icons/heroicons-outline";
+import { DocumentPlus } from "@styled-icons/heroicons-outline";
import { HighlightedRangeContext } from "../../../schema/FullState";
+import { postVscMessage } from "../vscode";
+import { getMetaKeyLabel } from "../util";
// #region styled components
const mainInputFontSize = 13;
@@ -48,21 +38,6 @@ const EmptyPillDiv = styled.div`
}
`;
-const ContextDropdown = styled.div`
- position: absolute;
- padding: 4px;
- width: calc(100% - 16px - 8px);
- background-color: ${secondaryDark};
- color: white;
- border-bottom-right-radius: ${defaultBorderRadius};
- border-bottom-left-radius: ${defaultBorderRadius};
- /* border: 1px solid white; */
- border-top: none;
- margin: 8px;
- outline: 1px solid orange;
- z-index: 5;
-`;
-
const MainTextInput = styled.textarea`
resize: none;
@@ -74,7 +49,7 @@ const MainTextInput = styled.textarea`
height: auto;
width: 100%;
background-color: ${secondaryDark};
- color: white;
+ color: ${vscForeground};
z-index: 1;
border: 1px solid transparent;
@@ -96,15 +71,15 @@ const Ul = styled.ul<{
: `transform: translateY(${2 * mainInputFontSize}px);`}
position: absolute;
background: ${vscBackground};
- background-color: ${secondaryDark};
- color: white;
+ color: ${vscForeground};
max-height: ${UlMaxHeight}px;
+ width: calc(100% - 16px);
overflow-y: scroll;
overflow-x: hidden;
padding: 0;
${({ hidden }) => hidden && "display: none;"}
border-radius: ${defaultBorderRadius};
- border: 0.5px solid gray;
+ outline: 0.5px solid gray;
z-index: 2;
// Get rid of scrollbar and its padding
scrollbar-width: none;
@@ -120,6 +95,7 @@ const Li = styled.li<{
selected: boolean;
isLastItem: boolean;
}>`
+ background-color: ${vscBackground};
${({ highlighted }) => highlighted && "background: #ff000066;"}
${({ selected }) => selected && "font-weight: bold;"}
padding: 0.5rem 0.75rem;
@@ -149,10 +125,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
// The position of the current command you are typing now, so the one that will be appended to history once you press enter
const [positionInHistory, setPositionInHistory] = React.useState<number>(0);
const [items, setItems] = React.useState(props.items);
- const [hoveringButton, setHoveringButton] = React.useState(false);
- const [hoveringContextDropdown, setHoveringContextDropdown] =
- React.useState(false);
- const [pinned, setPinned] = useState(false);
const [highlightedCodeSections, setHighlightedCodeSections] = React.useState(
props.highlightedCodeSections || []
);
@@ -181,6 +153,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
useImperativeHandle(ref, () => downshiftProps, [downshiftProps]);
const [metaKeyPressed, setMetaKeyPressed] = useState(false);
+ const [focused, setFocused] = useState(false);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Meta") {
@@ -241,6 +214,14 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
)} */}
{highlightedCodeSections.map((section, idx) => (
<PillButton
+ warning={
+ section.range.contents.length > 4000 && section.editing
+ ? "Editing such a large range may be slow"
+ : undefined
+ }
+ onlyShowDelete={
+ highlightedCodeSections.length <= 1 || section.editing
+ }
editing={section.editing}
pinned={section.pinned}
index={idx}
@@ -258,15 +239,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
return newSections;
});
}}
- onHover={(val: boolean) => {
- if (val) {
- setHoveringButton(val);
- } else {
- setTimeout(() => {
- setHoveringButton(val);
- }, 100);
- }
- }}
/>
))}
{props.highlightedCodeSections.length > 0 &&
@@ -276,11 +248,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
props.onToggleAddContext();
}}
>
- Highlight to Add Context
+ Highlight code section
</EmptyPillDiv>
) : (
<HeaderButtonWithText
- text="Add to Context"
+ text="Add more code to context"
onClick={() => {
props.onToggleAddContext();
}}
@@ -292,7 +264,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
<div className="flex px-2" ref={divRef} hidden={!downshiftProps.isOpen}>
<MainTextInput
disabled={props.disabled}
- placeholder="Ask a question, give instructions, or type '/' to see slash commands. ⌘⏎ to edit."
+ placeholder={`Ask a question, give instructions, or type '/' to see slash commands`}
{...getInputProps({
onChange: (e) => {
const target = e.target as HTMLTextAreaElement;
@@ -305,6 +277,13 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
// setShowContextDropdown(target.value.endsWith("@"));
},
+ onFocus: (e) => {
+ setFocused(true);
+ },
+ onBlur: (e) => {
+ setFocused(false);
+ postVscMessage("blurContinueInput", {});
+ },
onKeyDown: (event) => {
if (event.key === "Enter" && event.shiftKey) {
// Prevent Downshift's default 'Enter' behavior.
@@ -359,6 +338,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
})}
showAbove={showAbove()}
ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0}
+ hidden={!downshiftProps.isOpen || items.length === 0}
>
{downshiftProps.isOpen &&
items.map((item, index) => (
@@ -378,29 +358,13 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
</div>
{highlightedCodeSections.length === 0 &&
(downshiftProps.inputValue?.startsWith("/edit") ||
- (metaKeyPressed && downshiftProps.inputValue?.length > 0)) && (
+ (focused &&
+ metaKeyPressed &&
+ downshiftProps.inputValue?.length > 0)) && (
<div className="text-trueGray-400 pr-4 text-xs text-right">
Inserting at cursor
</div>
)}
- <ContextDropdown
- onMouseEnter={() => {
- setHoveringContextDropdown(true);
- }}
- onMouseLeave={() => {
- setHoveringContextDropdown(false);
- }}
- hidden={true || (!hoveringContextDropdown && !hoveringButton)}
- >
- {highlightedCodeSections.map((section, idx) => (
- <>
- <p>{section.display_name}</p>
- <CodeBlock showCopy={false} key={idx}>
- {section.range.contents}
- </CodeBlock>
- </>
- ))}
- </ContextDropdown>
</>
);
});
diff --git a/extension/react-app/src/components/InputAndButton.tsx b/extension/react-app/src/components/InputAndButton.tsx
index 0a8592f2..8019d014 100644
--- a/extension/react-app/src/components/InputAndButton.tsx
+++ b/extension/react-app/src/components/InputAndButton.tsx
@@ -1,6 +1,6 @@
import React, { useRef } from "react";
import styled from "styled-components";
-import { vscBackground } from ".";
+import { vscBackground, vscForeground } from ".";
interface InputAndButtonProps {
onUserInput: (input: string) => void;
@@ -16,7 +16,7 @@ const Input = styled.input`
padding: 0.5rem;
border: 1px solid white;
background-color: ${vscBackground};
- color: white;
+ color: ${vscForeground};
border-radius: 4px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
@@ -27,7 +27,7 @@ const Button = styled.button`
padding: 0.5rem;
border: 1px solid white;
background-color: ${vscBackground};
- color: white;
+ color: ${vscForeground};
border-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
@@ -35,8 +35,8 @@ const Button = styled.button`
cursor: pointer;
&:hover {
- background-color: white;
- color: black;
+ background-color: ${vscForeground};
+ color: ${vscBackground};
}
`;
diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx
index 7772a25e..231c1e93 100644
--- a/extension/react-app/src/components/Onboarding.tsx
+++ b/extension/react-app/src/components/Onboarding.tsx
@@ -22,22 +22,17 @@ const StyledSpan = styled.span`
&:hover {
background-color: #ffffff33;
}
+ white-space: nowrap;
`;
const Onboarding = () => {
const [counter, setCounter] = useState(4);
- const gifs = ["intro", "explain", "edit", "generate", "intro"];
+ const gifs = ["intro", "highlight", "question", "help"];
const topMessages = [
- "Welcome to Continue!",
- "Answer coding questions",
- "Edit in natural language",
- "Generate files from scratch",
- ];
- const bottomMessages = [
- "",
- "Ask Continue about a part of your code to get another perspective",
- "Highlight a section of code and instruct Continue to refactor it",
- "Let Continue build the scaffolding of Python scripts, React components, and more",
+ "Welcome!",
+ "Highlight code",
+ "Ask a question",
+ "Use /help to learn more",
];
useEffect(() => {
@@ -107,7 +102,6 @@ const Onboarding = () => {
/>
)}
</div>
- <p>{bottomMessages[counter]}</p>
<p
style={{
paddingLeft: "50px",
@@ -115,6 +109,7 @@ const Onboarding = () => {
paddingBottom: "50px",
textAlign: "center",
cursor: "pointer",
+ whiteSpace: "nowrap",
}}
>
<StyledSpan
diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx
index 31d98c0f..5929d06a 100644
--- a/extension/react-app/src/components/PillButton.tsx
+++ b/extension/react-app/src/components/PillButton.tsx
@@ -3,15 +3,20 @@ import styled from "styled-components";
import {
StyledTooltip,
defaultBorderRadius,
- lightGray,
secondaryDark,
+ vscBackground,
+ vscForeground,
} from ".";
-import { Trash, PaintBrush, MapPin } from "@styled-icons/heroicons-outline";
+import {
+ Trash,
+ PaintBrush,
+ ExclamationTriangle,
+} from "@styled-icons/heroicons-outline";
import { GUIClientContext } from "../App";
const Button = styled.button`
border: none;
- color: white;
+ color: ${vscForeground};
background-color: ${secondaryDark};
border-radius: ${defaultBorderRadius};
padding: 8px;
@@ -28,10 +33,8 @@ const GridDiv = styled.div`
height: 100%;
display: grid;
grid-gap: 0;
- grid-template-columns: 1fr 1fr;
align-items: center;
border-radius: ${defaultBorderRadius};
- overflow: hidden;
background-color: ${secondaryDark};
`;
@@ -48,6 +51,21 @@ const ButtonDiv = styled.div<{ backgroundColor: string }>`
}
`;
+const CircleDiv = styled.div`
+ position: absolute;
+ top: -10px;
+ right: -10px;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ background-color: red;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2px;
+`;
+
interface PillButtonProps {
onHover?: (arg0: boolean) => void;
onDelete?: () => void;
@@ -55,6 +73,8 @@ interface PillButtonProps {
index: number;
editing: boolean;
pinned: boolean;
+ warning?: string;
+ onlyShowDelete?: boolean;
}
const PillButton = (props: PillButtonProps) => {
@@ -63,75 +83,103 @@ const PillButton = (props: PillButtonProps) => {
return (
<>
- <Button
- style={{
- position: "relative",
- borderColor: props.editing
- ? "#8800aa"
- : props.pinned
- ? "#ffff0099"
- : "transparent",
- borderWidth: "1px",
- borderStyle: "solid",
- }}
- onMouseEnter={() => {
- setIsHovered(true);
- if (props.onHover) {
- props.onHover(true);
- }
- }}
- onMouseLeave={() => {
- setIsHovered(false);
- if (props.onHover) {
- props.onHover(false);
- }
- }}
- >
- {isHovered && (
- <GridDiv>
- <ButtonDiv
- data-tooltip-id={`edit-${props.index}`}
- backgroundColor={"#8800aa55"}
- onClick={() => {
- client?.setEditingAtIndices([props.index]);
+ <div style={{ position: "relative" }}>
+ <Button
+ style={{
+ position: "relative",
+ borderColor: props.warning
+ ? "red"
+ : props.editing
+ ? "#8800aa"
+ : props.pinned
+ ? "#ffff0099"
+ : "transparent",
+ borderWidth: "1px",
+ borderStyle: "solid",
+ }}
+ onMouseEnter={() => {
+ setIsHovered(true);
+ if (props.onHover) {
+ props.onHover(true);
+ }
+ }}
+ onMouseLeave={() => {
+ setIsHovered(false);
+ if (props.onHover) {
+ props.onHover(false);
+ }
+ }}
+ >
+ {isHovered && (
+ <GridDiv
+ style={{
+ gridTemplateColumns: props.onlyShowDelete ? "1fr" : "1fr 1fr",
+ backgroundColor: vscBackground,
}}
>
- <PaintBrush style={{ margin: "auto" }} width="1.6em"></PaintBrush>
- </ButtonDiv>
+ {props.onlyShowDelete || (
+ <ButtonDiv
+ data-tooltip-id={`edit-${props.index}`}
+ backgroundColor={"#8800aa55"}
+ onClick={() => {
+ client?.setEditingAtIndices([props.index]);
+ }}
+ >
+ <PaintBrush
+ style={{ margin: "auto" }}
+ width="1.6em"
+ ></PaintBrush>
+ </ButtonDiv>
+ )}
- {/* <ButtonDiv
+ {/* <ButtonDiv
data-tooltip-id={`pin-${props.index}`}
backgroundColor={"#ffff0055"}
onClick={() => {
client?.setPinnedAtIndices([props.index]);
}}
- >
+ >
<MapPin style={{ margin: "auto" }} width="1.6em"></MapPin>
</ButtonDiv> */}
- <StyledTooltip id={`pin-${props.index}`}>
- Edit this range
+ <StyledTooltip id={`pin-${props.index}`}>
+ Edit this range
+ </StyledTooltip>
+ <ButtonDiv
+ data-tooltip-id={`delete-${props.index}`}
+ backgroundColor={"#cc000055"}
+ onClick={() => {
+ if (props.onDelete) {
+ props.onDelete();
+ }
+ }}
+ >
+ <Trash style={{ margin: "auto" }} width="1.6em"></Trash>
+ </ButtonDiv>
+ </GridDiv>
+ )}
+ {props.title}
+ </Button>
+ <StyledTooltip id={`edit-${props.index}`}>
+ {props.editing
+ ? "Editing this section (with entire file as context)"
+ : "Edit this section"}
+ </StyledTooltip>
+ <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+ {props.warning && (
+ <>
+ <CircleDiv data-tooltip-id={`circle-div-${props.title}`}>
+ <ExclamationTriangle
+ style={{ margin: "auto" }}
+ width="1.0em"
+ strokeWidth={2}
+ />
+ </CircleDiv>
+ <StyledTooltip id={`circle-div-${props.title}`}>
+ {props.warning}
</StyledTooltip>
- <ButtonDiv
- data-tooltip-id={`delete-${props.index}`}
- backgroundColor={"#cc000055"}
- onClick={() => {
- if (props.onDelete) {
- props.onDelete();
- }
- }}
- >
- <Trash style={{ margin: "auto" }} width="1.6em"></Trash>
- </ButtonDiv>
- </GridDiv>
+ </>
)}
- {props.title}
- </Button>
- <StyledTooltip id={`edit-${props.index}`}>
- {props.editing
- ? "Editing this range (with rest of file as context)"
- : "Edit this range"}
- </StyledTooltip>
- <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+ </div>
</>
);
};
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index d1a8a46a..bc8665fd 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useRef, useState } from "react";
+import { useContext, useEffect, useRef, useState } from "react";
import styled, { keyframes } from "styled-components";
import {
appear,
@@ -6,18 +6,21 @@ import {
secondaryDark,
vscBackground,
vscBackgroundTransparent,
+ vscForeground,
} from ".";
import {
ChevronDown,
ChevronRight,
ArrowPath,
XMark,
+ MagnifyingGlass,
} from "@styled-icons/heroicons-outline";
import { StopCircle } from "@styled-icons/heroicons-solid";
import { HistoryNode } from "../../../schema/HistoryNode";
-import ReactMarkdown from "react-markdown";
import HeaderButtonWithText from "./HeaderButtonWithText";
-import CodeBlock from "./CodeBlock";
+import MarkdownPreview from "@uiw/react-markdown-preview";
+import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util";
+import { GUIClientContext } from "../App";
interface StepContainerProps {
historyNode: HistoryNode;
@@ -31,6 +34,7 @@ interface StepContainerProps {
onToggle: () => void;
isFirst: boolean;
isLast: boolean;
+ index: number;
}
// #region styled components
@@ -38,7 +42,6 @@ interface StepContainerProps {
const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>`
opacity: ${(props) => (props.inFuture ? 0.3 : 1)};
animation: ${appear} 0.3s ease-in-out;
- /* padding-left: ${(props) => props.stepDepth * 20}px; */
overflow: hidden;
margin-left: 0px;
margin-right: 0px;
@@ -52,12 +55,7 @@ const StepContainerDiv = styled.div<{ open: boolean }>`
`;
const HeaderDiv = styled.div<{ error: boolean; loading: boolean }>`
- background-color: ${(props) =>
- props.error
- ? "#522"
- : props.loading
- ? vscBackgroundTransparent
- : vscBackground};
+ background-color: ${(props) => (props.error ? "#522" : vscBackground)};
display: grid;
grid-template-columns: 1fr auto auto;
grid-gap: 8px;
@@ -72,19 +70,6 @@ const ContentDiv = styled.div<{ isUserInput: boolean }>`
font-size: 13px;
`;
-const MarkdownPre = styled.pre`
- background-color: ${secondaryDark};
- padding: 10px;
- border-radius: ${defaultBorderRadius};
- border: 0.5px solid white;
-`;
-
-const StyledCode = styled.code`
- word-wrap: break-word;
- color: #f69292;
- background: transparent;
-`;
-
const gradient = keyframes`
0% {
background-position: 0px 0;
@@ -124,6 +109,33 @@ const GradientBorder = styled.div<{
background-size: 200% 200%;
`;
+const StyledMarkdownPreview = styled(MarkdownPreview)`
+ pre {
+ background-color: ${secondaryDark};
+ padding: 1px;
+ border-radius: ${defaultBorderRadius};
+ border: 0.5px solid white;
+ }
+
+ code {
+ color: #f78383;
+ word-wrap: break-word;
+ border-radius: ${defaultBorderRadius};
+ background-color: ${secondaryDark};
+ }
+
+ pre > code {
+ background-color: ${secondaryDark};
+ color: ${vscForeground};
+ }
+
+ background-color: ${vscBackground};
+ font-family: "Lexend", sans-serif;
+ font-size: 13px;
+ padding: 8px;
+ color: ${vscForeground};
+`;
+
// #endregion
function StepContainer(props: StepContainerProps) {
@@ -131,6 +143,7 @@ function StepContainer(props: StepContainerProps) {
const naturalLanguageInputRef = useRef<HTMLTextAreaElement>(null);
const userInputRef = useRef<HTMLInputElement>(null);
const isUserInput = props.historyNode.step.name === "UserInputStep";
+ const client = useContext(GUIClientContext);
useEffect(() => {
if (userInputRef?.current) {
@@ -158,7 +171,7 @@ function StepContainer(props: StepContainerProps) {
>
<StepContainerDiv open={props.open}>
<GradientBorder
- loading={props.historyNode.active as boolean || false}
+ loading={(props.historyNode.active as boolean) || false}
isFirst={props.isFirst}
isLast={props.isLast}
borderColor={
@@ -170,7 +183,7 @@ function StepContainer(props: StepContainerProps) {
}
className="overflow-hidden cursor-pointer"
onClick={(e) => {
- if (e.metaKey) {
+ if (isMetaEquivalentKeyPressed(e)) {
props.onToggleAll();
} else {
props.onToggle();
@@ -178,7 +191,7 @@ function StepContainer(props: StepContainerProps) {
}}
>
<HeaderDiv
- loading={props.historyNode.active as boolean || false}
+ loading={(props.historyNode.active as boolean) || false}
error={props.historyNode.observation?.error ? true : false}
>
<div className="m-2">
@@ -201,12 +214,27 @@ function StepContainer(props: StepContainerProps) {
</HeaderButton> */}
<>
+ {(props.historyNode.logs as any)?.length > 0 && (
+ <HeaderButtonWithText
+ text="Logs"
+ onClick={(e) => {
+ e.stopPropagation();
+ client?.showLogsAtIndex(props.index);
+ }}
+ >
+ <MagnifyingGlass size="1.4em" />
+ </HeaderButtonWithText>
+ )}
<HeaderButtonWithText
onClick={(e) => {
e.stopPropagation();
props.onDelete();
}}
- text={props.historyNode.active ? "Stop (⌘⌫)" : "Delete"}
+ text={
+ props.historyNode.active
+ ? `Stop (${getMetaKeyLabel()}⌫)`
+ : "Delete"
+ }
>
{props.historyNode.active ? (
<StopCircle size="1.6em" onClick={props.onDelete} />
@@ -242,31 +270,19 @@ function StepContainer(props: StepContainerProps) {
)}
{props.historyNode.observation?.error ? (
- <pre className="overflow-x-scroll">
- {props.historyNode.observation.error as string}
- </pre>
+ <details>
+ <summary>View Traceback</summary>
+ <pre className="overflow-x-scroll">
+ {props.historyNode.observation.error as string}
+ </pre>
+ </details>
) : (
- <ReactMarkdown
- key={1}
- className="overflow-x-scroll"
- components={{
- pre: ({ node, ...props }) => {
- return (
- <CodeBlock
- children={(props.children[0] as any).props.children[0]}
- />
- );
- },
- code: ({ node, ...props }) => {
- return <StyledCode children={props.children[0] as any} />;
- },
- ul: ({ node, ...props }) => {
- return <ul className="ml-0" {...props} />;
- },
+ <StyledMarkdownPreview
+ source={props.historyNode.step.description || ""}
+ wrapperElement={{
+ "data-color-mode": "dark",
}}
- >
- {props.historyNode.step.description as any}
- </ReactMarkdown>
+ />
)}
</ContentDiv>
</StepContainerDiv>
diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx
index ea5727f0..9597b578 100644
--- a/extension/react-app/src/components/TextDialog.tsx
+++ b/extension/react-app/src/components/TextDialog.tsx
@@ -1,7 +1,9 @@
// Write a component that displays a dialog box with a text field and a button.
import React, { useEffect, useState } from "react";
import styled from "styled-components";
-import { Button, buttonColor, secondaryDark, vscBackground } from ".";
+import { Button, secondaryDark, vscBackground, vscForeground } from ".";
+import { isMetaEquivalentKeyPressed } from "../util";
+import { ReactMarkdown } from "react-markdown/lib/react-markdown";
const ScreenCover = styled.div`
position: absolute;
@@ -20,13 +22,13 @@ const DialogContainer = styled.div`
`;
const Dialog = styled.div`
- background-color: white;
+ color: ${vscForeground};
+ background-color: ${vscBackground};
border-radius: 8px;
padding: 8px;
display: flex;
flex-direction: column;
- /* box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.5); */
- border: 2px solid ${buttonColor};
+ box-shadow: 0 0 10px 0 ${vscForeground};
width: fit-content;
margin: auto;
`;
@@ -37,14 +39,16 @@ const TextArea = styled.textarea`
padding: 8px;
outline: 1px solid black;
resize: none;
+ background-color: ${secondaryDark};
+ color: ${vscForeground};
&:focus {
- outline: 1px solid ${buttonColor};
+ outline: 1px solid ${vscForeground};
}
`;
const P = styled.p`
- color: black;
+ color: ${vscForeground};
margin: 8px auto;
`;
@@ -53,6 +57,7 @@ const TextDialog = (props: {
onEnter: (text: string) => void;
onClose: () => void;
message?: string;
+ entryOn?: boolean;
}) => {
const [text, setText] = useState("");
const textAreaRef = React.createRef<HTMLTextAreaElement>();
@@ -76,29 +81,37 @@ const TextDialog = (props: {
}}
>
<Dialog>
- <P>{props.message || ""}</P>
- <TextArea
- rows={10}
- ref={textAreaRef}
- onKeyDown={(e) => {
- if (e.key === "Enter" && e.metaKey && textAreaRef.current) {
- props.onEnter(textAreaRef.current.value);
- setText("");
- } else if (e.key === "Escape") {
- props.onClose();
- }
- }}
- />
- <Button
- onClick={() => {
- if (textAreaRef.current) {
- props.onEnter(textAreaRef.current.value);
- setText("");
- }
- }}
- >
- Enter
- </Button>
+ <ReactMarkdown>{props.message || ""}</ReactMarkdown>
+ {props.entryOn && (
+ <>
+ <TextArea
+ rows={10}
+ ref={textAreaRef}
+ onKeyDown={(e) => {
+ if (
+ e.key === "Enter" &&
+ isMetaEquivalentKeyPressed(e) &&
+ textAreaRef.current
+ ) {
+ props.onEnter(textAreaRef.current.value);
+ setText("");
+ } else if (e.key === "Escape") {
+ props.onClose();
+ }
+ }}
+ />
+ <Button
+ onClick={() => {
+ if (textAreaRef.current) {
+ props.onEnter(textAreaRef.current.value);
+ setText("");
+ }
+ }}
+ >
+ Enter
+ </Button>
+ </>
+ )}
</Dialog>
</DialogContainer>
</ScreenCover>
diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts
index 9ae0f097..cb5e7915 100644
--- a/extension/react-app/src/components/index.ts
+++ b/extension/react-app/src/components/index.ts
@@ -3,12 +3,16 @@ import styled, { keyframes } from "styled-components";
export const defaultBorderRadius = "5px";
export const lightGray = "rgb(100 100 100)";
-export const secondaryDark = "rgb(45 45 45)";
-export const vscBackground = "rgb(30 30 30)";
+// export const secondaryDark = "rgb(45 45 45)";
+// export const vscBackground = "rgb(30 30 30)";
export const vscBackgroundTransparent = "#1e1e1ede";
export const buttonColor = "rgb(113 28 59)";
export const buttonColorHover = "rgb(113 28 59 0.67)";
+export const secondaryDark = "var(--vscode-textBlockQuote-background)";
+export const vscBackground = "var(--vscode-editor-background)";
+export const vscForeground = "var(--vscode-editor-foreground)";
+
export const Button = styled.button`
padding: 10px 12px;
margin: 8px 0;
@@ -46,8 +50,8 @@ export const TextArea = styled.textarea`
resize: vertical;
padding: 4px;
- caret-color: white;
- color: white;
+ caret-color: ${vscForeground};
+ color: #{vscForeground};
&:focus {
outline: 1px solid ${buttonColor};
@@ -120,7 +124,7 @@ export const MainTextInput = styled.textarea`
border: 1px solid #ccc;
margin: 8px 8px;
background-color: ${vscBackground};
- color: white;
+ color: ${vscForeground};
outline: 1px solid orange;
resize: none;
`;
@@ -137,8 +141,9 @@ export const appear = keyframes`
`;
export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`
- background-color: ${({ inverted }) => (inverted ? "white" : "transparent")};
- color: ${({ inverted }) => (inverted ? "black" : "white")};
+ background-color: ${({ inverted }) =>
+ inverted ? vscForeground : "transparent"};
+ color: ${({ inverted }) => (inverted ? vscBackground : vscForeground)};
border: none;
border-radius: ${defaultBorderRadius};
@@ -146,7 +151,9 @@ export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`
&:hover {
background-color: ${({ inverted }) =>
- typeof inverted === "undefined" || inverted ? lightGray : "transparent"};
+ typeof inverted === "undefined" || inverted
+ ? secondaryDark
+ : "transparent"};
}
display: flex;
align-items: center;
diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
new file mode 100644
index 00000000..6c0df8fc
--- /dev/null
+++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
@@ -0,0 +1,35 @@
+abstract class AbstractContinueGUIClientProtocol {
+ abstract sendMainInput(input: string): void;
+
+ abstract reverseToIndex(index: number): void;
+
+ abstract sendRefinementInput(input: string, index: number): void;
+
+ abstract sendStepUserInput(input: string, index: number): void;
+
+ abstract onStateUpdate(state: any): void;
+
+ abstract onAvailableSlashCommands(
+ callback: (commands: { name: string; description: string }[]) => void
+ ): void;
+
+ abstract changeDefaultModel(model: string): void;
+
+ abstract sendClear(): void;
+
+ abstract retryAtIndex(index: number): void;
+
+ abstract deleteAtIndex(index: number): void;
+
+ abstract deleteContextAtIndices(indices: number[]): void;
+
+ abstract setEditingAtIndices(indices: number[]): void;
+
+ abstract setPinnedAtIndices(indices: number[]): void;
+
+ abstract toggleAddingHighlightedCode(): void;
+
+ abstract showLogsAtIndex(index: number): void;
+}
+
+export default AbstractContinueGUIClientProtocol;
diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
index a179c2bf..7d6c2a71 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -1,33 +1,92 @@
-abstract class AbstractContinueGUIClientProtocol {
- abstract sendMainInput(input: string): void;
+import AbstractContinueGUIClientProtocol from "./AbstractContinueGUIClientProtocol";
+import { Messenger, WebsocketMessenger } from "./messenger";
+import { VscodeMessenger } from "./vscodeMessenger";
- abstract reverseToIndex(index: number): void;
+class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
+ messenger: Messenger;
+ // Server URL must contain the session ID param
+ serverUrlWithSessionId: string;
- abstract sendRefinementInput(input: string, index: number): void;
+ constructor(
+ serverUrlWithSessionId: string,
+ useVscodeMessagePassing: boolean
+ ) {
+ super();
+ this.serverUrlWithSessionId = serverUrlWithSessionId;
+ this.messenger = useVscodeMessagePassing
+ ? new VscodeMessenger(serverUrlWithSessionId)
+ : new WebsocketMessenger(serverUrlWithSessionId);
+ }
- abstract sendStepUserInput(input: string, index: number): void;
+ sendMainInput(input: string) {
+ this.messenger.send("main_input", { input });
+ }
- abstract onStateUpdate(state: any): void;
+ reverseToIndex(index: number) {
+ this.messenger.send("reverse_to_index", { index });
+ }
- abstract onAvailableSlashCommands(
+ sendRefinementInput(input: string, index: number) {
+ this.messenger.send("refinement_input", { input, index });
+ }
+
+ sendStepUserInput(input: string, index: number) {
+ this.messenger.send("step_user_input", { input, index });
+ }
+
+ onStateUpdate(callback: (state: any) => void) {
+ this.messenger.onMessageType("state_update", (data: any) => {
+ if (data.state) {
+ callback(data.state);
+ }
+ });
+ }
+
+ onAvailableSlashCommands(
callback: (commands: { name: string; description: string }[]) => void
- ): void;
+ ) {
+ this.messenger.onMessageType("available_slash_commands", (data: any) => {
+ if (data.commands) {
+ callback(data.commands);
+ }
+ });
+ }
+
+ changeDefaultModel(model: string) {
+ this.messenger.send("change_default_model", { model });
+ }
- abstract changeDefaultModel(model: string): void;
+ sendClear() {
+ this.messenger.send("clear_history", {});
+ }
- abstract sendClear(): void;
+ retryAtIndex(index: number) {
+ this.messenger.send("retry_at_index", { index });
+ }
- abstract retryAtIndex(index: number): void;
+ deleteAtIndex(index: number) {
+ this.messenger.send("delete_at_index", { index });
+ }
- abstract deleteAtIndex(index: number): void;
+ deleteContextAtIndices(indices: number[]) {
+ this.messenger.send("delete_context_at_indices", { indices });
+ }
- abstract deleteContextAtIndices(indices: number[]): void;
+ setEditingAtIndices(indices: number[]) {
+ this.messenger.send("set_editing_at_indices", { indices });
+ }
- abstract setEditingAtIndices(indices: number[]): void;
+ setPinnedAtIndices(indices: number[]) {
+ this.messenger.send("set_pinned_at_indices", { indices });
+ }
- abstract setPinnedAtIndices(indices: number[]): void;
+ toggleAddingHighlightedCode(): void {
+ this.messenger.send("toggle_adding_highlighted_code", {});
+ }
- abstract toggleAddingHighlightedCode(): void;
+ showLogsAtIndex(index: number): void {
+ this.messenger.send("show_logs_at_index", { index });
+ }
}
-export default AbstractContinueGUIClientProtocol;
+export default ContinueGUIClientProtocol;
diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts
deleted file mode 100644
index 2060dd7f..00000000
--- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import AbstractContinueGUIClientProtocol from "./ContinueGUIClientProtocol";
-// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger";
-import { Messenger, WebsocketMessenger } from "./messenger";
-import { VscodeMessenger } from "./vscodeMessenger";
-
-class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
- messenger: Messenger;
- // Server URL must contain the session ID param
- serverUrlWithSessionId: string;
-
- constructor(
- serverUrlWithSessionId: string,
- useVscodeMessagePassing: boolean
- ) {
- super();
- this.serverUrlWithSessionId = serverUrlWithSessionId;
- if (useVscodeMessagePassing) {
- this.messenger = new VscodeMessenger(serverUrlWithSessionId);
- } else {
- this.messenger = new WebsocketMessenger(serverUrlWithSessionId);
- }
- }
-
- sendMainInput(input: string) {
- this.messenger.send("main_input", { input });
- }
-
- reverseToIndex(index: number) {
- this.messenger.send("reverse_to_index", { index });
- }
-
- sendRefinementInput(input: string, index: number) {
- this.messenger.send("refinement_input", { input, index });
- }
-
- sendStepUserInput(input: string, index: number) {
- this.messenger.send("step_user_input", { input, index });
- }
-
- onStateUpdate(callback: (state: any) => void) {
- this.messenger.onMessageType("state_update", (data: any) => {
- if (data.state) {
- callback(data.state);
- }
- });
- }
-
- onAvailableSlashCommands(
- callback: (commands: { name: string; description: string }[]) => void
- ) {
- this.messenger.onMessageType("available_slash_commands", (data: any) => {
- if (data.commands) {
- callback(data.commands);
- }
- });
- }
-
- changeDefaultModel(model: string) {
- this.messenger.send("change_default_model", { model });
- }
-
- sendClear() {
- this.messenger.send("clear_history", {});
- }
-
- retryAtIndex(index: number) {
- this.messenger.send("retry_at_index", { index });
- }
-
- deleteAtIndex(index: number) {
- this.messenger.send("delete_at_index", { index });
- }
-
- deleteContextAtIndices(indices: number[]) {
- this.messenger.send("delete_context_at_indices", { indices });
- }
-
- setEditingAtIndices(indices: number[]) {
- this.messenger.send("set_editing_at_indices", { indices });
- }
-
- setPinnedAtIndices(indices: number[]) {
- this.messenger.send("set_pinned_at_indices", { indices });
- }
-
- toggleAddingHighlightedCode(): void {
- this.messenger.send("toggle_adding_highlighted_code", {});
- }
-}
-
-export default ContinueGUIClientProtocol;
diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts
index e762666f..6b36be97 100644
--- a/extension/react-app/src/hooks/useWebsocket.ts
+++ b/extension/react-app/src/hooks/useWebsocket.ts
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { RootStore } from "../redux/store";
import { useSelector } from "react-redux";
-import ContinueGUIClientProtocol from "./useContinueGUIProtocol";
+import ContinueGUIClientProtocol from "./ContinueGUIClientProtocol";
import { postVscMessage } from "../vscode";
function useContinueGUIProtocol(useVscodeMessagePassing: boolean = true) {
diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css
index 6e33c89c..bac7fe97 100644
--- a/extension/react-app/src/index.css
+++ b/extension/react-app/src/index.css
@@ -14,13 +14,13 @@ html,
body,
#root {
height: 100%;
- background-color: var(--vsc-background);
+ background-color: var(--vscode-editor-background);
font-family: "Lexend", sans-serif;
}
body {
padding: 0;
- color: white;
+ color: var(--vscode-editor-foreground);
padding: 0px;
margin: 0px;
height: 100%;
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index 4ff260fa..49f41dcf 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -1,5 +1,9 @@
import styled from "styled-components";
-import { defaultBorderRadius } from "../components";
+import {
+ defaultBorderRadius,
+ vscBackground,
+ vscForeground,
+} from "../components";
import Loader from "../components/Loader";
import ContinueButton from "../components/ContinueButton";
import { FullState, HighlightedRangeContext } from "../../../schema/FullState";
@@ -23,6 +27,7 @@ import { RootStore } from "../redux/store";
import { postVscMessage } from "../vscode";
import UserInputContainer from "../components/UserInputContainer";
import Onboarding from "../components/Onboarding";
+import { isMetaEquivalentKeyPressed } from "../util";
const TopGUIDiv = styled.div`
overflow: hidden;
@@ -70,7 +75,6 @@ function GUI(props: GUIProps) {
}
}, [dataSwitchOn]);
- const [usingFastModel, setUsingFastModel] = useState(false);
const [waitingForSteps, setWaitingForSteps] = useState(false);
const [userInputQueue, setUserInputQueue] = useState<string[]>([]);
const [highlightedRanges, setHighlightedRanges] = useState<
@@ -95,11 +99,8 @@ function GUI(props: GUIProps) {
name: "Welcome to Continue",
hide: false,
description: `- Highlight code and ask a question or give instructions
-- Use \`cmd+k\` (Mac) / \`ctrl+k\` (Windows) to open Continue
-- Use \`cmd+shift+e\` / \`ctrl+shift+e\` to open file Explorer
-- Add your own OpenAI API key to VS Code Settings with \`cmd+,\`
-- Use slash commands when you want fine-grained control
-- Past steps are included as part of the context by default`,
+ - Use \`cmd+m\` (Mac) / \`ctrl+m\` (Windows) to open Continue
+ - Use \`/help\` to ask questions about how to use Continue`,
system_message: null,
chat_context: [],
manage_own_chat_context: false,
@@ -115,6 +116,7 @@ function GUI(props: GUIProps) {
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
const [feedbackDialogMessage, setFeedbackDialogMessage] = useState("");
+ const [feedbackEntryOn, setFeedbackEntryOn] = useState(true);
const topGuiDivRef = useRef<HTMLDivElement>(null);
@@ -138,14 +140,11 @@ function GUI(props: GUIProps) {
}, [topGuiDivRef.current, scrollTimeout]);
useEffect(() => {
+ // Cmd + Backspace to delete current step
const listener = (e: any) => {
- // Cmd + i to toggle fast model
- if (e.key === "i" && e.metaKey && e.shiftKey) {
- setUsingFastModel((prev) => !prev);
- // Cmd + backspace to stop currently running step
- } else if (
+ if (
e.key === "Backspace" &&
- e.metaKey &&
+ isMetaEquivalentKeyPressed(e) &&
typeof history?.current_index !== "undefined" &&
history.timeline[history.current_index]?.active
) {
@@ -162,7 +161,6 @@ function GUI(props: GUIProps) {
useEffect(() => {
client?.onStateUpdate((state: FullState) => {
// Scroll only if user is at very bottom of the window.
- setUsingFastModel(state.default_model === "gpt-3.5-turbo");
const shouldScrollToBottom =
topGuiDivRef.current &&
topGuiDivRef.current?.offsetHeight - window.scrollY < 100;
@@ -223,7 +221,7 @@ function GUI(props: GUIProps) {
if (mainTextInputRef.current) {
let input = (mainTextInputRef.current as any).inputValue;
// cmd+enter to /edit
- if (event?.metaKey) {
+ if (isMetaEquivalentKeyPressed(event)) {
input = `/edit ${input}`;
}
(mainTextInputRef.current as any).setInputValue("");
@@ -269,15 +267,18 @@ function GUI(props: GUIProps) {
return (
<>
<Onboarding />
- <TextDialog showDialog={showFeedbackDialog}
- onEnter={(text) => {
- client?.sendMainInput(`/feedback ${text}`);
- setShowFeedbackDialog(false);
- }}
- onClose={() => {
- setShowFeedbackDialog(false);
- }}
- message={feedbackDialogMessage} />
+ <TextDialog
+ showDialog={showFeedbackDialog}
+ onEnter={(text) => {
+ client?.sendMainInput(`/feedback ${text}`);
+ setShowFeedbackDialog(false);
+ }}
+ onClose={() => {
+ setShowFeedbackDialog(false);
+ }}
+ message={feedbackDialogMessage}
+ entryOn={feedbackEntryOn}
+ />
<TopGUIDiv
ref={topGuiDivRef}
@@ -307,6 +308,7 @@ function GUI(props: GUIProps) {
)
) : (
<StepContainer
+ index={index}
isLast={index === history.timeline.length - 1}
isFirst={index === 0}
open={stepsOpen[index]}
@@ -371,12 +373,13 @@ function GUI(props: GUIProps) {
style={{
position: "fixed",
bottom: "50px",
- backgroundColor: "white",
- color: "black",
+ backgroundColor: vscBackground,
+ color: vscForeground,
borderRadius: defaultBorderRadius,
padding: "16px",
margin: "16px",
zIndex: 100,
+ boxShadow: `0px 0px 10px 0px ${vscForeground}`,
}}
hidden={!showDataSharingInfo}
>
@@ -425,24 +428,26 @@ function GUI(props: GUIProps) {
</div>
<HeaderButtonWithText
onClick={() => {
- // client?.changeDefaultModel(
- // usingFastModel ? "gpt-4" : "gpt-3.5-turbo"
- // );
- if (!usingFastModel) {
- // Show the dialog
- setFeedbackDialogMessage(
- "We don't yet support local models, but we're working on it! If privacy is a concern of yours, please write a short note to let us know."
- );
- setShowFeedbackDialog(true);
- }
- setUsingFastModel((prev) => !prev);
+ // Show the dialog
+ setFeedbackDialogMessage(
+ `Continue uses GPT-4 by default, but works with any model. If you'd like to keep your code completely private, there are few options:
+
+Run a local model with ggml: [5 minute quickstart](https://github.com/continuedev/ggml-server-example)
+
+Use Azure OpenAI service, which is GDPR and HIPAA compliant: [Tutorial](https://continue.dev/docs/customization#azure-openai-service)
+
+If you already have an LLM deployed on your own infrastructure, or would like to do so, please contact us at hi@continue.dev.
+ `
+ );
+ setFeedbackEntryOn(false);
+ setShowFeedbackDialog(true);
}}
- text={usingFastModel ? "local" : "gpt-4"}
+ text={"Use Private Model"}
>
<div
style={{ fontSize: "18px", marginLeft: "2px", marginRight: "2px" }}
>
- {usingFastModel ? "🔒" : "🧠"}
+ 🔒
</div>
</HeaderButtonWithText>
<HeaderButtonWithText
@@ -467,6 +472,7 @@ function GUI(props: GUIProps) {
setFeedbackDialogMessage(
"Having trouble using Continue? Want a new feature? Let us know! This box is anonymous, but we will promptly address your feedback."
);
+ setFeedbackEntryOn(true);
setShowFeedbackDialog(true);
}}
text="Feedback"
diff --git a/extension/react-app/src/util/index.ts b/extension/react-app/src/util/index.ts
new file mode 100644
index 00000000..c4168e13
--- /dev/null
+++ b/extension/react-app/src/util/index.ts
@@ -0,0 +1,43 @@
+type Platform = "mac" | "linux" | "windows" | "unknown";
+
+export function getPlatform(): Platform {
+ const platform = window.navigator.platform.toUpperCase();
+ if (platform.indexOf("MAC") >= 0) {
+ return "mac";
+ } else if (platform.indexOf("LINUX") >= 0) {
+ return "linux";
+ } else if (platform.indexOf("WIN") >= 0) {
+ return "windows";
+ } else {
+ return "unknown";
+ }
+}
+
+export function isMetaEquivalentKeyPressed(event: {
+ metaKey: boolean;
+ ctrlKey: boolean;
+}): boolean {
+ const platform = getPlatform();
+ switch (platform) {
+ case "mac":
+ return event.metaKey;
+ case "linux":
+ case "windows":
+ return event.ctrlKey;
+ default:
+ return event.metaKey;
+ }
+}
+
+export function getMetaKeyLabel(): string {
+ const platform = getPlatform();
+ switch (platform) {
+ case "mac":
+ return "⌘";
+ case "linux":
+ case "windows":
+ return "^";
+ default:
+ return "⌘";
+ }
+}
diff --git a/extension/schema/FullState.d.ts b/extension/schema/FullState.d.ts
index abb0832d..1b7b1f3b 100644
--- a/extension/schema/FullState.d.ts
+++ b/extension/schema/FullState.d.ts
@@ -21,6 +21,7 @@ export type ManageOwnChatContext = boolean;
export type Depth = number;
export type Deleted = boolean;
export type Active = boolean;
+export type Logs = string[];
export type Timeline = HistoryNode[];
export type CurrentIndex = number;
export type Active1 = boolean;
@@ -69,6 +70,7 @@ export interface HistoryNode {
depth: Depth;
deleted?: Deleted;
active?: Active;
+ logs?: Logs;
[k: string]: unknown;
}
export interface Step {
diff --git a/extension/schema/History.d.ts b/extension/schema/History.d.ts
index 6eb8ad81..90124f4a 100644
--- a/extension/schema/History.d.ts
+++ b/extension/schema/History.d.ts
@@ -21,6 +21,7 @@ export type ManageOwnChatContext = boolean;
export type Depth = number;
export type Deleted = boolean;
export type Active = boolean;
+export type Logs = string[];
export type Timeline = HistoryNode[];
export type CurrentIndex = number;
@@ -41,6 +42,7 @@ export interface HistoryNode {
depth: Depth;
deleted?: Deleted;
active?: Active;
+ logs?: Logs;
[k: string]: unknown;
}
export interface Step {
diff --git a/extension/schema/HistoryNode.d.ts b/extension/schema/HistoryNode.d.ts
index bc77be89..5ad32061 100644
--- a/extension/schema/HistoryNode.d.ts
+++ b/extension/schema/HistoryNode.d.ts
@@ -21,6 +21,7 @@ export type ManageOwnChatContext = boolean;
export type Depth = number;
export type Deleted = boolean;
export type Active = boolean;
+export type Logs = string[];
/**
* A point in history, a list of which make up History
@@ -31,6 +32,7 @@ export interface HistoryNode1 {
depth: Depth;
deleted?: Deleted;
active?: Active;
+ logs?: Logs;
[k: string]: unknown;
}
export interface Step {
diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts
index b03282e5..0c6e6009 100644
--- a/extension/src/activation/activate.ts
+++ b/extension/src/activation/activate.ts
@@ -1,7 +1,6 @@
import * as vscode from "vscode";
import { registerAllCommands } from "../commands";
import { registerAllCodeLensProviders } from "../lang-server/codeLens";
-import { sendTelemetryEvent, TelemetryEvent } from "../telemetry";
import IdeProtocolClient from "../continueIdeClient";
import { getContinueServerUrl } from "../bridge";
import { ContinueGUIWebviewViewProvider } from "../debugPanel";
@@ -10,6 +9,8 @@ import {
startContinuePythonServer,
} from "./environmentSetup";
import fetch from "node-fetch";
+import registerQuickFixProvider from "../lang-server/codeActions";
+import { get } from "http";
// import { CapturedTerminal } from "../terminal/terminalEmulator";
const PACKAGE_JSON_RAW_GITHUB_URL =
@@ -35,21 +36,40 @@ export async function activateExtension(context: vscode.ExtensionContext) {
})
.catch((e) => console.log("Error checking for extension updates: ", e));
+ // Start the server and display loader if taking > 2 seconds
const sessionIdPromise = (async () => {
- // Start the Python server
- await new Promise((resolve, reject) => {
- vscode.window.withProgress(
- {
- location: vscode.ProgressLocation.Notification,
- title:
- "Starting Continue Server... (it may take a minute to download Python packages)",
- cancellable: false,
- },
- async (progress, token) => {
- await startContinuePythonServer();
- resolve(null);
+ await new Promise((resolve) => {
+ let serverStarted = false;
+
+ // Start the server and set serverStarted to true when done
+ startContinuePythonServer().then(() => {
+ serverStarted = true;
+ resolve(null);
+ });
+
+ // Wait for 2 seconds
+ setTimeout(() => {
+ // If the server hasn't started after 2 seconds, show the notification
+ if (!serverStarted) {
+ vscode.window.withProgress(
+ {
+ location: vscode.ProgressLocation.Notification,
+ title:
+ "Starting Continue Server... (it may take a minute to download Python packages)",
+ cancellable: false,
+ },
+ async (progress, token) => {
+ // Wait for the server to start
+ while (!serverStarted) {
+ await new Promise((innerResolve) =>
+ setTimeout(innerResolve, 1000)
+ );
+ }
+ return Promise.resolve();
+ }
+ );
}
- );
+ }, 2000);
});
// Initialize IDE Protocol Client
@@ -58,11 +78,10 @@ export async function activateExtension(context: vscode.ExtensionContext) {
`${serverUrl.replace("http", "ws")}/ide/ws`,
context
);
-
- return ideProtocolClient.getSessionId();
+ return await ideProtocolClient.getSessionId();
})();
- // Register the webview
+ // Register Continue GUI as sidebar webview, and beging a new session
const provider = new ContinueGUIWebviewViewProvider(sessionIdPromise);
context.subscriptions.push(
@@ -74,9 +93,4 @@ export async function activateExtension(context: vscode.ExtensionContext) {
}
)
);
-
- // Register commands and providers
- sendTelemetryEvent(TelemetryEvent.ExtensionActivated);
- registerAllCodeLensProviders(context);
- registerAllCommands(context);
}
diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts
index 7bd08929..5a9345a6 100644
--- a/extension/src/activation/environmentSetup.ts
+++ b/extension/src/activation/environmentSetup.ts
@@ -9,7 +9,9 @@ import fetch from "node-fetch";
import * as vscode from "vscode";
import * as os from "os";
import fkill from "fkill";
-import { sendTelemetryEvent, TelemetryEvent } from "../telemetry";
+
+const WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR =
+ "A Python virtual enviroment cannot be activated because running scripts is disabled for this user. In order to use Continue, please enable signed scripts to run with this command in PowerShell: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`, reload VS Code, and then try again.";
const MAX_RETRIES = 3;
async function retryThenFail(
@@ -17,17 +19,43 @@ async function retryThenFail(
retries: number = MAX_RETRIES
): Promise<any> {
try {
+ if (retries < MAX_RETRIES && process.platform === "win32") {
+ let [stdout, stderr] = await runCommand("Get-ExecutionPolicy");
+ if (!stdout.includes("RemoteSigned")) {
+ [stdout, stderr] = await runCommand(
+ "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser"
+ );
+ console.log("Execution policy stdout: ", stdout);
+ console.log("Execution policy stderr: ", stderr);
+ }
+ }
+
return await fn();
} catch (e: any) {
if (retries > 0) {
return await retryThenFail(fn, retries - 1);
}
- vscode.window.showInformationMessage(
- "Failed to set up Continue extension. Please email nate@continue.dev and we'll get this fixed ASAP!"
- );
- sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, {
- error: e.message,
- });
+
+ // Show corresponding error message depending on the platform
+ let msg =
+ "Failed to set up Continue extension. Please email hi@continue.dev and we'll get this fixed ASAP!";
+ try {
+ switch (process.platform) {
+ case "win32":
+ msg = WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR;
+ break;
+ case "darwin":
+ break;
+ case "linux":
+ const [pythonCmd] = await getPythonPipCommands();
+ msg = await getLinuxAptInstallError(pythonCmd);
+ break;
+ }
+ } finally {
+ console.log("After retries, failed to set up Continue extension", msg);
+ vscode.window.showErrorMessage(msg);
+ }
+
throw e;
}
}
@@ -51,12 +79,6 @@ async function runCommand(cmd: string): Promise<[string, string | undefined]> {
stdout = "";
}
- if (stderr) {
- sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, {
- error: stderr,
- });
- }
-
return [stdout, stderr];
}
@@ -107,7 +129,7 @@ export async function getPythonPipCommands() {
if (!versionExists) {
vscode.window.showErrorMessage(
- "Continue requires Python3 version 3.8 or greater. Please update your Python3 installation, reload VS Code, and try again."
+ "Continue requires Python version 3.8 or greater. Please update your Python installation, reload VS Code, and try again."
);
throw new Error("Python3.8 or greater is not installed.");
}
@@ -186,16 +208,22 @@ async function checkRequirementsInstalled() {
return fs.existsSync(continuePath);
}
-async function setupPythonEnv() {
- console.log("Setting up python env for Continue extension...");
-
- const [pythonCmd, pipCmd] = await getPythonPipCommands();
- const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands(
- pythonCmd,
- pipCmd
- );
+async function getLinuxAptInstallError(pythonCmd: string) {
+ // First, try to run the command to install python3-venv
+ let [stdout, stderr] = await runCommand(`${pythonCmd} --version`);
+ if (stderr) {
+ await vscode.window.showErrorMessage(
+ "Python3 is not installed. Please install from https://www.python.org/downloads, reload VS Code, and try again."
+ );
+ throw new Error(stderr);
+ }
+ const version = stdout.split(" ")[1].split(".")[1];
+ const installVenvCommand = `apt-get install python3.${version}-venv`;
+ await runCommand("apt-get update");
+ return `[Important] Continue needs to create a Python virtual environment, but python3.${version}-venv is not installed. Please run this command in your terminal: \`${installVenvCommand}\`, reload VS Code, and then try again.`;
+}
- // First, create the virtual environment
+async function createPythonVenv(pythonCmd: string) {
if (checkEnvExists()) {
console.log("Python env already exists, skipping...");
} else {
@@ -210,32 +238,38 @@ async function setupPythonEnv() {
stderr &&
stderr.includes("running scripts is disabled on this system")
) {
- await vscode.window.showErrorMessage(
- "A Python virtual enviroment cannot be activated because running scripts is disabled for this user. Please enable signed scripts to run with this command in PowerShell: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`, reload VS Code, and then try again."
- );
+ console.log("Scripts disabled error when trying to create env");
+ await vscode.window.showErrorMessage(WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR);
throw new Error(stderr);
} else if (
stderr?.includes("On Debian/Ubuntu systems") ||
stdout?.includes("On Debian/Ubuntu systems")
) {
- // First, try to run the command to install python3-venv
- let [stdout, stderr] = await runCommand(`${pythonCmd} --version`);
- if (stderr) {
- await vscode.window.showErrorMessage(
- "Python3 is not installed. Please install from https://www.python.org/downloads, reload VS Code, and try again."
- );
- throw new Error(stderr);
- }
- const version = stdout.split(" ")[1].split(".")[1];
- const installVenvCommand = `apt-get install python3.${version}-venv`;
- await runCommand("apt-get update");
- // Ask the user to run the command to install python3-venv (requires sudo, so we can't)
- // First, get the python version
- const msg = `[Important] Continue needs to create a Python virtual environment, but python3.${version}-venv is not installed. Please run this command in your terminal: \`${installVenvCommand}\`, reload VS Code, and then try again.`;
+ const msg = await getLinuxAptInstallError(pythonCmd);
console.log(msg);
await vscode.window.showErrorMessage(msg);
} else if (checkEnvExists()) {
console.log("Successfully set up python env at ", `${serverPath()}/env`);
+ } else if (
+ stderr?.includes("Permission denied") &&
+ stderr?.includes("python.exe")
+ ) {
+ // This might mean that another window is currently using the python.exe file to install requirements
+ // So we want to wait and try again
+ let i = 0;
+ await new Promise((resolve, reject) =>
+ setInterval(() => {
+ if (i > 5) {
+ reject("Timed out waiting for other window to create env...");
+ }
+ if (checkEnvExists()) {
+ resolve(null);
+ } else {
+ console.log("Waiting for other window to create env...");
+ }
+ i++;
+ }, 5000)
+ );
} else {
const msg = [
"Python environment not successfully created. Trying again. Here was the stdout + stderr: ",
@@ -246,9 +280,22 @@ async function setupPythonEnv() {
throw new Error(msg);
}
}
+}
+
+async function setupPythonEnv() {
+ console.log("Setting up python env for Continue extension...");
+
+ const [pythonCmd, pipCmd] = await getPythonPipCommands();
+ const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands(
+ pythonCmd,
+ pipCmd
+ );
- // Install the requirements
await retryThenFail(async () => {
+ // First, create the virtual environment
+ await createPythonVenv(pythonCmd);
+
+ // Install the requirements
if (await checkRequirementsInstalled()) {
console.log("Python requirements already installed, skipping...");
} else {
@@ -343,6 +390,14 @@ function serverPath(): string {
return sPath;
}
+export function devDataPath(): string {
+ const sPath = path.join(getContinueGlobalPath(), "dev_data");
+ if (!fs.existsSync(sPath)) {
+ fs.mkdirSync(sPath);
+ }
+ return sPath;
+}
+
function serverVersionPath(): string {
return path.join(serverPath(), "server_version.txt");
}
@@ -409,21 +464,17 @@ export async function startContinuePythonServer() {
console.log(`stdout: ${data}`);
if (
data.includes("Uvicorn running on") || // Successfully started the server
- data.includes("address already in use") // The server is already running (probably a simultaneously opened VS Code window)
+ data.includes("only one usage of each socket address") || // [windows] The server is already running (probably a simultaneously opened VS Code window)
+ data.includes("address already in use") // [mac/linux] The server is already running (probably a simultaneously opened VS Code window)
) {
console.log("Successfully started Continue python server");
resolve(null);
} else if (data.includes("ERROR") || data.includes("Traceback")) {
- sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, {
- error: data,
- });
+ console.log("Error starting Continue python server: ", data);
}
});
child.on("error", (error: any) => {
console.log(`error: ${error.message}`);
- sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, {
- error: error.message,
- });
});
// Write the current version of vscode to a file called server_version.txt
diff --git a/extension/src/commands.ts b/extension/src/commands.ts
index 0b002549..1da2f04e 100644
--- a/extension/src/commands.ts
+++ b/extension/src/commands.ts
@@ -16,9 +16,14 @@ import {
import { acceptDiffCommand, rejectDiffCommand } from "./diffs";
import * as bridge from "./bridge";
import { debugPanelWebview } from "./debugPanel";
-import { sendTelemetryEvent, TelemetryEvent } from "./telemetry";
import { ideProtocolClient } from "./activation/activate";
+let focusedOnContinueInput = false;
+
+export const setFocusedOnContinueInput = (value: boolean) => {
+ focusedOnContinueInput = value;
+};
+
// Copy everything over from extension.ts
const commandsMap: { [command: string]: (...args: any) => any } = {
"continue.suggestionDown": suggestionDownCommand,
@@ -29,11 +34,26 @@ const commandsMap: { [command: string]: (...args: any) => any } = {
"continue.rejectDiff": rejectDiffCommand,
"continue.acceptAllSuggestions": acceptAllSuggestionsCommand,
"continue.rejectAllSuggestions": rejectAllSuggestionsCommand,
+ "continue.quickFix": async (message: string, code: string, edit: boolean) => {
+ ideProtocolClient.sendMainUserInput(
+ `${
+ edit ? "/edit " : ""
+ }${code}\n\nHow do I fix this problem in the above code?: ${message}`
+ );
+ if (!edit) {
+ vscode.commands.executeCommand("continue.continueGUIView.focus");
+ }
+ },
"continue.focusContinueInput": async () => {
- vscode.commands.executeCommand("continue.continueGUIView.focus");
- debugPanelWebview?.postMessage({
- type: "focusContinueInput",
- });
+ if (focusedOnContinueInput) {
+ vscode.commands.executeCommand("workbench.action.focusActiveEditorGroup");
+ } else {
+ vscode.commands.executeCommand("continue.continueGUIView.focus");
+ debugPanelWebview?.postMessage({
+ type: "focusContinueInput",
+ });
+ }
+ focusedOnContinueInput = !focusedOnContinueInput;
},
"continue.quickTextEntry": async () => {
const text = await vscode.window.showInputBox({
@@ -53,4 +73,4 @@ export function registerAllCommands(context: vscode.ExtensionContext) {
vscode.commands.registerCommand(command, callback)
);
}
-} \ No newline at end of file
+}
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index 4c1fdf1e..802afc1d 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -15,6 +15,12 @@ import { FileEditWithFullContents } from "../schema/FileEditWithFullContents";
import fs = require("fs");
import { WebsocketMessenger } from "./util/messenger";
import { diffManager } from "./diffs";
+import path = require("path");
+import { registerAllCodeLensProviders } from "./lang-server/codeLens";
+import { registerAllCommands } from "./commands";
+import registerQuickFixProvider from "./lang-server/codeActions";
+
+const continueVirtualDocumentScheme = "continue";
class IdeProtocolClient {
private messenger: WebsocketMessenger | null = null;
@@ -73,6 +79,11 @@ class IdeProtocolClient {
this._serverUrl = serverUrl;
this._newWebsocketMessenger();
+ // Register commands and providers
+ registerAllCodeLensProviders(context);
+ registerAllCommands(context);
+ registerQuickFixProvider();
+
// Setup listeners for any file changes in open editors
// vscode.workspace.onDidChangeTextDocument((event) => {
// if (this._makingEdit === 0) {
@@ -103,8 +114,11 @@ class IdeProtocolClient {
// }
// });
- // Setup listeners for any file changes in open editors
+ // Setup listeners for any selection changes in open editors
vscode.window.onDidChangeTextEditorSelection((event) => {
+ if (this.editorIsTerminal(event.textEditor)) {
+ return;
+ }
if (this._highlightDebounce) {
clearTimeout(this._highlightDebounce);
}
@@ -132,6 +146,41 @@ class IdeProtocolClient {
this.sendHighlightedCode(highlightedCode);
}, 100);
});
+
+ // Register a content provider for the readonly virtual documents
+ const documentContentProvider = new (class
+ implements vscode.TextDocumentContentProvider
+ {
+ // emitter and its event
+ onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
+ onDidChange = this.onDidChangeEmitter.event;
+
+ provideTextDocumentContent(uri: vscode.Uri): string {
+ return uri.query;
+ }
+ })();
+ context.subscriptions.push(
+ vscode.workspace.registerTextDocumentContentProvider(
+ continueVirtualDocumentScheme,
+ documentContentProvider
+ )
+ );
+
+ // Listen for changes to settings.json
+ vscode.workspace.onDidChangeConfiguration((event) => {
+ if (event.affectsConfiguration("continue")) {
+ vscode.window
+ .showInformationMessage(
+ "Please reload VS Code for changes to Continue settings to take effect.",
+ "Reload"
+ )
+ .then((selection) => {
+ if (selection === "Reload") {
+ vscode.commands.executeCommand("workbench.action.reloadWindow");
+ }
+ });
+ }
+ });
}
async handleMessage(
@@ -196,6 +245,9 @@ class IdeProtocolClient {
this.openFile(data.filepath);
// TODO: Close file if False
break;
+ case "showVirtualFile":
+ this.showVirtualFile(data.name, data.contents);
+ break;
case "setSuggestionsLocked":
this.setSuggestionsLocked(data.filepath, data.locked);
break;
@@ -291,6 +343,20 @@ class IdeProtocolClient {
openEditorAndRevealRange(filepath, undefined, vscode.ViewColumn.One);
}
+ showVirtualFile(name: string, contents: string) {
+ vscode.workspace
+ .openTextDocument(
+ vscode.Uri.parse(
+ `${continueVirtualDocumentScheme}:${name}?${encodeURIComponent(
+ contents
+ )}`
+ )
+ )
+ .then((doc) => {
+ vscode.window.showTextDocument(doc, { preview: false });
+ });
+ }
+
setSuggestionsLocked(filepath: string, locked: boolean) {
editorSuggestionsLocked.set(filepath, locked);
// TODO: Rerender?
@@ -350,44 +416,55 @@ class IdeProtocolClient {
// ------------------------------------ //
// Respond to request
+ private editorIsTerminal(editor: vscode.TextEditor) {
+ return (
+ !!path.basename(editor.document.uri.fsPath).match(/\d/) ||
+ (editor.document.languageId === "plaintext" &&
+ editor.document.getText() === "accessible-buffer-accessible-buffer-")
+ );
+ }
+
getOpenFiles(): string[] {
return vscode.window.visibleTextEditors
- .filter((editor) => {
- return !(
- editor.document.uri.fsPath.endsWith("/1") ||
- (editor.document.languageId === "plaintext" &&
- editor.document.getText() ===
- "accessible-buffer-accessible-buffer-")
- );
- })
+ .filter((editor) => !this.editorIsTerminal(editor))
.map((editor) => {
return editor.document.uri.fsPath;
});
}
getVisibleFiles(): string[] {
- return vscode.window.visibleTextEditors.map((editor) => {
- return editor.document.uri.fsPath;
- });
+ return vscode.window.visibleTextEditors
+ .filter((editor) => !this.editorIsTerminal(editor))
+ .map((editor) => {
+ return editor.document.uri.fsPath;
+ });
}
saveFile(filepath: string) {
- vscode.window.visibleTextEditors.forEach((editor) => {
- if (editor.document.uri.fsPath === filepath) {
- editor.document.save();
- }
- });
+ vscode.window.visibleTextEditors
+ .filter((editor) => !this.editorIsTerminal(editor))
+ .forEach((editor) => {
+ if (editor.document.uri.fsPath === filepath) {
+ editor.document.save();
+ }
+ });
}
readFile(filepath: string): string {
let contents: string | undefined;
- vscode.window.visibleTextEditors.forEach((editor) => {
- if (editor.document.uri.fsPath === filepath) {
- contents = editor.document.getText();
+ vscode.window.visibleTextEditors
+ .filter((editor) => !this.editorIsTerminal(editor))
+ .forEach((editor) => {
+ if (editor.document.uri.fsPath === filepath) {
+ contents = editor.document.getText();
+ }
+ });
+ if (typeof contents === "undefined") {
+ if (fs.existsSync(filepath)) {
+ contents = fs.readFileSync(filepath, "utf-8");
+ } else {
+ contents = "";
}
- });
- if (!contents) {
- contents = fs.readFileSync(filepath, "utf-8");
}
return contents;
}
@@ -421,25 +498,27 @@ class IdeProtocolClient {
getHighlightedCode(): RangeInFile[] {
// TODO
let rangeInFiles: RangeInFile[] = [];
- vscode.window.visibleTextEditors.forEach((editor) => {
- editor.selections.forEach((selection) => {
- // if (!selection.isEmpty) {
- rangeInFiles.push({
- filepath: editor.document.uri.fsPath,
- range: {
- start: {
- line: selection.start.line,
- character: selection.start.character,
- },
- end: {
- line: selection.end.line,
- character: selection.end.character,
+ vscode.window.visibleTextEditors
+ .filter((editor) => !this.editorIsTerminal(editor))
+ .forEach((editor) => {
+ editor.selections.forEach((selection) => {
+ // if (!selection.isEmpty) {
+ rangeInFiles.push({
+ filepath: editor.document.uri.fsPath,
+ range: {
+ start: {
+ line: selection.start.line,
+ character: selection.start.character,
+ },
+ end: {
+ line: selection.end.line,
+ character: selection.end.character,
+ },
},
- },
+ });
+ // }
});
- // }
});
- });
return rangeInFiles;
}
diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts
index 5e1689d1..dd24a8d8 100644
--- a/extension/src/debugPanel.ts
+++ b/extension/src/debugPanel.ts
@@ -6,6 +6,7 @@ import {
openEditorAndRevealRange,
} from "./util/vscode";
import { RangeInFile } from "./client";
+import { setFocusedOnContinueInput } from "./commands";
const WebSocket = require("ws");
let websocketConnections: { [url: string]: WebsocketConnection | undefined } =
@@ -226,6 +227,10 @@ export function setupDebugPanel(
openEditorAndRevealRange(data.path, undefined, vscode.ViewColumn.One);
break;
}
+ case "blurContinueInput": {
+ setFocusedOnContinueInput(false);
+ break;
+ }
case "withProgress": {
// This message allows withProgress to be used in the webview
if (data.done) {
diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts
index 28089fc6..1130a06a 100644
--- a/extension/src/diffs.ts
+++ b/extension/src/diffs.ts
@@ -3,21 +3,30 @@ import * as path from "path";
import * as fs from "fs";
import * as vscode from "vscode";
import { extensionContext, ideProtocolClient } from "./activation/activate";
+import { getMetaKeyLabel } from "./util/util";
+import { devDataPath } from "./activation/environmentSetup";
interface DiffInfo {
originalFilepath: string;
newFilepath: string;
editor?: vscode.TextEditor;
step_index: number;
+ range: vscode.Range;
}
-export const DIFF_DIRECTORY = path.join(os.homedir(), ".continue", "diffs");
+export const DIFF_DIRECTORY = path
+ .join(os.homedir(), ".continue", "diffs")
+ .replace(/^C:/, "c:");
class DiffManager {
// Create a temporary file in the global .continue directory which displays the updated version
// Doing this because virtual files are read-only
private diffs: Map<string, DiffInfo> = new Map();
+ diffAtNewFilepath(newFilepath: string): DiffInfo | undefined {
+ return this.diffs.get(newFilepath);
+ }
+
private setupDirectory() {
// Make sure the diff directory exists
if (!fs.existsSync(DIFF_DIRECTORY)) {
@@ -29,12 +38,25 @@ class DiffManager {
constructor() {
this.setupDirectory();
+
+ // Listen for file closes, and if it's a diff file, clean up
+ vscode.workspace.onDidCloseTextDocument((document) => {
+ const newFilepath = document.uri.fsPath;
+ const diffInfo = this.diffs.get(newFilepath);
+ if (diffInfo) {
+ this.cleanUpDiff(diffInfo, false);
+ }
+ });
}
private escapeFilepath(filepath: string): string {
return filepath.replace(/\\/g, "_").replace(/\//g, "_");
}
+ private getNewFilepath(originalFilepath: string): string {
+ return path.join(DIFF_DIRECTORY, this.escapeFilepath(originalFilepath));
+ }
+
private openDiffEditor(
originalFilepath: string,
newFilepath: string
@@ -47,7 +69,7 @@ class DiffManager {
return undefined;
}
- const rightUri = vscode.Uri.parse(newFilepath);
+ const rightUri = vscode.Uri.file(newFilepath);
const leftUri = vscode.Uri.file(originalFilepath);
const title = "Continue Diff";
console.log(
@@ -77,7 +99,7 @@ class DiffManager {
) {
vscode.window
.showInformationMessage(
- "Accept (⌘⇧↩) or reject (⌘⇧⌫) at the top of the file.",
+ `Accept (${getMetaKeyLabel()}⇧↩) or reject (${getMetaKeyLabel()}⇧⌫) at the top of the file.`,
"Got it",
"Don't show again"
)
@@ -95,6 +117,17 @@ class DiffManager {
return editor;
}
+ private _findFirstDifferentLine(contentA: string, contentB: string): number {
+ const linesA = contentA.split("\n");
+ const linesB = contentB.split("\n");
+ for (let i = 0; i < linesA.length && i < linesB.length; i++) {
+ if (linesA[i] !== linesB[i]) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
writeDiff(
originalFilepath: string,
newContent: string,
@@ -103,18 +136,20 @@ class DiffManager {
this.setupDirectory();
// Create or update existing diff
- const newFilepath = path.join(
- DIFF_DIRECTORY,
- this.escapeFilepath(originalFilepath)
- );
+ const newFilepath = this.getNewFilepath(originalFilepath);
fs.writeFileSync(newFilepath, newContent);
// Open the diff editor if this is a new diff
if (!this.diffs.has(newFilepath)) {
+ // Figure out the first line that is different
+ const oldContent = ideProtocolClient.readFile(originalFilepath);
+ const line = this._findFirstDifferentLine(oldContent, newContent);
+
const diffInfo: DiffInfo = {
originalFilepath,
newFilepath,
step_index,
+ range: new vscode.Range(line, 0, line + 1, 0),
};
this.diffs.set(newFilepath, diffInfo);
}
@@ -126,12 +161,17 @@ class DiffManager {
this.diffs.set(newFilepath, diffInfo);
}
+ vscode.commands.executeCommand(
+ "workbench.action.files.revert",
+ vscode.Uri.file(newFilepath)
+ );
+
return newFilepath;
}
- cleanUpDiff(diffInfo: DiffInfo) {
+ cleanUpDiff(diffInfo: DiffInfo, hideEditor: boolean = true) {
// Close the editor, remove the record, delete the file
- if (diffInfo.editor) {
+ if (hideEditor && diffInfo.editor) {
vscode.window.showTextDocument(diffInfo.editor.document);
vscode.commands.executeCommand("workbench.action.closeActiveEditor");
}
@@ -139,10 +179,38 @@ class DiffManager {
fs.unlinkSync(diffInfo.newFilepath);
}
+ private inferNewFilepath() {
+ const activeEditorPath =
+ vscode.window.activeTextEditor?.document.uri.fsPath;
+ if (activeEditorPath && path.dirname(activeEditorPath) === DIFF_DIRECTORY) {
+ return activeEditorPath;
+ }
+ const visibleEditors = vscode.window.visibleTextEditors.map(
+ (editor) => editor.document.uri.fsPath
+ );
+ for (const editorPath of visibleEditors) {
+ if (path.dirname(editorPath) === DIFF_DIRECTORY) {
+ for (const otherEditorPath of visibleEditors) {
+ if (
+ path.dirname(otherEditorPath) !== DIFF_DIRECTORY &&
+ this.getNewFilepath(otherEditorPath) === editorPath
+ ) {
+ return editorPath;
+ }
+ }
+ }
+ }
+
+ if (this.diffs.size === 1) {
+ return Array.from(this.diffs.keys())[0];
+ }
+ return undefined;
+ }
+
acceptDiff(newFilepath?: string) {
- // If no newFilepath is provided and there is only one in the dictionary, use that
- if (!newFilepath && this.diffs.size === 1) {
- newFilepath = Array.from(this.diffs.keys())[0];
+ // When coming from a keyboard shortcut, we have to infer the newFilepath from visible text editors
+ if (!newFilepath) {
+ newFilepath = this.inferNewFilepath();
}
if (!newFilepath) {
console.log("No newFilepath provided to accept the diff");
@@ -166,12 +234,14 @@ class DiffManager {
);
this.cleanUpDiff(diffInfo);
});
+
+ recordAcceptReject(true, diffInfo);
}
rejectDiff(newFilepath?: string) {
// If no newFilepath is provided and there is only one in the dictionary, use that
- if (!newFilepath && this.diffs.size === 1) {
- newFilepath = Array.from(this.diffs.keys())[0];
+ if (!newFilepath) {
+ newFilepath = this.inferNewFilepath();
}
if (!newFilepath) {
console.log(
@@ -195,11 +265,50 @@ class DiffManager {
.then(() => {
this.cleanUpDiff(diffInfo);
});
+
+ recordAcceptReject(false, diffInfo);
}
}
export const diffManager = new DiffManager();
+function recordAcceptReject(accepted: boolean, diffInfo: DiffInfo) {
+ const collectOn = vscode.workspace
+ .getConfiguration("continue")
+ .get<boolean>("dataSwitch");
+
+ if (collectOn) {
+ const devDataDir = devDataPath();
+ const suggestionsPath = path.join(devDataDir, "suggestions.json");
+
+ // Initialize suggestions list
+ let suggestions = [];
+
+ // Check if suggestions.json exists
+ if (fs.existsSync(suggestionsPath)) {
+ const rawData = fs.readFileSync(suggestionsPath, "utf-8");
+ suggestions = JSON.parse(rawData);
+ }
+
+ // Add the new suggestion to the list
+ suggestions.push({
+ accepted,
+ timestamp: Date.now(),
+ suggestion: diffInfo.originalFilepath,
+ });
+
+ // Send the suggestion to the server
+ // ideProtocolClient.sendAcceptRejectSuggestion(accepted);
+
+ // Write the updated suggestions back to the file
+ fs.writeFileSync(
+ suggestionsPath,
+ JSON.stringify(suggestions, null, 4),
+ "utf-8"
+ );
+ }
+}
+
export async function acceptDiffCommand(newFilepath?: string) {
diffManager.acceptDiff(newFilepath);
ideProtocolClient.sendAcceptRejectDiff(true);
diff --git a/extension/src/extension.ts b/extension/src/extension.ts
index 6959ec05..f2e580a1 100644
--- a/extension/src/extension.ts
+++ b/extension/src/extension.ts
@@ -17,15 +17,5 @@ async function dynamicImportAndActivate(context: vscode.ExtensionContext) {
}
export function activate(context: vscode.ExtensionContext) {
- // Only show progress if we have to setup
- vscode.window.withProgress(
- {
- location: vscode.ProgressLocation.Notification,
- title: "Setting up Continue extension...",
- cancellable: false,
- },
- async () => {
- dynamicImportAndActivate(context);
- }
- );
+ dynamicImportAndActivate(context);
}
diff --git a/extension/src/lang-server/codeActions.ts b/extension/src/lang-server/codeActions.ts
new file mode 100644
index 00000000..892c69be
--- /dev/null
+++ b/extension/src/lang-server/codeActions.ts
@@ -0,0 +1,58 @@
+import * as vscode from "vscode";
+
+class ContinueQuickFixProvider implements vscode.CodeActionProvider {
+ public static readonly providedCodeActionKinds = [
+ vscode.CodeActionKind.QuickFix,
+ ];
+
+ provideCodeActions(
+ document: vscode.TextDocument,
+ range: vscode.Range | vscode.Selection,
+ context: vscode.CodeActionContext,
+ token: vscode.CancellationToken
+ ): vscode.ProviderResult<(vscode.Command | vscode.CodeAction)[]> {
+ if (context.diagnostics.length === 0) {
+ return [];
+ }
+
+ const createQuickFix = (edit: boolean) => {
+ const diagnostic = context.diagnostics[0];
+ const quickFix = new vscode.CodeAction(
+ edit ? "Fix with Continue" : "Ask Continue",
+ vscode.CodeActionKind.QuickFix
+ );
+ quickFix.isPreferred = false;
+ const surroundingRange = new vscode.Range(
+ Math.max(0, range.start.line - 3),
+ 0,
+ Math.min(document.lineCount, range.end.line + 3),
+ 0
+ );
+ quickFix.command = {
+ command: "continue.quickFix",
+ title: "Continue Quick Fix",
+ arguments: [
+ diagnostic.message,
+ document.getText(surroundingRange),
+ edit,
+ ],
+ };
+ return quickFix;
+ };
+ return [
+ // createQuickFix(true),
+ createQuickFix(false),
+ ];
+ }
+}
+
+export default function registerQuickFixProvider() {
+ // In your extension's activate function:
+ vscode.languages.registerCodeActionsProvider(
+ { language: "*" },
+ new ContinueQuickFixProvider(),
+ {
+ providedCodeActionKinds: ContinueQuickFixProvider.providedCodeActionKinds,
+ }
+ );
+}
diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts
index 778b98dc..ba80e557 100644
--- a/extension/src/lang-server/codeLens.ts
+++ b/extension/src/lang-server/codeLens.ts
@@ -2,7 +2,8 @@ import * as vscode from "vscode";
import { editorToSuggestions, editorSuggestionsLocked } from "../suggestions";
import * as path from "path";
import * as os from "os";
-import { DIFF_DIRECTORY } from "../diffs";
+import { DIFF_DIRECTORY, diffManager } from "../diffs";
+import { getMetaKeyLabel } from "../util/util";
class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {
public provideCodeLenses(
document: vscode.TextDocument,
@@ -35,7 +36,7 @@ class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {
if (codeLenses.length === 2) {
codeLenses.push(
new vscode.CodeLens(range, {
- title: "(⌘⇧↩/⌘⇧⌫ to accept/reject all)",
+ title: `(${getMetaKeyLabel()}⇧↩/${getMetaKeyLabel()}⇧⌫ to accept/reject all)`,
command: "",
})
);
@@ -53,15 +54,19 @@ class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {
): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {
if (path.dirname(document.uri.fsPath) === DIFF_DIRECTORY) {
const codeLenses: vscode.CodeLens[] = [];
- const range = new vscode.Range(0, 0, 1, 0);
+ let range = new vscode.Range(0, 0, 1, 0);
+ const diffInfo = diffManager.diffAtNewFilepath(document.uri.fsPath);
+ if (diffInfo) {
+ range = diffInfo.range;
+ }
codeLenses.push(
new vscode.CodeLens(range, {
- title: "Accept ✅ (⌘⇧↩)",
+ title: `Accept All ✅ (${getMetaKeyLabel()}⇧↩)`,
command: "continue.acceptDiff",
arguments: [document.uri.fsPath],
}),
new vscode.CodeLens(range, {
- title: "Reject ❌ (⌘⇧⌫)",
+ title: `Reject All ❌ (${getMetaKeyLabel()}⇧⌫)`,
command: "continue.rejectDiff",
arguments: [document.uri.fsPath],
})
diff --git a/extension/src/suggestions.ts b/extension/src/suggestions.ts
index 6e5a444f..5c2b8860 100644
--- a/extension/src/suggestions.ts
+++ b/extension/src/suggestions.ts
@@ -1,9 +1,6 @@
import * as vscode from "vscode";
-import { sendTelemetryEvent, TelemetryEvent } from "./telemetry";
import { openEditorAndRevealRange } from "./util/vscode";
-import { translate, readFileAtRange } from "./util/vscode";
-import * as fs from "fs";
-import * as path from "path";
+import { translate } from "./util/vscode";
import { registerAllCodeLensProviders } from "./lang-server/codeLens";
import { extensionContext, ideProtocolClient } from "./activation/activate";
@@ -214,62 +211,6 @@ function selectSuggestion(
: suggestion.newRange;
}
- let workspaceDir = vscode.workspace.workspaceFolders
- ? vscode.workspace.workspaceFolders[0]?.uri.fsPath
- : undefined;
-
- let collectOn = vscode.workspace
- .getConfiguration("continue")
- .get<boolean>("dataSwitch");
-
- if (workspaceDir && collectOn) {
- let continueDir = path.join(workspaceDir, ".continue");
-
- // Check if .continue directory doesn't exists
- if (!fs.existsSync(continueDir)) {
- fs.mkdirSync(continueDir);
- }
-
- let suggestionsPath = path.join(continueDir, "suggestions.json");
-
- // Initialize suggestions list
- let suggestions = [];
-
- // Check if suggestions.json exists
- if (fs.existsSync(suggestionsPath)) {
- let rawData = fs.readFileSync(suggestionsPath, "utf-8");
- suggestions = JSON.parse(rawData);
- }
-
- const accepted =
- accept === "new" || (accept === "selected" && suggestion.newSelected);
- suggestions.push({
- accepted,
- timestamp: Date.now(),
- suggestion: suggestion.newContent,
- });
- ideProtocolClient.sendAcceptRejectSuggestion(accepted);
-
- // Write the updated suggestions back to the file
- fs.writeFileSync(
- suggestionsPath,
- JSON.stringify(suggestions, null, 4),
- "utf-8"
- );
-
- // If it's not already there, add .continue to .gitignore
- const gitignorePath = path.join(workspaceDir, ".gitignore");
- if (fs.existsSync(gitignorePath)) {
- const gitignoreData = fs.readFileSync(gitignorePath, "utf-8");
- const gitIgnoreLines = gitignoreData.split("\n");
- if (!gitIgnoreLines.includes(".continue")) {
- fs.appendFileSync(gitignorePath, "\n.continue\n");
- }
- } else {
- fs.writeFileSync(gitignorePath, ".continue\n");
- }
- }
-
rangeToDelete = new vscode.Range(
rangeToDelete.start,
new vscode.Position(rangeToDelete.end.line, 0)
@@ -302,7 +243,6 @@ function selectSuggestion(
}
export function acceptSuggestionCommand(key: SuggestionRanges | null = null) {
- sendTelemetryEvent(TelemetryEvent.SuggestionAccepted);
selectSuggestion("selected", key);
}
@@ -329,7 +269,6 @@ export function rejectAllSuggestionsCommand() {
export async function rejectSuggestionCommand(
key: SuggestionRanges | null = null
) {
- sendTelemetryEvent(TelemetryEvent.SuggestionRejected);
selectSuggestion("old", key);
}
diff --git a/extension/src/telemetry.ts b/extension/src/telemetry.ts
deleted file mode 100644
index db5cb8ca..00000000
--- a/extension/src/telemetry.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import * as Segment from "@segment/analytics-node";
-import * as vscode from "vscode";
-
-// Setup Segment
-const SEGMENT_WRITE_KEY = "57yy2uYXH2bwMuy7djm9PorfFlYqbJL1";
-const analytics = new Segment.Analytics({ writeKey: SEGMENT_WRITE_KEY });
-analytics.identify({
- userId: vscode.env.machineId,
- // traits: {
- // name: "Michael Bolton",
- // email: "mbolton@example.com",
- // createdAt: new Date("2014-06-14T02:00:19.467Z"),
- // },
-});
-
-// Enum of telemetry events
-export enum TelemetryEvent {
- // Extension has been activated
- ExtensionActivated = "ExtensionActivated",
- // Suggestion has been accepted
- SuggestionAccepted = "SuggestionAccepted",
- // Suggestion has been rejected
- SuggestionRejected = "SuggestionRejected",
- // Queried universal prompt
- UniversalPromptQuery = "UniversalPromptQuery",
- // `Explain Code` button clicked
- ExplainCode = "ExplainCode",
- // `Generate Ideas` button clicked
- GenerateIdeas = "GenerateIdeas",
- // `Suggest Fix` button clicked
- SuggestFix = "SuggestFix",
- // `Create Test` button clicked
- CreateTest = "CreateTest",
- // `AutoDebug This Test` button clicked
- AutoDebugThisTest = "AutoDebugThisTest",
- // Command run to generate docstring
- GenerateDocstring = "GenerateDocstring",
- // Error setting up the extension
- ExtensionSetupError = "ExtensionSetupError",
-}
-
-export function sendTelemetryEvent(
- event: TelemetryEvent,
- properties?: Record<string, any>
-) {
- if (!vscode.env.isTelemetryEnabled) return;
-
- analytics.track({
- event,
- userId: vscode.env.machineId,
- properties,
- });
-}
diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts
index 7fd71ddd..3044898e 100644
--- a/extension/src/util/messenger.ts
+++ b/extension/src/util/messenger.ts
@@ -15,7 +15,7 @@ export abstract class Messenger {
abstract onOpen(callback: () => void): void;
abstract onClose(callback: () => void): void;
-
+
abstract onError(callback: () => void): void;
abstract sendAndReceive(messageType: string, data: any): Promise<any>;
diff --git a/extension/src/util/util.ts b/extension/src/util/util.ts
index d33593e1..dfc10c90 100644
--- a/extension/src/util/util.ts
+++ b/extension/src/util/util.ts
@@ -1,5 +1,6 @@
import { RangeInFile, SerializedDebugContext } from "../client";
import * as fs from "fs";
+const os = require("os");
function charIsEscapedAtIndex(index: number, str: string): boolean {
if (index === 0) return false;
@@ -113,3 +114,31 @@ export function debounced(delay: number, fn: Function) {
}, delay);
};
}
+
+type Platform = "mac" | "linux" | "windows" | "unknown";
+
+function getPlatform(): Platform {
+ const platform = os.platform();
+ if (platform === "darwin") {
+ return "mac";
+ } else if (platform === "linux") {
+ return "linux";
+ } else if (platform === "win32") {
+ return "windows";
+ } else {
+ return "unknown";
+ }
+}
+
+export function getMetaKeyLabel() {
+ const platform = getPlatform();
+ switch (platform) {
+ case "mac":
+ return "⌘";
+ case "linux":
+ case "windows":
+ return "^";
+ default:
+ return "⌘";
+ }
+}
diff --git a/media/help.gif b/media/help.gif
new file mode 100644
index 00000000..96c24b47
--- /dev/null
+++ b/media/help.gif
Binary files differ
diff --git a/media/highlight.gif b/media/highlight.gif
new file mode 100644
index 00000000..2b77383f
--- /dev/null
+++ b/media/highlight.gif
Binary files differ
diff --git a/media/question.gif b/media/question.gif
new file mode 100644
index 00000000..be3fac3c
--- /dev/null
+++ b/media/question.gif
Binary files differ
diff --git a/schema/json/FullState.json b/schema/json/FullState.json
index 5a7e9d10..62ed337b 100644
--- a/schema/json/FullState.json
+++ b/schema/json/FullState.json
@@ -120,6 +120,14 @@
"title": "Active",
"default": true,
"type": "boolean"
+ },
+ "logs": {
+ "title": "Logs",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
}
},
"required": [
diff --git a/schema/json/History.json b/schema/json/History.json
index ee797412..56415520 100644
--- a/schema/json/History.json
+++ b/schema/json/History.json
@@ -120,6 +120,14 @@
"title": "Active",
"default": true,
"type": "boolean"
+ },
+ "logs": {
+ "title": "Logs",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
}
},
"required": [
diff --git a/schema/json/HistoryNode.json b/schema/json/HistoryNode.json
index d0e12ac5..81e239b3 100644
--- a/schema/json/HistoryNode.json
+++ b/schema/json/HistoryNode.json
@@ -120,6 +120,14 @@
"title": "Active",
"default": true,
"type": "boolean"
+ },
+ "logs": {
+ "title": "Logs",
+ "default": [],
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
}
},
"required": [