summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--continuedev/src/continuedev/core/autopilot.py19
-rw-r--r--continuedev/src/continuedev/core/main.py7
-rw-r--r--continuedev/src/continuedev/core/sdk.py13
-rw-r--r--continuedev/src/continuedev/libs/llm/hugging_face.py11
-rw-r--r--continuedev/src/continuedev/libs/llm/openai.py10
-rw-r--r--continuedev/src/continuedev/libs/llm/proxy_server.py8
-rw-r--r--continuedev/src/continuedev/server/gui.py10
-rw-r--r--continuedev/src/continuedev/server/ide.py6
-rw-r--r--continuedev/src/continuedev/server/ide_protocol.py4
-rw-r--r--continuedev/src/continuedev/steps/chat.py1
-rw-r--r--continuedev/src/continuedev/steps/core/core.py2
-rw-r--r--continuedev/src/continuedev/steps/search_directory.py4
-rw-r--r--extension/package-lock.json4
-rw-r--r--extension/package.json4
-rw-r--r--extension/react-app/src/components/ComboBox.tsx53
-rw-r--r--extension/react-app/src/components/HeaderButtonWithText.tsx2
-rw-r--r--extension/react-app/src/components/PillButton.tsx10
-rw-r--r--extension/react-app/src/components/StepContainer.tsx3
-rw-r--r--extension/react-app/src/components/UserInputContainer.tsx6
-rw-r--r--extension/react-app/src/components/index.ts13
-rw-r--r--extension/react-app/src/hooks/ContinueGUIClientProtocol.ts2
-rw-r--r--extension/react-app/src/hooks/useContinueGUIProtocol.ts4
-rw-r--r--extension/react-app/src/tabs/gui.tsx31
-rw-r--r--extension/src/continueIdeClient.ts67
24 files changed, 214 insertions, 80 deletions
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 5193a02b..313ceded 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -144,9 +144,7 @@ class Autopilot(ContinueBaseModel):
async def handle_highlighted_code(self, range_in_files: List[RangeInFileWithContents]):
workspace_path = self.continue_sdk.ide.workspace_directory
for rif in range_in_files:
- rif.filepath = os.path.relpath(rif.filepath, workspace_path)
- if rif.filepath.startswith(".."):
- rif.filepath = os.path.basename(rif.filepath)
+ rif.filepath = os.path.basename(rif.filepath)
# If current range overlaps with any others, delete them and only keep the new range
new_ranges = []
@@ -156,6 +154,13 @@ class Autopilot(ContinueBaseModel):
if rif.filepath == new_rif.filepath and rif.range.overlaps_with(new_rif.range):
found_overlap = True
break
+
+ # Also don't allow multiple ranges in same file with same content. This is useless to the model, and avoids
+ # the bug where cmd+f causes repeated highlights
+ if rif.filepath == new_rif.filepath and rif.contents == new_rif.contents:
+ found_overlap = True
+ break
+
if not found_overlap:
new_ranges.append(rif)
@@ -173,8 +178,12 @@ class Autopilot(ContinueBaseModel):
self.history.timeline[index].deleted = True
await self.update_subscribers()
- async def delete_context_item_at_index(self, index: int):
- self._highlighted_ranges.pop(index)
+ async def delete_context_at_indices(self, indices: List[int]):
+ kept_ranges = []
+ for i, rif in enumerate(self._highlighted_ranges):
+ if i not in indices:
+ kept_ranges.append(rif)
+ self._highlighted_ranges = kept_ranges
await self.update_subscribers()
async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]:
diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py
index 9a6617f4..8bad09d1 100644
--- a/continuedev/src/continuedev/core/main.py
+++ b/continuedev/src/continuedev/core/main.py
@@ -107,11 +107,9 @@ class HistoryNode(ContinueBaseModel):
return self.step.chat_context
return self.step.chat_context + [
ChatMessage(
- role="function",
+ role="assistant",
name=self.step.__class__.__name__,
- content=json.dumps({
- "description": self.step.description or "Function complete",
- }),
+ content=self.step.description or f"Ran function {self.step.name}",
summary=f"Called function {self.step.name}"
)]
@@ -200,6 +198,7 @@ class SlashCommandDescription(ContinueBaseModel):
name: str
description: str
+
class FullState(ContinueBaseModel):
"""A full state of the program, including the history"""
history: History
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index 988ac6b0..fe975b99 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -97,7 +97,18 @@ class ContinueSDK(AbstractContinueSDK):
async def _ensure_absolute_path(self, path: str) -> str:
if os.path.isabs(path):
return path
- return os.path.join(self.ide.workspace_directory, path)
+
+ # Else if in workspace
+ workspace_path = os.path.join(self.ide.workspace_directory, path)
+ if os.path.exists(workspace_path):
+ return workspace_path
+ else:
+ # Check if it matches any of the open files, then use that absolute path
+ open_files = await self.ide.getOpenFiles()
+ for open_file in open_files:
+ if os.path.basename(open_file) == os.path.basename(path):
+ return open_file
+ raise Exception(f"Path {path} does not exist")
async def run_step(self, step: Step) -> Coroutine[Observation, None, None]:
return await self.__autopilot._run_singular_step(step)
diff --git a/continuedev/src/continuedev/libs/llm/hugging_face.py b/continuedev/src/continuedev/libs/llm/hugging_face.py
index 868cb560..b0db585b 100644
--- a/continuedev/src/continuedev/libs/llm/hugging_face.py
+++ b/continuedev/src/continuedev/libs/llm/hugging_face.py
@@ -1,14 +1,17 @@
from .llm import LLM
from transformers import AutoTokenizer, AutoModelForCausalLM
+
class HuggingFace(LLM):
def __init__(self, model_path: str = "Salesforce/codegen-2B-mono"):
self.model_path = model_path
self.tokenizer = AutoTokenizer.from_pretrained(model_path)
self.model = AutoModelForCausalLM.from_pretrained(model_path)
-
+
def complete(self, prompt: str, **kwargs):
- args = { "max_tokens": 100 } | kwargs
+ args = {"max_tokens": 100}
+ args.update(kwargs)
input_ids = self.tokenizer(prompt, return_tensors="pt").input_ids
- generated_ids = self.model.generate(input_ids, max_length=args["max_tokens"])
- return self.tokenizer.decode(generated_ids[0], skip_special_tokens=True) \ No newline at end of file
+ generated_ids = self.model.generate(
+ input_ids, max_length=args["max_tokens"])
+ return self.tokenizer.decode(generated_ids[0], skip_special_tokens=True)
diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py
index a3ca5c80..c4e4139f 100644
--- a/continuedev/src/continuedev/libs/llm/openai.py
+++ b/continuedev/src/continuedev/libs/llm/openai.py
@@ -24,13 +24,14 @@ class OpenAI(LLM):
@property
def default_args(self):
- return DEFAULT_ARGS | {"model": self.default_model}
+ return {**DEFAULT_ARGS, "model": self.default_model}
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]:
- args = self.default_args | kwargs
+ args = self.default_args.copy()
+ args.update(kwargs)
args["stream"] = True
if args["model"] in CHAT_MODELS:
@@ -48,7 +49,8 @@ class OpenAI(LLM):
yield chunk.choices[0].text
async def stream_chat(self, messages: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
- args = self.default_args | kwargs
+ args = self.default_args.copy()
+ args.update(kwargs)
args["stream"] = True
args["model"] = self.default_model if self.default_model in CHAT_MODELS else "gpt-3.5-turbo-0613"
if not args["model"].endswith("0613") and "functions" in args:
@@ -62,7 +64,7 @@ class OpenAI(LLM):
yield chunk.choices[0].delta
async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
- args = self.default_args | kwargs
+ args = {**self.default_args, **kwargs}
if args["model"] in CHAT_MODELS:
resp = (await openai.ChatCompletion.acreate(
diff --git a/continuedev/src/continuedev/libs/llm/proxy_server.py b/continuedev/src/continuedev/libs/llm/proxy_server.py
index 69c96ee8..05ece394 100644
--- a/continuedev/src/continuedev/libs/llm/proxy_server.py
+++ b/continuedev/src/continuedev/libs/llm/proxy_server.py
@@ -28,13 +28,13 @@ class ProxyServer(LLM):
@property
def default_args(self):
- return DEFAULT_ARGS | {"model": self.default_model}
+ return {**DEFAULT_ARGS, "model": self.default_model}
def count_tokens(self, text: str):
return count_tokens(self.default_model, text)
async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]:
- args = self.default_args | kwargs
+ args = {**self.default_args, **kwargs}
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session:
async with session.post(f"{SERVER_URL}/complete", json={
@@ -48,7 +48,7 @@ class ProxyServer(LLM):
raise Exception(await resp.text())
async def stream_chat(self, messages: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, Generator[Union[Any, List, Dict], None, None]]:
- args = self.default_args | kwargs
+ args = {**self.default_args, **kwargs}
messages = compile_chat_messages(
self.default_model, messages, None, functions=args.get("functions", None))
@@ -72,7 +72,7 @@ class ProxyServer(LLM):
raise Exception(str(line[0]))
async def stream_complete(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
- args = self.default_args | kwargs
+ args = {**self.default_args, **kwargs}
messages = compile_chat_messages(
self.default_model, with_history, prompt, functions=args.get("functions", None))
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index b2f23bac..4e960f7c 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -1,6 +1,6 @@
import json
from fastapi import Depends, Header, WebSocket, APIRouter
-from typing import Any, Type, TypeVar, Union
+from typing import Any, List, Type, TypeVar, Union
from pydantic import BaseModel
from uvicorn.main import Server
@@ -83,8 +83,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
self.on_clear_history()
elif message_type == "delete_at_index":
self.on_delete_at_index(data["index"])
- elif message_type == "delete_context_item_at_index":
- self.on_delete_context_item_at_index(data["index"])
+ elif message_type == "delete_context_at_indices":
+ self.on_delete_context_at_indices(data["indices"])
except Exception as e:
print(e)
@@ -123,9 +123,9 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
def on_delete_at_index(self, index: int):
asyncio.create_task(self.session.autopilot.delete_at_index(index))
- def on_delete_context_item_at_index(self, index: int):
+ def on_delete_context_at_indices(self, indices: List[int]):
asyncio.create_task(
- self.session.autopilot.delete_context_item_at_index(index)
+ self.session.autopilot.delete_context_at_indices(indices)
)
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index e2685493..ea355d3c 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -160,6 +160,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
"edit": file_edit.dict()
})
+ async def showDiff(self, filepath: str, replacement: str):
+ await self._send_json("showDiff", {
+ "filepath": filepath,
+ "replacement": replacement
+ })
+
async def setFileOpen(self, filepath: str, open: bool = True):
# Autopilot needs access to this.
await self._send_json("setFileOpen", {
diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py
index de2eea27..2e1f78d7 100644
--- a/continuedev/src/continuedev/server/ide_protocol.py
+++ b/continuedev/src/continuedev/server/ide_protocol.py
@@ -95,6 +95,10 @@ class AbstractIdeProtocolServer(ABC):
def onHighlightedCodeUpdate(self, range_in_files: List[RangeInFileWithContents]):
"""Called when highlighted code is updated"""
+ @abstractmethod
+ async def showDiff(self, filepath: str, replacement: str):
+ """Show a diff"""
+
@abstractproperty
def workspace_directory(self) -> str:
"""Get the workspace directory"""
diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py
index 49dd98e4..db3f9d7f 100644
--- a/continuedev/src/continuedev/steps/chat.py
+++ b/continuedev/src/continuedev/steps/chat.py
@@ -149,6 +149,7 @@ class ChatWithFunctions(Step):
name: str = "Input"
manage_own_chat_context: bool = True
description: str = ""
+ hide: bool = True
async def run(self, sdk: ContinueSDK):
await sdk.update_ui()
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index 4ad47689..b9f0da35 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -174,7 +174,7 @@ class DefaultModelEditCodeStep(Step):
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)
- return f"`{self.user_input}`\n\n{self._cleanup_output(description)}"
+ return f"{self._cleanup_output(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.
diff --git a/continuedev/src/continuedev/steps/search_directory.py b/continuedev/src/continuedev/steps/search_directory.py
index d2966f46..2eecc99c 100644
--- a/continuedev/src/continuedev/steps/search_directory.py
+++ b/continuedev/src/continuedev/steps/search_directory.py
@@ -1,6 +1,6 @@
import asyncio
from textwrap import dedent
-from typing import List
+from typing import List, Union
from ..models.filesystem import RangeInFile
from ..models.main import Range
@@ -54,7 +54,7 @@ class WriteRegexPatternStep(Step):
class EditAllMatchesStep(Step):
pattern: str
user_request: str
- directory: str | None = None
+ directory: Union[str, None] = None
async def run(self, sdk: ContinueSDK):
# Search all files for a given string
diff --git a/extension/package-lock.json b/extension/package-lock.json
index 4a3e01f2..2dd7d6d4 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "continue",
- "version": "0.0.106",
+ "version": "0.0.108",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "continue",
- "version": "0.0.106",
+ "version": "0.0.108",
"license": "Apache-2.0",
"dependencies": {
"@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index f39e7bd7..52e7f891 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.106",
+ "version": "0.0.108",
"publisher": "Continue",
"engines": {
"vscode": "^1.67.0"
@@ -55,7 +55,7 @@
},
"continue.OPENAI_API_KEY": {
"type": "password",
- "default": "",
+ "default": null,
"description": "The OpenAI API key to use for code generation."
},
"continue.dataSwitch": {
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 742c643b..3e1f3e16 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect } from "react";
+import React, { useCallback, useEffect, useState } from "react";
import { useCombobox } from "downshift";
import styled from "styled-components";
import {
@@ -10,7 +10,10 @@ import {
import CodeBlock from "./CodeBlock";
import { RangeInFile } from "../../../src/client";
import PillButton from "./PillButton";
+import HeaderButtonWithText from "./HeaderButtonWithText";
+import { Trash, LockClosed, LockOpen } from "@styled-icons/heroicons-outline";
+// #region styled components
const mainInputFontSize = 16;
const ContextDropdown = styled.div`
@@ -87,13 +90,16 @@ const Li = styled.li<{
cursor: pointer;
`;
+// #endregion
+
interface ComboBoxProps {
items: { name: string; description: string }[];
onInputValueChange: (inputValue: string) => void;
disabled?: boolean;
- onEnter?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
- highlightedCodeSections?: (RangeInFile & { contents: string })[];
- deleteContextItem?: (idx: number) => void;
+ onEnter: (e: React.KeyboardEvent<HTMLInputElement>) => void;
+ highlightedCodeSections: (RangeInFile & { contents: string })[];
+ deleteContextItems: (indices: number[]) => void;
+ onTogglePin: () => void;
}
const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
@@ -104,6 +110,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
const [hoveringButton, setHoveringButton] = React.useState(false);
const [hoveringContextDropdown, setHoveringContextDropdown] =
React.useState(false);
+ const [pinned, setPinned] = useState(false);
const [highlightedCodeSections, setHighlightedCodeSections] = React.useState(
props.highlightedCodeSections || [
{
@@ -242,12 +249,42 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
</Ul>
</div>
<div className="px-2 flex gap-2 items-center flex-wrap">
+ {highlightedCodeSections.length > 0 && (
+ <>
+ <HeaderButtonWithText
+ text="Clear Context"
+ onClick={() => {
+ props.deleteContextItems(
+ highlightedCodeSections.map((_, idx) => idx)
+ );
+ }}
+ >
+ <Trash size="1.6em" />
+ </HeaderButtonWithText>
+ <HeaderButtonWithText
+ text={pinned ? "Unpin Context" : "Pin Context"}
+ inverted={pinned}
+ onClick={() => {
+ setPinned((prev) => !prev);
+ props.onTogglePin();
+ }}
+ >
+ {pinned ? (
+ <LockClosed size="1.6em"></LockClosed>
+ ) : (
+ <LockOpen size="1.6em"></LockOpen>
+ )}
+ </HeaderButtonWithText>
+ </>
+ )}
{highlightedCodeSections.map((section, idx) => (
<PillButton
- title={section.filepath}
+ title={`${section.filepath} (${section.range.start.line + 1}-${
+ section.range.end.line + 1
+ })`}
onDelete={() => {
- if (props.deleteContextItem) {
- props.deleteContextItem(idx);
+ if (props.deleteContextItems) {
+ props.deleteContextItems([idx]);
}
setHighlightedCodeSections((prev) => {
const newSections = [...prev];
@@ -280,7 +317,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
onMouseLeave={() => {
setHoveringContextDropdown(false);
}}
- hidden={!hoveringContextDropdown && !hoveringButton}
+ hidden={true || (!hoveringContextDropdown && !hoveringButton)}
>
{highlightedCodeSections.map((section, idx) => (
<>
diff --git a/extension/react-app/src/components/HeaderButtonWithText.tsx b/extension/react-app/src/components/HeaderButtonWithText.tsx
index 30931f86..3ddac93c 100644
--- a/extension/react-app/src/components/HeaderButtonWithText.tsx
+++ b/extension/react-app/src/components/HeaderButtonWithText.tsx
@@ -7,12 +7,14 @@ interface HeaderButtonWithTextProps {
onClick?: (e: any) => void;
children: React.ReactNode;
disabled?: boolean;
+ inverted?: boolean;
}
const HeaderButtonWithText = (props: HeaderButtonWithTextProps) => {
const [hover, setHover] = useState(false);
return (
<HeaderButton
+ inverted={props.inverted}
disabled={props.disabled}
style={{ padding: "1px", paddingLeft: hover ? "4px" : "1px" }}
onMouseEnter={() => {
diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx
index 33451db5..2352c3ad 100644
--- a/extension/react-app/src/components/PillButton.tsx
+++ b/extension/react-app/src/components/PillButton.tsx
@@ -1,6 +1,7 @@
import { useState } from "react";
import styled from "styled-components";
import { defaultBorderRadius } from ".";
+import { XMark } from "@styled-icons/heroicons-outline";
const Button = styled.button`
border: none;
@@ -42,22 +43,21 @@ const PillButton = (props: PillButtonProps) => {
<div
style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: "4px" }}
>
- <span>{props.title}</span>
<span
style={{
cursor: "pointer",
color: "red",
- borderLeft: "1px solid black",
- paddingLeft: "4px",
+ borderRight: "1px solid black",
+ paddingRight: "4px",
}}
- hidden={!isHovered}
onClick={() => {
props.onDelete?.();
props.onHover?.(false);
}}
>
- X
+ <XMark style={{ padding: "0px" }} size="1.2em" strokeWidth="2px" />
</span>
+ <span>{props.title}</span>
</div>
</Button>
);
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 492857b5..311f68cf 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -84,7 +84,8 @@ const MarkdownPre = styled.pre`
const StyledCode = styled.code`
word-wrap: break-word;
- color: lightgray;
+ color: #f69292;
+ background: transparent;
`;
const gradient = keyframes`
diff --git a/extension/react-app/src/components/UserInputContainer.tsx b/extension/react-app/src/components/UserInputContainer.tsx
index 59453169..44fdba38 100644
--- a/extension/react-app/src/components/UserInputContainer.tsx
+++ b/extension/react-app/src/components/UserInputContainer.tsx
@@ -6,10 +6,12 @@ import HeaderButtonWithText from "./HeaderButtonWithText";
import { Play, XMark } from "@styled-icons/heroicons-outline";
import { RootStore } from "../redux/store";
import { useSelector } from "react-redux";
+import { HistoryNode } from "../../../schema/HistoryNode";
interface UserInputContainerProps {
onDelete: () => void;
children: string;
+ historyNode: HistoryNode;
}
const StyledDiv = styled.div`
@@ -26,7 +28,7 @@ const StyledDiv = styled.div`
const UserInputContainer = (props: UserInputContainerProps) => {
return (
- <StyledDiv>
+ <StyledDiv hidden={props.historyNode.step.hide as any}>
{props.children}
<div style={{ marginLeft: "auto" }}>
<HeaderButtonWithText
@@ -36,7 +38,7 @@ const UserInputContainer = (props: UserInputContainerProps) => {
}}
text="Delete"
>
- <XMark size="1.6em" onClick={props.onDelete} />
+ <XMark size="1.6em" />
</HeaderButtonWithText>
</div>
</StyledDiv>
diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts
index 429a7df5..db1925ed 100644
--- a/extension/react-app/src/components/index.ts
+++ b/extension/react-app/src/components/index.ts
@@ -124,16 +124,19 @@ export const appear = keyframes`
}
`;
-export const HeaderButton = styled.button`
- background-color: transparent;
+export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`
+ background-color: ${({ inverted }) => (inverted ? "white" : "transparent")};
+ color: ${({ inverted }) => (inverted ? "black" : "white")};
+
border: 1px solid white;
border-radius: ${defaultBorderRadius};
cursor: pointer;
- color: white;
&:hover {
- background-color: white;
- color: black;
+ background-color: ${({ inverted }) =>
+ typeof inverted === "undefined" || inverted ? "white" : "transparent"};
+ color: ${({ inverted }) =>
+ typeof inverted === "undefined" || inverted ? "black" : "white"};
}
display: flex;
align-items: center;
diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
index 228e9a53..96ea7ab3 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -21,7 +21,7 @@ abstract class AbstractContinueGUIClientProtocol {
abstract deleteAtIndex(index: number): void;
- abstract deleteContextItemAtIndex(index: number): void;
+ abstract deleteContextAtIndices(indices: number[]): void;
}
export default AbstractContinueGUIClientProtocol;
diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts
index a0c38c0f..e950387c 100644
--- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts
+++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts
@@ -71,8 +71,8 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
this.messenger.send("delete_at_index", { index });
}
- deleteContextItemAtIndex(index: number) {
- this.messenger.send("delete_context_item_at_index", { index });
+ deleteContextAtIndices(indices: number[]) {
+ this.messenger.send("delete_context_at_indices", { indices });
}
}
diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx
index 40256f86..bbf0b126 100644
--- a/extension/react-app/src/tabs/gui.tsx
+++ b/extension/react-app/src/tabs/gui.tsx
@@ -74,6 +74,7 @@ function GUI(props: GUIProps) {
const [availableSlashCommands, setAvailableSlashCommands] = useState<
{ name: string; description: string }[]
>([]);
+ const [pinned, setPinned] = useState(false);
const [showDataSharingInfo, setShowDataSharingInfo] = useState(false);
const [stepsOpen, setStepsOpen] = useState<boolean[]>([
true,
@@ -87,13 +88,16 @@ function GUI(props: GUIProps) {
step: {
name: "Welcome to Continue",
hide: false,
- description:
- "Highlight code and ask a question or give instructions. Past steps are used as additional context by default. Use slash commands when you want fine-grained control.",
+ 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`,
system_message: null,
chat_context: [],
manage_own_chat_context: false,
- message:
- "Highlight code and ask a question or give instructions. Past steps are used as additional context by default. Use slash commands when you want fine-grained control.",
+ message: "",
},
depth: 0,
deleted: false,
@@ -185,9 +189,9 @@ function GUI(props: GUIProps) {
const mainTextInputRef = useRef<HTMLInputElement>(null);
- const deleteContextItem = useCallback(
- (idx: number) => {
- client?.deleteContextItemAtIndex(idx);
+ const deleteContextItems = useCallback(
+ (indices: number[]) => {
+ client?.deleteContextAtIndices(indices);
},
[client]
);
@@ -241,6 +245,13 @@ function GUI(props: GUIProps) {
setUserInputQueue((queue) => {
return [...queue, input];
});
+
+ // Delete all context items unless locked
+ if (!pinned) {
+ client?.deleteContextAtIndices(
+ highlightedRanges.map((_, index) => index)
+ );
+ }
}
};
@@ -286,6 +297,7 @@ function GUI(props: GUIProps) {
onDelete={() => {
client?.deleteAtIndex(index);
}}
+ historyNode={node}
>
{node.step.description as string}
</UserInputContainer>
@@ -345,7 +357,10 @@ function GUI(props: GUIProps) {
onInputValueChange={() => {}}
items={availableSlashCommands}
highlightedCodeSections={highlightedRanges}
- deleteContextItem={deleteContextItem}
+ deleteContextItems={deleteContextItems}
+ onTogglePin={() => {
+ setPinned((prev: boolean) => !prev);
+ }}
/>
<ContinueButton onClick={onMainTextInput} />
</TopGUIDiv>
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index 999bca88..b9969858 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -159,6 +159,9 @@ class IdeProtocolClient {
case "showSuggestion":
this.showSuggestion(data.edit);
break;
+ case "showDiff":
+ this.showDiff(data.filepath, data.replacement);
+ break;
case "openGUI":
case "connected":
break;
@@ -236,6 +239,42 @@ class IdeProtocolClient {
);
}
+ contentProvider: vscode.Disposable | null = null;
+
+ showDiff(filepath: string, replacement: string) {
+ const myProvider = new (class
+ implements vscode.TextDocumentContentProvider
+ {
+ onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
+ onDidChange = this.onDidChangeEmitter.event;
+ provideTextDocumentContent = (uri: vscode.Uri) => {
+ return replacement;
+ };
+ })();
+ this.contentProvider = vscode.workspace.registerTextDocumentContentProvider(
+ "continueDiff",
+ myProvider
+ );
+
+ // Call the event fire
+ const diffFilename = `continueDiff://${filepath}`;
+ myProvider.onDidChangeEmitter.fire(vscode.Uri.parse(diffFilename));
+
+ const leftUri = vscode.Uri.file(filepath);
+ const rightUri = vscode.Uri.parse(diffFilename);
+ const title = "Continue Diff";
+ vscode.commands
+ .executeCommand("vscode.diff", leftUri, rightUri, title)
+ .then(
+ () => {
+ console.log("Diff view opened successfully");
+ },
+ (error) => {
+ console.error("Error opening diff view:", error);
+ }
+ );
+ }
+
openFile(filepath: string) {
// vscode has a builtin open/get open files
openEditorAndRevealRange(filepath, undefined, vscode.ViewColumn.One);
@@ -371,21 +410,21 @@ class IdeProtocolClient {
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,
- },
+ // 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;