diff options
author | Nate Sesti <33237525+sestinj@users.noreply.github.com> | 2023-07-26 00:56:29 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-26 00:56:29 -0700 |
commit | def8e5612cd4c889a2e26d4152fffcf3d694abdf (patch) | |
tree | ca423625619b9d628651bcc9a395ba8f47fa03a6 /extension | |
parent | b759e2dbfe36b3e8873527b9736d64866da9b604 (diff) | |
parent | d9a4ed993aad36464776c093333af1a310e5a492 (diff) | |
download | sncontinue-def8e5612cd4c889a2e26d4152fffcf3d694abdf.tar.gz sncontinue-def8e5612cd4c889a2e26d4152fffcf3d694abdf.tar.bz2 sncontinue-def8e5612cd4c889a2e26d4152fffcf3d694abdf.zip |
Merge pull request #297 from continuedev/merge-config-py-TO-main
Merge config py to main
Diffstat (limited to 'extension')
26 files changed, 533 insertions, 313 deletions
diff --git a/extension/README.md b/extension/README.md index e431c067..acb9e097 100644 --- a/extension/README.md +++ b/extension/README.md @@ -4,9 +4,10 @@ ## Task, not tab, auto-complete -### Answer coding questions +### Get possible explainations + +Ask Continue about a part of your code to get another perspective -Highlight sections of code and ask Continue for another perspective - “how can I set up a Prisma schema that cascades deletes?” - “where in the page should I be making this request to the backend?” - “how can I communicate between these iframes?” @@ -14,6 +15,7 @@ Highlight sections of code and ask Continue for another perspective ### Edit in natural language Highlight a section of code and instruct Continue to refactor it + - “/edit migrate this digital ocean terraform file into one that works for GCP” - “/edit change this plot into a bar chart in this dashboard component” - “/edit rewrite this function to be async” @@ -21,6 +23,7 @@ Highlight a section of code and instruct Continue to refactor it ### Generate files from scratch Open a blank file and let Continue start new Python scripts, React components, etc. + - “/edit here is a connector for postgres, now write one for kafka” - “/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” @@ -32,6 +35,7 @@ Open a blank file and let Continue start new Python scripts, React components, e 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` diff --git a/extension/media/terminal-continue.png b/extension/media/terminal-continue.png Binary files differnew file mode 100644 index 00000000..ef310fa3 --- /dev/null +++ b/extension/media/terminal-continue.png diff --git a/extension/package-lock.json b/extension/package-lock.json index 28b15e01..e92de305 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.197", + "version": "0.0.201", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.197", + "version": "0.0.201", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 80807d41..2f8b577d 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "continue", - "icon": "media/continue-gradient.png", + "icon": "media/terminal-continue.png", "repository": { "type": "git", "url": "https://github.com/continuedev/continue" @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.197", + "version": "0.0.201", "publisher": "Continue", "engines": { "vscode": "^1.67.0" @@ -35,7 +35,8 @@ "chat" ], "activationEvents": [ - "*" + "onStartupFinished", + "onView:continueGUIView" ], "main": "./out/extension.js", "browser": "./out/extension.js", @@ -157,7 +158,7 @@ { "id": "continue", "title": "Continue ", - "icon": "react-app/dist/continue-dev-square.png" + "icon": "media/continue-dev-square.png" } ] }, @@ -225,7 +226,7 @@ "lint": "eslint src --ext ts", "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", + "package": "mkdir -p ./build && vsce package --out ./build", "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", diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json index 13e02e86..fa7834f1 100644 --- a/extension/react-app/package-lock.json +++ b/extension/react-app/package-lock.json @@ -13,6 +13,7 @@ "@types/vscode-webview": "^1.57.1", "@uiw/react-markdown-preview": "^4.1.13", "downshift": "^7.6.0", + "meilisearch": "^0.33.0", "posthog-js": "^1.58.0", "prismjs": "^1.29.0", "react": "^18.2.0", @@ -1423,6 +1424,14 @@ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -2500,6 +2509,14 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/meilisearch": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.33.0.tgz", + "integrity": "sha512-bYPb9WyITnJfzf92e7QFK8Rc50DmshFWxypXCs3ILlpNh8pT15A7KSu9Xgnnk/K3G/4vb3wkxxtFS4sxNkWB8w==", + "dependencies": { + "cross-fetch": "^3.1.6" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3092,6 +3109,25 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", @@ -4115,6 +4151,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -4423,6 +4464,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -5325,6 +5380,14 @@ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==" }, + "cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "requires": { + "node-fetch": "^2.6.12" + } + }, "css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -6098,6 +6161,14 @@ "@types/mdast": "^3.0.0" } }, + "meilisearch": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.33.0.tgz", + "integrity": "sha512-bYPb9WyITnJfzf92e7QFK8Rc50DmshFWxypXCs3ILlpNh8pT15A7KSu9Xgnnk/K3G/4vb3wkxxtFS4sxNkWB8w==", + "requires": { + "cross-fetch": "^3.1.6" + } + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6434,6 +6505,14 @@ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true }, + "node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", @@ -7129,6 +7208,11 @@ "is-number": "^7.0.0" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -7315,6 +7399,20 @@ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/extension/react-app/package.json b/extension/react-app/package.json index 704f520a..e5f5a329 100644 --- a/extension/react-app/package.json +++ b/extension/react-app/package.json @@ -14,6 +14,7 @@ "@types/vscode-webview": "^1.57.1", "@uiw/react-markdown-preview": "^4.1.13", "downshift": "^7.6.0", + "meilisearch": "^0.33.0", "posthog-js": "^1.58.0", "prismjs": "^1.29.0", "react": "^18.2.0", diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index bf07cb93..a14e2280 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -1,4 +1,9 @@ -import React, { useEffect, useImperativeHandle, useState } from "react"; +import React, { + useContext, + useEffect, + useImperativeHandle, + useState, +} from "react"; import { useCombobox } from "downshift"; import styled from "styled-components"; import { @@ -8,13 +13,20 @@ import { vscBackground, vscForeground, } from "."; -import CodeBlock from "./CodeBlock"; import PillButton from "./PillButton"; import HeaderButtonWithText from "./HeaderButtonWithText"; import { DocumentPlus } from "@styled-icons/heroicons-outline"; -import { HighlightedRangeContext } from "../../../schema/FullState"; +import { ContextItem } from "../../../schema/FullState"; import { postVscMessage } from "../vscode"; -import { getMetaKeyLabel } from "../util"; +import { GUIClientContext } from "../App"; +import { MeiliSearch } from "meilisearch"; +import { + setBottomMessage, + setBottomMessageCloseTimeout, +} from "../redux/slices/uiStateSlice"; +import { useDispatch } from "react-redux"; + +const SEARCH_INDEX_NAME = "continue_context_items"; // #region styled components const mainInputFontSize = 13; @@ -64,6 +76,7 @@ const Ul = styled.ul<{ hidden: boolean; showAbove: boolean; ulHeightPixels: number; + inputBoxHeight?: string; }>` ${(props) => props.showAbove @@ -104,35 +117,79 @@ const Li = styled.li<{ // #endregion interface ComboBoxProps { - items: { name: string; description: string }[]; + items: { name: string; description: string; id?: string }[]; onInputValueChange: (inputValue: string) => void; disabled?: boolean; onEnter: (e: React.KeyboardEvent<HTMLInputElement>) => void; - highlightedCodeSections: HighlightedRangeContext[]; - deleteContextItems: (indices: number[]) => void; - onTogglePin: () => void; + selectedContextItems: ContextItem[]; onToggleAddContext: () => void; addingHighlightedCode: boolean; } const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { + const searchClient = new MeiliSearch({ host: "http://127.0.0.1:7700" }); + const client = useContext(GUIClientContext); + const dispatch = useDispatch(); + const [history, setHistory] = React.useState<string[]>([]); // 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 [highlightedCodeSections, setHighlightedCodeSections] = React.useState( - props.highlightedCodeSections || [] - ); + const inputRef = React.useRef<HTMLInputElement>(null); + const [inputBoxHeight, setInputBoxHeight] = useState<string | undefined>( + undefined + ); - useEffect(() => { - setHighlightedCodeSections(props.highlightedCodeSections || []); - }, [props.highlightedCodeSections]); + // Whether the current input follows an '@' and should be treated as context query + const [currentlyInContextQuery, setCurrentlyInContextQuery] = useState(false); const { getInputProps, ...downshiftProps } = useCombobox({ - onInputValueChange({ inputValue }) { + onSelectedItemChange: ({ selectedItem }) => { + if (selectedItem?.id) { + // Get the query from the input value + const segs = downshiftProps.inputValue.split("@"); + const query = segs[segs.length - 1]; + const restOfInput = segs.splice(0, segs.length - 1).join("@"); + + // Tell server the context item was selected + client?.selectContextItem(selectedItem.id, query); + + // Remove the '@' and the context query from the input + if (downshiftProps.inputValue.includes("@")) { + downshiftProps.setInputValue(restOfInput); + } + } + }, + onInputValueChange({ inputValue, highlightedIndex }) { if (!inputValue) return; props.onInputValueChange(inputValue); + + if (inputValue.endsWith("@") || currentlyInContextQuery) { + setCurrentlyInContextQuery(true); + + const segs = inputValue.split("@"); + const providerAndQuery = segs[segs.length - 1]; + const [provider, query] = providerAndQuery.split(" "); + searchClient + .index(SEARCH_INDEX_NAME) + .search(providerAndQuery) + .then((res) => { + setItems( + res.hits.map((hit) => { + return { + name: hit.name, + description: hit.description, + id: hit.id, + }; + }) + ); + }) + .catch(() => { + // Swallow errors, because this simply is not supported on Windows at the moment + }); + return; + } setItems( props.items.filter((item) => item.name.toLowerCase().startsWith(inputValue.toLowerCase()) @@ -145,6 +202,18 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { }, }); + useEffect(() => { + if (downshiftProps.highlightedIndex < 0) { + downshiftProps.setHighlightedIndex(0); + } + }, [downshiftProps.inputValue]); + + const divRef = React.useRef<HTMLDivElement>(null); + const ulRef = React.useRef<HTMLUListElement>(null); + const showAbove = () => { + return (divRef.current?.getBoundingClientRect().top || 0) > UlMaxHeight; + }; + useImperativeHandle(ref, () => downshiftProps, [downshiftProps]); const [metaKeyPressed, setMetaKeyPressed] = useState(false); @@ -184,59 +253,25 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { }; }, [inputRef.current]); - const divRef = React.useRef<HTMLDivElement>(null); - const ulRef = React.useRef<HTMLUListElement>(null); - const showAbove = () => { - return (divRef.current?.getBoundingClientRect().top || 0) > UlMaxHeight; - }; - return ( <> <div className="px-2 flex gap-2 items-center flex-wrap mt-2"> - {/* {highlightedCodeSections.length > 1 && ( - <> - <HeaderButtonWithText - text="Clear Context" - onClick={() => { - props.deleteContextItems( - highlightedCodeSections.map((_, idx) => idx) - ); - }} - > - <Trash size="1.6em" /> - </HeaderButtonWithText> - </> - )} */} - {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} - key={`${section.display_name}${idx}`} - title={`${section.display_name} (${ - section.range.range.start.line + 1 - }-${section.range.range.end.line + 1})`} - onDelete={() => { - if (props.deleteContextItems) { - props.deleteContextItems([idx]); + {props.selectedContextItems.map((item, idx) => { + return ( + <PillButton + key={`${item.description.id.item_id}${idx}`} + item={item} + warning={ + item.content.length > 4000 && item.editing + ? "Editing such a large range may be slow" + : undefined } - setHighlightedCodeSections((prev) => { - const newSections = [...prev]; - newSections.splice(idx, 1); - return newSections; - }); - }} - /> - ))} - {props.highlightedCodeSections.length > 0 && + addingHighlightedCode={props.addingHighlightedCode} + index={idx} + /> + ); + })} + {props.selectedContextItems.length > 0 && (props.addingHighlightedCode ? ( <EmptyPillDiv onClick={() => { @@ -259,7 +294,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`} + placeholder={`Ask a question, give instructions, type '/' for slash commands, or '@' to add context`} {...getInputProps({ onChange: (e) => { const target = e.target as HTMLTextAreaElement; @@ -269,20 +304,24 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { target.scrollHeight, 300 ).toString()}px`; + setInputBoxHeight(target.style.height); // setShowContextDropdown(target.value.endsWith("@")); }, onFocus: (e) => { setFocused(true); + dispatch(setBottomMessage(undefined)); }, onBlur: (e) => { setFocused(false); postVscMessage("blurContinueInput", {}); }, onKeyDown: (event) => { + dispatch(setBottomMessage(undefined)); if (event.key === "Enter" && event.shiftKey) { // Prevent Downshift's default 'Enter' behavior. (event.nativeEvent as any).preventDownshiftDefault = true; + setCurrentlyInContextQuery(false); } else if ( event.key === "Enter" && (!downshiftProps.isOpen || items.length === 0) @@ -296,6 +335,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { (event.nativeEvent as any).preventDownshiftDefault = true; if (props.onEnter) props.onEnter(event); + setCurrentlyInContextQuery(false); } else if (event.key === "Tab" && items.length > 0) { downshiftProps.setInputValue(items[0].name); event.preventDefault(); @@ -315,6 +355,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { } downshiftProps.setInputValue(history[positionInHistory - 1]); setPositionInHistory((prev) => prev - 1); + setCurrentlyInContextQuery(false); } else if (event.key === "ArrowDown") { if (positionInHistory < history.length) { downshiftProps.setInputValue(history[positionInHistory + 1]); @@ -322,8 +363,12 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { setPositionInHistory((prev) => Math.min(prev + 1, history.length) ); + setCurrentlyInContextQuery(false); } }, + onClick: () => { + dispatch(setBottomMessage(undefined)); + }, ref: inputRef, })} /> @@ -345,13 +390,14 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { selected={downshiftProps.selectedItem === item} > <span> - {item.name}: {item.description} + {item.name}:{" "} + <span style={{ color: lightGray }}>{item.description}</span> </span> </Li> ))} </Ul> </div> - {highlightedCodeSections.length === 0 && + {props.selectedContextItems.length === 0 && (downshiftProps.inputValue?.startsWith("/edit") || (focused && metaKeyPressed && diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx index 231c1e93..6d0e0b28 100644 --- a/extension/react-app/src/components/Onboarding.tsx +++ b/extension/react-app/src/components/Onboarding.tsx @@ -1,5 +1,3 @@ -import { useSelector } from "react-redux"; -import { RootStore } from "../redux/store"; import React, { useState, useEffect } from "react"; import styled from "styled-components"; import { ArrowLeft, ArrowRight } from "@styled-icons/heroicons-outline"; @@ -14,6 +12,8 @@ const StyledDiv = styled.div` height: 100%; background-color: #1e1e1e; z-index: 200; + + color: white; `; const StyledSpan = styled.span` @@ -38,7 +38,7 @@ const Onboarding = () => { useEffect(() => { const hasVisited = localStorage.getItem("hasVisited"); if (hasVisited) { - setCounter(4); + setCounter(0); } else { setCounter(0); localStorage.setItem("hasVisited", "true"); @@ -60,13 +60,6 @@ const Onboarding = () => { alignItems: "center", height: "100%", textAlign: "center", - background: `linear-gradient( - 101.79deg, - #12887a66 0%, - #87245c66 32%, - #e1263766 63%, - #ffb21566 100% - )`, paddingLeft: "16px", paddingRight: "16px", }} diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx index 5929d06a..e3d05711 100644 --- a/extension/react-app/src/components/PillButton.tsx +++ b/extension/react-app/src/components/PillButton.tsx @@ -1,8 +1,9 @@ -import { useContext, useState } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import styled from "styled-components"; import { StyledTooltip, defaultBorderRadius, + lightGray, secondaryDark, vscBackground, vscForeground, @@ -13,6 +14,14 @@ import { ExclamationTriangle, } from "@styled-icons/heroicons-outline"; import { GUIClientContext } from "../App"; +import { useDispatch } from "react-redux"; +import { + setBottomMessage, + setBottomMessageCloseTimeout, +} from "../redux/slices/uiStateSlice"; +import { ContextItem } from "../../../schema/FullState"; +import { ReactMarkdown } from "react-markdown/lib/react-markdown"; +import StyledMarkdownPreview from "./StyledMarkdownPreview"; const Button = styled.button` border: none; @@ -68,19 +77,55 @@ const CircleDiv = styled.div` interface PillButtonProps { onHover?: (arg0: boolean) => void; - onDelete?: () => void; - title: string; - index: number; - editing: boolean; - pinned: boolean; + item: ContextItem; warning?: string; - onlyShowDelete?: boolean; + index: number; + addingHighlightedCode?: boolean; } const PillButton = (props: PillButtonProps) => { const [isHovered, setIsHovered] = useState(false); const client = useContext(GUIClientContext); + const dispatch = useDispatch(); + + useEffect(() => { + if (isHovered) { + dispatch(setBottomMessageCloseTimeout(undefined)); + dispatch( + setBottomMessage( + <> + <b>{props.item.description.name}</b>:{" "} + {props.item.description.description} + <pre> + <code + style={{ + fontSize: "11px", + backgroundColor: vscBackground, + color: vscForeground, + whiteSpace: "pre-wrap", + wordWrap: "break-word", + }} + > + {props.item.content} + </code> + </pre> + </> + ) + ); + } else { + dispatch( + setBottomMessageCloseTimeout( + setTimeout(() => { + if (!isHovered) { + dispatch(setBottomMessage(undefined)); + } + }, 2000) + ) + ); + } + }, [isHovered]); + return ( <> <div style={{ position: "relative" }}> @@ -89,10 +134,8 @@ const PillButton = (props: PillButtonProps) => { position: "relative", borderColor: props.warning ? "red" - : props.editing + : props.item.editing ? "#8800aa" - : props.pinned - ? "#ffff0099" : "transparent", borderWidth: "1px", borderStyle: "solid", @@ -113,11 +156,14 @@ const PillButton = (props: PillButtonProps) => { {isHovered && ( <GridDiv style={{ - gridTemplateColumns: props.onlyShowDelete ? "1fr" : "1fr 1fr", + gridTemplateColumns: + props.item.editable && props.addingHighlightedCode + ? "1fr 1fr" + : "1fr", backgroundColor: vscBackground, }} > - {props.onlyShowDelete || ( + {props.item.editable && props.addingHighlightedCode && ( <ButtonDiv data-tooltip-id={`edit-${props.index}`} backgroundColor={"#8800aa55"} @@ -132,15 +178,6 @@ const PillButton = (props: PillButtonProps) => { </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> @@ -148,33 +185,34 @@ const PillButton = (props: PillButtonProps) => { data-tooltip-id={`delete-${props.index}`} backgroundColor={"#cc000055"} onClick={() => { - if (props.onDelete) { - props.onDelete(); - } + client?.deleteContextWithIds([props.item.description.id]); + dispatch(setBottomMessage(undefined)); }} > <Trash style={{ margin: "auto" }} width="1.6em"></Trash> </ButtonDiv> </GridDiv> )} - {props.title} + {props.item.description.name} </Button> <StyledTooltip id={`edit-${props.index}`}> - {props.editing + {props.item.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}`}> + <CircleDiv + data-tooltip-id={`circle-div-${props.item.description.name}`} + > <ExclamationTriangle style={{ margin: "auto" }} width="1.0em" strokeWidth={2} /> </CircleDiv> - <StyledTooltip id={`circle-div-${props.title}`}> + <StyledTooltip id={`circle-div-${props.item.description.name}`}> {props.warning} </StyledTooltip> </> diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index bc8665fd..2cfe7ecd 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -18,9 +18,9 @@ import { import { StopCircle } from "@styled-icons/heroicons-solid"; import { HistoryNode } from "../../../schema/HistoryNode"; import HeaderButtonWithText from "./HeaderButtonWithText"; -import MarkdownPreview from "@uiw/react-markdown-preview"; import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util"; import { GUIClientContext } from "../App"; +import StyledMarkdownPreview from "./StyledMarkdownPreview"; interface StepContainerProps { historyNode: HistoryNode; @@ -109,33 +109,6 @@ 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) { diff --git a/extension/react-app/src/components/StyledMarkdownPreview.tsx b/extension/react-app/src/components/StyledMarkdownPreview.tsx new file mode 100644 index 00000000..9c2ecb62 --- /dev/null +++ b/extension/react-app/src/components/StyledMarkdownPreview.tsx @@ -0,0 +1,37 @@ +import styled from "styled-components"; +import { + defaultBorderRadius, + secondaryDark, + vscBackground, + vscForeground, +} from "."; +import MarkdownPreview from "@uiw/react-markdown-preview"; + +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}; +`; + +export default StyledMarkdownPreview; diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index 9597b578..7d8e9920 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -6,7 +6,7 @@ import { isMetaEquivalentKeyPressed } from "../util"; import { ReactMarkdown } from "react-markdown/lib/react-markdown"; const ScreenCover = styled.div` - position: absolute; + position: fixed; width: 100%; height: 100%; background-color: rgba(168, 168, 168, 0.5); diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts index 6c0df8fc..8e3735ec 100644 --- a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts @@ -1,3 +1,5 @@ +import { ContextItemId } from "../../../schema/FullState"; + abstract class AbstractContinueGUIClientProtocol { abstract sendMainInput(input: string): void; @@ -13,23 +15,21 @@ abstract class AbstractContinueGUIClientProtocol { 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 deleteContextWithIds(ids: ContextItemId[]): void; abstract setEditingAtIndices(indices: number[]): void; - abstract setPinnedAtIndices(indices: number[]): void; - abstract toggleAddingHighlightedCode(): void; abstract showLogsAtIndex(index: number): void; + + abstract selectContextItem(id: string, query: string): void; } export default AbstractContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index 7d6c2a71..b8019664 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -1,3 +1,4 @@ +import { ContextItemId } from "../../../schema/FullState"; import AbstractContinueGUIClientProtocol from "./AbstractContinueGUIClientProtocol"; import { Messenger, WebsocketMessenger } from "./messenger"; import { VscodeMessenger } from "./vscodeMessenger"; @@ -52,10 +53,6 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { }); } - changeDefaultModel(model: string) { - this.messenger.send("change_default_model", { model }); - } - sendClear() { this.messenger.send("clear_history", {}); } @@ -68,18 +65,16 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { this.messenger.send("delete_at_index", { index }); } - deleteContextAtIndices(indices: number[]) { - this.messenger.send("delete_context_at_indices", { indices }); + deleteContextWithIds(ids: ContextItemId[]) { + this.messenger.send("delete_context_with_ids", { + ids: ids.map((id) => `${id.provider_title}-${id.item_id}`), + }); } 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", {}); } @@ -87,6 +82,10 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { showLogsAtIndex(index: number): void { this.messenger.send("show_logs_at_index", { index }); } + + selectContextItem(id: string, query: string): void { + this.messenger.send("select_context_item", { id, query }); + } } export default ContinueGUIClientProtocol; diff --git a/extension/react-app/src/main.tsx b/extension/react-app/src/main.tsx index e29a7d5f..1776490c 100644 --- a/extension/react-app/src/main.tsx +++ b/extension/react-app/src/main.tsx @@ -8,13 +8,11 @@ import "./index.css"; import posthog from "posthog-js"; import { PostHogProvider } from "posthog-js/react"; +console.log("Starting React"); + posthog.init("phc_JS6XFROuNbhJtVCEdTSYk6gl5ArRrTNMpCcguAXlSPs", { api_host: "https://app.posthog.com", disable_session_recording: true, - session_recording: { - // WARNING: Only enable this if you understand the security implications - // recordCrossOriginIframes: true, - } as any, }); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx index 70031d40..9bb558c7 100644 --- a/extension/react-app/src/pages/gui.tsx +++ b/extension/react-app/src/pages/gui.tsx @@ -6,7 +6,7 @@ import { } from "../components"; import Loader from "../components/Loader"; import ContinueButton from "../components/ContinueButton"; -import { FullState, HighlightedRangeContext } from "../../../schema/FullState"; +import { ContextItem, FullState } from "../../../schema/FullState"; import { useCallback, useEffect, useRef, useState, useContext } from "react"; import { History } from "../../../schema/History"; import { HistoryNode } from "../../../schema/HistoryNode"; @@ -22,12 +22,16 @@ import TextDialog from "../components/TextDialog"; import HeaderButtonWithText from "../components/HeaderButtonWithText"; import ReactSwitch from "react-switch"; import { usePostHog } from "posthog-js/react"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { RootStore } from "../redux/store"; import { postVscMessage } from "../vscode"; import UserInputContainer from "../components/UserInputContainer"; import Onboarding from "../components/Onboarding"; import { isMetaEquivalentKeyPressed } from "../util"; +import { + setBottomMessage, + setBottomMessageCloseTimeout, +} from "../redux/slices/uiStateSlice"; const TopGUIDiv = styled.div` overflow: hidden; @@ -64,9 +68,6 @@ function GUI(props: GUIProps) { const vscMachineId = useSelector( (state: RootStore) => state.config.vscMachineId ); - const vscMediaUrl = useSelector( - (state: RootStore) => state.config.vscMediaUrl - ); const [dataSwitchChecked, setDataSwitchChecked] = useState(false); const dataSwitchOn = useSelector( (state: RootStore) => state.config.dataSwitchOn @@ -80,15 +81,13 @@ function GUI(props: GUIProps) { const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState<string[]>([]); - const [highlightedRanges, setHighlightedRanges] = useState< - HighlightedRangeContext[] - >([]); const [addingHighlightedCode, setAddingHighlightedCode] = useState(false); + const [selectedContextItems, setSelectedContextItems] = useState< + ContextItem[] + >([]); const [availableSlashCommands, setAvailableSlashCommands] = useState< { name: string; description: string }[] >([]); - const [pinned, setPinned] = useState(false); - const [showDataSharingInfo, setShowDataSharingInfo] = useState(false); const [stepsOpen, setStepsOpen] = useState<boolean[]>([ true, true, @@ -117,10 +116,36 @@ function GUI(props: GUIProps) { current_index: 3, } as any); + const vscMediaUrl = useSelector( + (state: RootStore) => state.config.vscMediaUrl + ); const [showFeedbackDialog, setShowFeedbackDialog] = useState(false); const [feedbackDialogMessage, setFeedbackDialogMessage] = useState(""); const [feedbackEntryOn, setFeedbackEntryOn] = useState(true); + const dispatch = useDispatch(); + const bottomMessage = useSelector( + (state: RootStore) => state.uiState.bottomMessage + ); + + const [displayBottomMessageOnBottom, setDisplayBottomMessageOnBottom] = + useState<boolean>(true); + const mainTextInputRef = useRef<HTMLInputElement>(null); + + const aboveComboBoxDivRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + if (!aboveComboBoxDivRef.current) return; + if ( + aboveComboBoxDivRef.current.getBoundingClientRect().top > + window.innerHeight / 2 + ) { + setDisplayBottomMessageOnBottom(false); + } else { + setDisplayBottomMessageOnBottom(true); + } + }, [bottomMessage, aboveComboBoxDivRef.current]); + const topGuiDivRef = useRef<HTMLDivElement>(null); const [scrollTimeout, setScrollTimeout] = useState<NodeJS.Timeout | null>( @@ -152,6 +177,8 @@ function GUI(props: GUIProps) { history.timeline[history.current_index]?.active ) { client?.deleteAtIndex(history.current_index); + } else if (e.key === "Escape") { + dispatch(setBottomMessage(undefined)); } }; window.addEventListener("keydown", listener); @@ -178,7 +205,7 @@ function GUI(props: GUIProps) { setWaitingForSteps(waitingForSteps); setHistory(state.history); - setHighlightedRanges(state.highlighted_ranges); + setSelectedContextItems(state.selected_context_items || []); setUserInputQueue(state.user_input_queue); setAddingHighlightedCode(state.adding_highlighted_code); setAvailableSlashCommands( @@ -211,15 +238,6 @@ function GUI(props: GUIProps) { scrollToBottom(); }, [waitingForSteps]); - const mainTextInputRef = useRef<HTMLInputElement>(null); - - const deleteContextItems = useCallback( - (indices: number[]) => { - client?.deleteContextAtIndices(indices); - }, - [client] - ); - const onMainTextInput = (event?: any) => { if (mainTextInputRef.current) { let input = (mainTextInputRef.current as any).inputValue; @@ -351,6 +369,7 @@ function GUI(props: GUIProps) { })} </div> + <div ref={aboveComboBoxDivRef} /> <ComboBox ref={mainTextInputRef} onEnter={(e) => { @@ -360,11 +379,7 @@ function GUI(props: GUIProps) { }} onInputValueChange={() => {}} items={availableSlashCommands} - highlightedCodeSections={highlightedRanges} - deleteContextItems={deleteContextItems} - onTogglePin={() => { - setPinned((prev: boolean) => !prev); - }} + selectedContextItems={selectedContextItems} onToggleAddContext={() => { client?.toggleAddingHighlightedCode(); }} @@ -373,29 +388,34 @@ function GUI(props: GUIProps) { <ContinueButton onClick={onMainTextInput} /> </TopGUIDiv> <div + onMouseEnter={() => { + dispatch(setBottomMessageCloseTimeout(undefined)); + }} + onMouseLeave={(e) => { + if (!e.buttons) { + dispatch(setBottomMessage(undefined)); + } + }} style={{ position: "fixed", - bottom: "50px", + bottom: displayBottomMessageOnBottom ? "50px" : undefined, + top: displayBottomMessageOnBottom ? undefined : "50px", + left: "0", + right: "0", + margin: "8px", + marginTop: "0px", backgroundColor: vscBackground, color: vscForeground, borderRadius: defaultBorderRadius, - padding: "16px", - margin: "16px", + padding: "12px", zIndex: 100, - boxShadow: `0px 0px 10px 0px ${vscForeground}`, + boxShadow: `0px 0px 4px 0px ${vscForeground}`, + maxHeight: "50vh", + overflow: "scroll", }} - hidden={!showDataSharingInfo} + hidden={!bottomMessage} > - By turning on this switch, you will begin collecting accepted and - rejected suggestions in .continue/suggestions.json. This data is stored - locally on your machine and not sent anywhere. - <br /> - <br /> - <b> - {dataSwitchChecked - ? "👍 Data is being collected" - : "👎 No data is being collected"} - </b> + {bottomMessage} </div> <Footer dataSwitchChecked={dataSwitchChecked}> {vscMediaUrl && ( @@ -403,10 +423,13 @@ function GUI(props: GUIProps) { href="https://github.com/continuedev/continue" style={{ marginRight: "auto" }} > - <img src={`${vscMediaUrl}/continue-dev-square.png`} width="22px" /> + <img + src={`${vscMediaUrl}/continue-dev-square.png`} + width="22px" + style={{ backgroundColor: "black", color: "red" }} + /> </a> )} - {/* <p style={{ margin: "0", marginRight: "auto" }}>Continue</p> */} <HeaderButtonWithText onClick={() => { // Show the dialog diff --git a/extension/react-app/src/redux/selectors/uiStateSelectors.ts b/extension/react-app/src/redux/selectors/uiStateSelectors.ts new file mode 100644 index 00000000..7ebc9338 --- /dev/null +++ b/extension/react-app/src/redux/selectors/uiStateSelectors.ts @@ -0,0 +1,5 @@ +import { RootStore } from "../store"; + +const selectBottomMessage = (state: RootStore) => state.uiState.bottomMessage; + +export { selectBottomMessage }; diff --git a/extension/react-app/src/redux/slices/uiStateSlice.ts b/extension/react-app/src/redux/slices/uiStateSlice.ts new file mode 100644 index 00000000..837d19e9 --- /dev/null +++ b/extension/react-app/src/redux/slices/uiStateSlice.ts @@ -0,0 +1,24 @@ +import { createSlice } from "@reduxjs/toolkit"; + +export const uiStateSlice = createSlice({ + name: "uiState", + initialState: { + bottomMessage: undefined, + bottomMessageCloseTimeout: undefined, + }, + reducers: { + setBottomMessage: (state, action) => { + state.bottomMessage = action.payload; + }, + setBottomMessageCloseTimeout: (state, action) => { + if (state.bottomMessageCloseTimeout) { + clearTimeout(state.bottomMessageCloseTimeout); + } + state.bottomMessageCloseTimeout = action.payload; + }, + }, +}); + +export const { setBottomMessage, setBottomMessageCloseTimeout } = + uiStateSlice.actions; +export default uiStateSlice.reducer; diff --git a/extension/react-app/src/redux/store.ts b/extension/react-app/src/redux/store.ts index b6eb55b3..d49513e5 100644 --- a/extension/react-app/src/redux/store.ts +++ b/extension/react-app/src/redux/store.ts @@ -3,6 +3,7 @@ import debugStateReducer from "./slices/debugContexSlice"; import chatReducer from "./slices/chatSlice"; import configReducer from "./slices/configSlice"; import miscReducer from "./slices/miscSlice"; +import uiStateReducer from "./slices/uiStateSlice"; import { RangeInFile, SerializedDebugContext } from "../../../src/client"; export interface ChatMessage { @@ -31,6 +32,10 @@ export interface RootStore { misc: { highlightedCode: RangeInFile | undefined; }; + uiState: { + bottomMessage: JSX.Element | undefined; + bottomMessageCloseTimeout: NodeJS.Timeout | undefined; + }; } const store = configureStore({ @@ -39,6 +44,7 @@ const store = configureStore({ chat: chatReducer, config: configReducer, misc: miscReducer, + uiState: uiStateReducer, }, }); diff --git a/extension/schema/ContextItem.d.ts b/extension/schema/ContextItem.d.ts new file mode 100644 index 00000000..62d2c32f --- /dev/null +++ b/extension/schema/ContextItem.d.ts @@ -0,0 +1,45 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +export type ContextItem = ContextItem1; +export type Name = string; +export type Description = string; +export type ProviderTitle = string; +export type ItemId = string; +export type Content = string; +export type Editing = boolean; +export type Editable = boolean; + +/** + * A ContextItem is a single item that is stored in the ContextManager. + */ +export interface ContextItem1 { + description: ContextItemDescription; + content: Content; + editing?: Editing; + editable?: Editable; + [k: string]: unknown; +} +/** + * A ContextItemDescription is a description of a ContextItem that is displayed to the user when they type '@'. + * + * The id can be used to retrieve the ContextItem from the ContextManager. + */ +export interface ContextItemDescription { + name: Name; + description: Description; + id: ContextItemId; + [k: string]: unknown; +} +/** + * A ContextItemId is a unique identifier for a ContextItem. + */ +export interface ContextItemId { + provider_title: ProviderTitle; + item_id: ItemId; + [k: string]: unknown; +} diff --git a/extension/schema/FullState.d.ts b/extension/schema/FullState.d.ts index 1b7b1f3b..0095f41b 100644 --- a/extension/schema/FullState.d.ts +++ b/extension/schema/FullState.d.ts @@ -27,18 +27,18 @@ export type CurrentIndex = number; export type Active1 = boolean; export type UserInputQueue = string[]; export type DefaultModel = string; -export type Filepath = string; -export type Line = number; -export type Character = number; -export type Contents = string; -export type Editing = boolean; -export type Pinned = boolean; -export type DisplayName = string; -export type HighlightedRanges = HighlightedRangeContext[]; export type Name3 = string; export type Description1 = string; export type SlashCommands = SlashCommandDescription[]; export type AddingHighlightedCode = boolean; +export type Name4 = string; +export type Description2 = string; +export type ProviderTitle = string; +export type ItemId = string; +export type Content1 = string; +export type Editing = boolean; +export type Editable = boolean; +export type SelectedContextItems = ContextItem[]; /** * A full state of the program, including the history @@ -48,9 +48,9 @@ export interface FullState1 { active: Active1; user_input_queue: UserInputQueue; default_model: DefaultModel; - highlighted_ranges: HighlightedRanges; slash_commands: SlashCommands; adding_highlighted_code: AddingHighlightedCode; + selected_context_items: SelectedContextItems; [k: string]: unknown; } /** @@ -98,40 +98,37 @@ export interface FunctionCall { export interface Observation { [k: string]: unknown; } -/** - * Context for a highlighted range - */ -export interface HighlightedRangeContext { - range: RangeInFileWithContents; - editing: Editing; - pinned: Pinned; - display_name: DisplayName; +export interface SlashCommandDescription { + name: Name3; + description: Description1; [k: string]: unknown; } /** - * A range in a file with the contents of the range. + * A ContextItem is a single item that is stored in the ContextManager. */ -export interface RangeInFileWithContents { - filepath: Filepath; - range: Range; - contents: Contents; +export interface ContextItem { + description: ContextItemDescription; + content: Content1; + editing?: Editing; + editable?: Editable; [k: string]: unknown; } /** - * A range in a file. 0-indexed. + * A ContextItemDescription is a description of a ContextItem that is displayed to the user when they type '@'. + * + * The id can be used to retrieve the ContextItem from the ContextManager. */ -export interface Range { - start: Position; - end: Position; +export interface ContextItemDescription { + name: Name4; + description: Description2; + id: ContextItemId; [k: string]: unknown; } -export interface Position { - line: Line; - character: Character; - [k: string]: unknown; -} -export interface SlashCommandDescription { - name: Name3; - description: Description1; +/** + * A ContextItemId is a unique identifier for a ContextItem. + */ +export interface ContextItemId { + provider_title: ProviderTitle; + item_id: ItemId; [k: string]: unknown; } diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index a1d88a31..356d0256 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -17,7 +17,7 @@ export let ideProtocolClient: IdeProtocolClient; export async function activateExtension(context: vscode.ExtensionContext) { extensionContext = context; - + console.log("Using Continue version: ", getExtensionVersion()); // Before anything else, check whether this is an out-of-date version of the extension // Do so by grabbing the package.json off of the GitHub respository for now. fetch(PACKAGE_JSON_RAW_GITHUB_URL) @@ -67,6 +67,7 @@ export async function activateExtension(context: vscode.ExtensionContext) { }, 2000); }); + console.log("Continue server started"); // Initialize IDE Protocol Client const serverUrl = getContinueServerUrl(); ideProtocolClient = new IdeProtocolClient( diff --git a/extension/src/bridge.ts b/extension/src/bridge.ts index d614ace4..0d665826 100644 --- a/extension/src/bridge.ts +++ b/extension/src/bridge.ts @@ -1,44 +1,5 @@ -import fetch from "node-fetch"; -import * as path from "path"; import * as vscode from "vscode"; -import { Configuration, DebugApi, UnittestApi } from "./client"; -import { convertSingleToDoubleQuoteJSON } from "./util/util"; -import { getExtensionUri } from "./util/vscode"; import { extensionContext } from "./activation/activate"; -const util = require("util"); -const exec = util.promisify(require("child_process").exec); - -const configuration = new Configuration({ - basePath: get_api_url(), - fetchApi: fetch as any, - middleware: [ - { - pre: async (context) => { - // If there is a SerializedDebugContext in the body, add the files for the filesystem - context.init.body; - - // Add the VS Code Machine Code Header - context.init.headers = { - ...context.init.headers, - "x-vsc-machine-id": vscode.env.machineId, - }; - }, - }, - ], -}); -export const debugApi = new DebugApi(configuration); -export const unittestApi = new UnittestApi(configuration); - -export function get_api_url() { - const extensionUri = getExtensionUri(); - const configFile = path.join(extensionUri.fsPath, "config/config.json"); - const config = require(configFile); - - if (config.API_URL) { - return config.API_URL; - } - return "http://localhost:65432"; -} export function getContinueServerUrl() { // If in debug mode, always use 8001 @@ -53,37 +14,3 @@ export function getContinueServerUrl() { "http://localhost:65432" ); } - -function listToCmdLineArgs(list: string[]): string { - return list.map((el) => `"$(echo "${el}")"`).join(" "); -} - -export async function runPythonScript( - scriptName: string, - args: string[] -): Promise<any> { - // TODO: Need to make sure that the path to poetry is in the PATH and that it is installed in the first place. Realistically also need to install npm in some cases. - const command = `export PATH="$PATH:/opt/homebrew/bin" && cd ${path.join( - getExtensionUri().fsPath, - "scripts" - )} && source env/bin/activate && python3 ${scriptName} ${listToCmdLineArgs( - args - )}`; - - const { stdout, stderr } = await exec(command); - - try { - let jsonString = stdout.substring( - stdout.indexOf("{"), - stdout.lastIndexOf("}") + 1 - ); - jsonString = convertSingleToDoubleQuoteJSON(jsonString); - return JSON.parse(jsonString); - } catch (e) { - if (stderr) { - throw new Error(stderr); - } else { - throw new Error("Failed to parse JSON: " + e); - } - } -} diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 802afc1d..d92a829d 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -399,8 +399,9 @@ class IdeProtocolClient { } }, 1000); }); + console.log("Getting session ID"); const resp = await this.messenger?.sendAndReceive("getSessionId", {}); - // console.log("New Continue session with ID: ", sessionId); + console.log("New Continue session with ID: ", resp.sessionId); this._sessionId = resp.sessionId; return resp.sessionId; } diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 6da79cdc..6dcb588a 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -166,11 +166,13 @@ export function setupDebugPanel( switch (data.type) { case "onLoad": { let sessionId: string; + console.log("Running onLoad"); if (typeof sessionIdPromise === "string") { sessionId = sessionIdPromise; } else { sessionId = await sessionIdPromise; } + console.log("Done with onLoad: ", sessionId); panel.webview.postMessage({ type: "onLoad", vscMachineId: vscode.env.machineId, diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts index 3044898e..32490a68 100644 --- a/extension/src/util/messenger.ts +++ b/extension/src/util/messenger.ts @@ -39,6 +39,7 @@ export class WebsocketMessenger extends Messenger { // var WebSocket = require("ws"); // } + console.log("Creating websocket at: ", this.serverUrl); const newWebsocket = new WebSocket(this.serverUrl); for (const listener of this.onOpenListeners) { this.onOpen(listener); |