summaryrefslogtreecommitdiff
path: root/extension/react-app/src/components/ComboBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'extension/react-app/src/components/ComboBox.tsx')
-rw-r--r--extension/react-app/src/components/ComboBox.tsx776
1 files changed, 553 insertions, 223 deletions
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 934b7337..6c99a650 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -7,7 +7,7 @@ import React, {
useState,
} from "react";
import { useCombobox } from "downshift";
-import styled from "styled-components";
+import styled, { keyframes } from "styled-components";
import {
buttonColor,
defaultBorderRadius,
@@ -21,8 +21,10 @@ import HeaderButtonWithText from "./HeaderButtonWithText";
import {
ArrowLeftIcon,
ArrowRightIcon,
- MagnifyingGlassIcon,
+ ArrowUpLeftIcon,
+ StopCircleIcon,
TrashIcon,
+ XMarkIcon,
} from "@heroicons/react/24/outline";
import { postVscMessage } from "../vscode";
import { GUIClientContext } from "../App";
@@ -31,12 +33,58 @@ import { setBottomMessage } from "../redux/slices/uiStateSlice";
import { useDispatch, useSelector } from "react-redux";
import { RootStore } from "../redux/store";
import ContinueButton from "./ContinueButton";
-import { getFontSize } from "../util";
+import {
+ getFontSize,
+ getMarkdownLanguageTagForFile,
+ getMetaKeyLabel,
+} from "../util";
+import { ContextItem } from "../../../schema/FullState";
+import StyledMarkdownPreview from "./StyledMarkdownPreview";
const SEARCH_INDEX_NAME = "continue_context_items";
// #region styled components
+const gradient = keyframes`
+ 0% {
+ background-position: 0px 0;
+ }
+ 100% {
+ background-position: 100em 0;
+ }
+`;
+
+const GradientBorder = styled.div<{
+ borderRadius?: string;
+ borderColor?: string;
+ isFirst: boolean;
+ isLast: boolean;
+ loading: boolean;
+}>`
+ border-radius: ${(props) => props.borderRadius || "0"};
+ padding: 1px;
+ background: ${(props) =>
+ props.borderColor
+ ? props.borderColor
+ : `repeating-linear-gradient(
+ 101.79deg,
+ #1BBE84 0%,
+ #331BBE 16%,
+ #BE1B55 33%,
+ #A6BE1B 55%,
+ #BE1B55 67%,
+ #331BBE 85%,
+ #1BBE84 99%
+ )`};
+ animation: ${(props) => (props.loading ? gradient : "")} 6s linear infinite;
+ background-size: 200% 200%;
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-top: 8px;
+`;
+
const HiddenHeaderButtonWithText = styled.button`
opacity: 0;
background-color: transparent;
@@ -75,7 +123,7 @@ const MainTextInput = styled.textarea<{
font-size: ${(props) => props.fontSize || mainInputFontSize}px;
font-family: inherit;
border-radius: ${defaultBorderRadius};
- margin: 8px auto;
+ margin: 0;
height: auto;
width: 100%;
background-color: ${secondaryDark};
@@ -98,6 +146,15 @@ const MainTextInput = styled.textarea<{
}
`;
+const DeleteButtonDiv = styled.div`
+ position: absolute;
+ top: 14px;
+ right: 12px;
+ background-color: ${secondaryDark};
+ border-radius: ${defaultBorderRadius};
+ z-index: 100;
+`;
+
const DynamicQueryTitleDiv = styled.div`
position: absolute;
right: 0px;
@@ -119,12 +176,14 @@ const Ul = styled.ul<{
ulHeightPixels: number;
inputBoxHeight?: string;
fontSize?: number;
+ isMainInput: boolean;
}>`
${(props) =>
props.showAbove
? `transform: translateY(-${props.ulHeightPixels + 8}px);`
: `transform: translateY(${
- 5 * (props.fontSize || mainInputFontSize) - 2
+ (props.isMainInput ? 5 : 4) * (props.fontSize || mainInputFontSize) -
+ (props.isMainInput ? 2 : 4)
}px);`}
position: absolute;
background: ${vscBackground};
@@ -137,11 +196,11 @@ const Ul = styled.ul<{
${({ hidden }) => hidden && "display: none;"}
border-radius: ${defaultBorderRadius};
outline: 0.5px solid ${lightGray};
- z-index: 2;
-ms-overflow-style: none;
font-size: ${(props) => props.fontSize || mainInputFontSize}px;
scrollbar-width: none; /* Firefox */
+ z-index: 500;
/* Hide scrollbar for Chrome, Safari and Opera */
&::-webkit-scrollbar {
@@ -165,6 +224,7 @@ const Li = styled.li<{
${({ isLastItem }) => isLastItem && "border-bottom: 1px solid gray;"}
/* border-top: 1px solid gray; */
cursor: pointer;
+ z-index: 500;
`;
// #endregion
@@ -176,14 +236,39 @@ interface ComboBoxItem {
content?: string;
}
interface ComboBoxProps {
- onInputValueChange: (inputValue: string) => void;
+ onInputValueChange?: (inputValue: string) => void;
disabled?: boolean;
- onEnter: (e?: React.KeyboardEvent<HTMLInputElement>) => void;
- onToggleAddContext: () => void;
+ onEnter?: (e?: React.KeyboardEvent<HTMLInputElement>, value?: string) => void;
+ onToggleAddContext?: () => void;
+
+ isMainInput: boolean;
+ value?: string;
+ active?: boolean;
+ groupIndices?: number[];
+ onToggle?: (arg0: boolean) => void;
+ onToggleAll?: (arg0: boolean) => void;
+ isToggleOpen?: boolean;
+ index?: number;
+ onDelete?: () => void;
}
const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
- const searchClient = new MeiliSearch({ host: "http://127.0.0.1:7700" });
+ const meilisearchUrl = useSelector(
+ (state: RootStore) =>
+ state.serverState.meilisearch_url || "http://127.0.0.1:7700"
+ );
+
+ const [searchClient, setSearchClient] = useState<MeiliSearch | undefined>(
+ undefined
+ );
+
+ useEffect(() => {
+ const client = new MeiliSearch({
+ host: meilisearchUrl,
+ });
+ setSearchClient(client);
+ }, [meilisearchUrl]);
+
const client = useContext(GUIClientContext);
const dispatch = useDispatch();
const workspacePaths = useSelector(
@@ -197,6 +282,14 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
const inputRef = React.useRef<HTMLInputElement>(null);
+ useEffect(() => {
+ if (!inputRef.current) return;
+ if (inputRef.current.scrollHeight > inputRef.current.clientHeight) {
+ inputRef.current.style.height = "auto";
+ inputRef.current.style.height = inputRef.current.scrollHeight + "px";
+ }
+ }, [inputRef.current, props.value]);
+
// Whether the current input follows an '@' and should be treated as context query
const [currentlyInContextQuery, setCurrentlyInContextQuery] = useState(false);
const [nestedContextProvider, setNestedContextProvider] = useState<
@@ -206,9 +299,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
any | undefined
>(undefined);
- const sessionId = useSelector(
- (state: RootStore) => state.serverState.session_info?.session_id
- );
const availableSlashCommands = useSelector(
(state: RootStore) => state.serverState.slash_commands
).map((cmd) => {
@@ -217,15 +307,16 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
description: cmd.description,
};
});
- const selectedContextItems = useSelector(
- (state: RootStore) => state.serverState.selected_context_items
- );
-
- useEffect(() => {
- if (inputRef.current) {
- inputRef.current.focus();
+ const selectedContextItems = useSelector((state: RootStore) => {
+ if (props.index) {
+ return state.serverState.history.timeline[props.index].context_used || [];
+ } else {
+ return state.serverState.selected_context_items;
}
- }, [sessionId, inputRef.current]);
+ });
+ const timeline = useSelector(
+ (state: RootStore) => state.serverState.history.timeline
+ );
useEffect(() => {
if (!currentlyInContextQuery) {
@@ -287,7 +378,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
setInQueryForContextProvider(undefined);
}
- props.onInputValueChange(inputValue);
+ props.onInputValueChange?.(inputValue);
// Handle context selection
if (inputValue.endsWith("@") || currentlyInContextQuery) {
@@ -365,7 +456,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
.join(", ")} ] AND provider_name = '${provider}'`
: undefined;
try {
- const res = await searchClient.index(SEARCH_INDEX_NAME).search(query, {
+ const res = await searchClient?.index(SEARCH_INDEX_NAME).search(query, {
filter: workspaceFilter,
});
return (
@@ -410,13 +501,15 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
useImperativeHandle(ref, () => downshiftProps, [downshiftProps]);
const contextItemsDivRef = React.useRef<HTMLDivElement>(null);
- const handleTabPressed = () => {
+ const handleTabPressed = useCallback(() => {
+ setShowContextItemsIfNotMain(true);
// Set the focus to the next item in the context items div
if (!contextItemsDivRef.current) {
return;
}
- const focusableItems =
- contextItemsDivRef.current.querySelectorAll(".pill-button");
+ const focusableItems = contextItemsDivRef.current.querySelectorAll(
+ `.pill-button-${props.index || "main"}`
+ );
const focusableItemsArray = Array.from(focusableItems);
const focusedItemIndex = focusableItemsArray.findIndex(
(item) => item === document.activeElement
@@ -433,22 +526,30 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
const firstItem = focusableItemsArray[0];
(firstItem as any)?.focus();
}
- };
+ }, [props.index]);
useEffect(() => {
- if (typeof window !== "undefined") {
+ if (inputRef.current) {
const listener = (e: any) => {
if (e.key === "Tab") {
e.preventDefault();
handleTabPressed();
}
};
- window.addEventListener("keydown", listener);
+ inputRef.current.addEventListener("keydown", listener);
return () => {
- window.removeEventListener("keydown", listener);
+ inputRef.current?.removeEventListener("keydown", listener);
};
}
- }, []);
+ }, [inputRef.current]);
+
+ useEffect(() => {
+ if (props.value) {
+ downshiftProps.setInputValue(props.value);
+ }
+ }, [props.value, downshiftProps.setInputValue]);
+
+ const [isHovered, setIsHovered] = useState(false);
useLayoutEffect(() => {
if (!ulRef.current) {
@@ -458,7 +559,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
}, [items, downshiftProps.setHighlightedIndex, ulRef.current]);
const [metaKeyPressed, setMetaKeyPressed] = useState(false);
- const [focused, setFocused] = useState(false);
+ const [inputFocused, setInputFocused] = useState(false);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Meta") {
@@ -479,10 +580,12 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
}, []);
useEffect(() => {
- if (!inputRef.current) {
+ if (!inputRef.current || !props.isMainInput) {
return;
}
- inputRef.current.focus();
+ if (props.isMainInput) {
+ inputRef.current.focus();
+ }
const handler = (event: any) => {
if (event.data.type === "focusContinueInput") {
inputRef.current!.focus();
@@ -498,7 +601,20 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
return () => {
window.removeEventListener("message", handler);
};
- }, [inputRef.current]);
+ }, [inputRef.current, props.isMainInput]);
+
+ const deleteButtonDivRef = React.useRef<HTMLDivElement>(null);
+
+ const selectContextItem = useCallback(
+ (id: string, query: string) => {
+ if (props.isMainInput) {
+ client?.selectContextItem(id, query);
+ } else if (props.index) {
+ client?.selectContextItemAtIndex(id, query, props.index);
+ }
+ },
+ [client, props.index]
+ );
const selectContextItemFromDropdown = useCallback(
(event: any) => {
@@ -511,7 +627,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
if (!newProvider) {
if (nestedContextProvider && newItem.id) {
// Tell server the context item was selected
- client?.selectContextItem(newItem.id, "");
+ selectContextItem(newItem.id, "");
// Clear the input
downshiftProps.setInputValue("");
@@ -542,7 +658,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
const query = segs[segs.length - 1];
// Tell server the context item was selected
- client?.selectContextItem(newItem.id, query);
+ selectContextItem(newItem.id, query);
if (downshiftProps.inputValue.includes("@")) {
const selectedNestedContextProvider = contextProviders.find(
(provider) => provider.title === newItem.id
@@ -582,221 +698,428 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
contextProviders,
nestedContextProvider,
downshiftProps.inputValue,
+ selectContextItem,
]
);
const [isComposing, setIsComposing] = useState(false);
+ const [previewingContextItem, setPreviewingContextItem] = useState<
+ ContextItem | undefined
+ >(undefined);
+
+ const [focusedContextItem, setFocusedContextItem] = useState<
+ ContextItem | undefined
+ >(undefined);
+
+ const topRef = React.useRef<HTMLDivElement>(null);
+
+ const [showContextItemsIfNotMain, setShowContextItemsIfNotMain] =
+ useState(false);
+
+ useEffect(() => {
+ if (!inputFocused) {
+ setShowContextItemsIfNotMain(false);
+ }
+ }, [inputFocused]);
+
return (
- <>
- <div
- className="px-2 flex gap-2 items-center flex-wrap mt-2"
- ref={contextItemsDivRef}
- >
- <HiddenHeaderButtonWithText
- className={selectedContextItems.length > 0 ? "pill-button" : ""}
- onClick={() => {
- client?.deleteContextWithIds(
- selectedContextItems.map((item) => item.description.id)
- );
- inputRef.current?.focus();
- }}
- onKeyDown={(e: any) => {
- if (e.key === "Backspace") {
+ <div ref={topRef}>
+ {props.isMainInput ||
+ (selectedContextItems.length > 0 && showContextItemsIfNotMain) ? (
+ <div
+ className="px-2 flex gap-2 items-center flex-wrap"
+ ref={contextItemsDivRef}
+ style={{ backgroundColor: vscBackground }}
+ >
+ <HiddenHeaderButtonWithText
+ className={
+ selectedContextItems.length > 0
+ ? `pill-button-${props.index || "main"}`
+ : ""
+ }
+ onClick={() => {
client?.deleteContextWithIds(
- selectedContextItems.map((item) => item.description.id)
+ selectedContextItems.map((item) => item.description.id),
+ props.index
);
inputRef.current?.focus();
- }
- }}
- >
- <TrashIcon width="1.4em" height="1.4em" />
- </HiddenHeaderButtonWithText>
- {selectedContextItems.map((item, idx) => {
- return (
- <PillButton
- areMultipleItems={selectedContextItems.length > 1}
- key={`${item.description.id.item_id}${idx}`}
- item={item}
- editing={
- item.editing &&
- (inputRef.current as any)?.value?.startsWith("/edit")
- }
- editingAny={(inputRef.current as any)?.value?.startsWith("/edit")}
- index={idx}
- onDelete={() => {
- client?.deleteContextWithIds([item.description.id]);
+ }}
+ onKeyDown={(e: any) => {
+ if (e.key === "Backspace") {
+ client?.deleteContextWithIds(
+ selectedContextItems.map((item) => item.description.id),
+ props.index
+ );
inputRef.current?.focus();
- }}
- />
- );
- })}
+ setPreviewingContextItem(undefined);
+ setFocusedContextItem(undefined);
+ }
+ }}
+ >
+ <TrashIcon width="1.4em" height="1.4em" />
+ </HiddenHeaderButtonWithText>
+ {(props.isMainInput
+ ? selectedContextItems
+ : timeline[props.index!].context_used || []
+ ).map((item, idx) => {
+ return (
+ <PillButton
+ areMultipleItems={selectedContextItems.length > 1}
+ key={`${item.description.id.item_id}${idx}`}
+ item={item}
+ editing={
+ item.editing &&
+ (inputRef.current as any)?.value?.startsWith("/edit")
+ }
+ editingAny={(inputRef.current as any)?.value?.startsWith(
+ "/edit"
+ )}
+ stepIndex={props.index}
+ index={idx}
+ onDelete={() => {
+ client?.deleteContextWithIds(
+ [item.description.id],
+ props.index
+ );
+ inputRef.current?.focus();
+ if (
+ (item.description.id.item_id ===
+ focusedContextItem?.description.id.item_id &&
+ focusedContextItem?.description.id.provider_name ===
+ item.description.id.provider_name) ||
+ (item.description.id.item_id ===
+ previewingContextItem?.description.id.item_id &&
+ previewingContextItem?.description.id.provider_name ===
+ item.description.id.provider_name)
+ ) {
+ setPreviewingContextItem(undefined);
+ setFocusedContextItem(undefined);
+ }
+ }}
+ onClick={(e) => {
+ if (
+ item.description.id.item_id ===
+ focusedContextItem?.description.id.item_id &&
+ focusedContextItem?.description.id.provider_name ===
+ item.description.id.provider_name
+ ) {
+ setFocusedContextItem(undefined);
+ } else {
+ setFocusedContextItem(item);
+ }
+ }}
+ onBlur={() => {
+ setFocusedContextItem(undefined);
+ }}
+ toggleViewContent={() => {
+ setPreviewingContextItem((prev) => {
+ if (!prev) return item;
+ if (
+ prev.description.id.item_id ===
+ item.description.id.item_id &&
+ prev.description.id.provider_name ===
+ item.description.id.provider_name
+ ) {
+ return undefined;
+ } else {
+ return item;
+ }
+ });
+ }}
+ previewing={
+ item.description.id.item_id ===
+ previewingContextItem?.description.id.item_id &&
+ previewingContextItem?.description.id.provider_name ===
+ item.description.id.provider_name
+ }
+ focusing={
+ item.description.id.item_id ===
+ focusedContextItem?.description.id.item_id &&
+ focusedContextItem?.description.id.provider_name ===
+ item.description.id.provider_name
+ }
+ />
+ );
+ })}
- {selectedContextItems.length > 0 && (
+ {/* {selectedContextItems.length > 0 && (
<HeaderButtonWithText
onClick={() => {
- client?.showContextVirtualFile();
+ client?.showContextVirtualFile(props.index);
}}
text="View Current Context"
>
<MagnifyingGlassIcon width="1.4em" height="1.4em" />
</HeaderButtonWithText>
- )}
- </div>
+ )} */}
+ </div>
+ ) : (
+ selectedContextItems.length > 0 && (
+ <div
+ onClick={() => {
+ inputRef.current?.focus();
+ setShowContextItemsIfNotMain(true);
+ }}
+ style={{
+ color: lightGray,
+ fontSize: "10px",
+ backgroundColor: vscBackground,
+ paddingLeft: "12px",
+ cursor: "default",
+ paddingTop: getFontSize(),
+ }}
+ >
+ {props.active ? "Using" : "Used"} {selectedContextItems.length}{" "}
+ context item
+ {selectedContextItems.length === 1 ? "" : "s"}
+ </div>
+ )
+ )}
+ {previewingContextItem && (
+ <pre className="m-0">
+ <StyledMarkdownPreview
+ fontSize={getFontSize()}
+ source={`\`\`\`${getMarkdownLanguageTagForFile(
+ previewingContextItem.description.description
+ )}\n${previewingContextItem.content}\n\`\`\``}
+ wrapperElement={{
+ "data-color-mode": "dark",
+ }}
+ maxHeight={200}
+ />
+ </pre>
+ )}
<div
className="flex px-2 relative"
+ style={{
+ backgroundColor: vscBackground,
+ }}
ref={divRef}
- hidden={!downshiftProps.isOpen}
>
- <MainTextInput
- inQueryForDynamicProvider={
- typeof inQueryForContextProvider !== "undefined"
- }
- fontSize={getFontSize()}
- disabled={props.disabled}
- placeholder={`Ask a question, '/' for slash commands, '@' to add context`}
- {...getInputProps({
- onCompositionStart: () => setIsComposing(true),
- onCompositionEnd: () => setIsComposing(false),
- onChange: (e) => {
- const target = e.target as HTMLTextAreaElement;
- // Update the height of the textarea to match the content, up to a max of 200px.
- target.style.height = "auto";
- target.style.height = `${Math.min(
- target.scrollHeight,
- 300
- ).toString()}px`;
-
- // setShowContextDropdown(target.value.endsWith("@"));
- },
- onFocus: (e) => {
- setFocused(true);
- dispatch(setBottomMessage(undefined));
- },
- 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) &&
- !isComposing
+ <GradientBorder
+ loading={props.active || false}
+ isFirst={false}
+ isLast={false}
+ borderColor={props.active ? undefined : vscBackground}
+ borderRadius={defaultBorderRadius}
+ >
+ <MainTextInput
+ onMouseEnter={() => setIsHovered(true)}
+ onMouseLeave={(e) => {
+ console.log("left");
+ if (
+ e.relatedTarget === deleteButtonDivRef.current ||
+ deleteButtonDivRef.current?.contains(e.relatedTarget as Node)
) {
- const value = downshiftProps.inputValue;
- if (inQueryForContextProvider) {
- const segs = value.split("@");
- client?.selectContextItem(
- inQueryForContextProvider.title,
- segs[segs.length - 1]
- );
- setCurrentlyInContextQuery(false);
- downshiftProps.setInputValue("");
+ return;
+ }
+ setIsHovered(false);
+ }}
+ rows={props.isMainInput ? undefined : 1}
+ inQueryForDynamicProvider={
+ typeof inQueryForContextProvider !== "undefined"
+ }
+ fontSize={getFontSize()}
+ disabled={props.disabled}
+ placeholder={`Ask a question, '/' for slash commands, '@' to add context`}
+ {...getInputProps({
+ onCompositionStart: () => setIsComposing(true),
+ onCompositionEnd: () => setIsComposing(false),
+ onChange: (e) => {
+ const target = e.target as HTMLTextAreaElement;
+ // Update the height of the textarea to match the content, up to a max of 200px.
+ target.style.height = "auto";
+ target.style.height = `${Math.min(
+ target.scrollHeight,
+ 300
+ ).toString()}px`;
+
+ // setShowContextDropdown(target.value.endsWith("@"));
+ },
+ onFocus: (e) => {
+ setInputFocused(true);
+ dispatch(setBottomMessage(undefined));
+ },
+ onBlur: (e) => {
+ if (topRef.current?.contains(e.relatedTarget as Node)) {
return;
- } else {
- if (value !== "") {
- setPositionInHistory(history.length + 1);
- setHistory([...history, value]);
- }
+ }
+ setInputFocused(false);
+ },
+ onKeyDown: (event) => {
+ dispatch(setBottomMessage(undefined));
+ if (event.key === "Enter" && event.shiftKey) {
// Prevent Downshift's default 'Enter' behavior.
(event.nativeEvent as any).preventDownshiftDefault = true;
-
- if (props.onEnter) {
- props.onEnter(event);
+ setCurrentlyInContextQuery(false);
+ } else if (
+ event.key === "Enter" &&
+ (!downshiftProps.isOpen || items.length === 0) &&
+ !isComposing
+ ) {
+ const value = downshiftProps.inputValue;
+ if (inQueryForContextProvider) {
+ const segs = value.split("@");
+ selectContextItem(
+ inQueryForContextProvider.title,
+ segs[segs.length - 1]
+ );
+ setCurrentlyInContextQuery(false);
+ downshiftProps.setInputValue("");
+ return;
+ } else {
+ if (value !== "") {
+ setPositionInHistory(history.length + 1);
+ setHistory([...history, value]);
+ }
+ // Prevent Downshift's default 'Enter' behavior.
+ (event.nativeEvent as any).preventDownshiftDefault = true;
+
+ if (props.onEnter) {
+ props.onEnter(event, value);
+ }
}
- }
- setCurrentlyInContextQuery(false);
- } else if (event.key === "Enter" && currentlyInContextQuery) {
- // Handle "Enter" for Context Providers
- selectContextItemFromDropdown(event);
- } else if (
- event.key === "Tab" &&
- downshiftProps.isOpen &&
- items.length > 0 &&
- items[downshiftProps.highlightedIndex]?.name.startsWith("/")
- ) {
- downshiftProps.setInputValue(items[0].name);
- event.preventDefault();
- } else if (event.key === "Tab") {
- (event.nativeEvent as any).preventDownshiftDefault = true;
- } else if (
- (event.key === "ArrowUp" || event.key === "ArrowDown") &&
- items.length > 0
- ) {
- return;
- } else if (event.key === "ArrowUp") {
- // Only go back in history if selectionStart is 0
- // (i.e. the cursor is at the beginning of the input)
- if (
- positionInHistory == 0 ||
- event.currentTarget.selectionStart !== 0
+ setCurrentlyInContextQuery(false);
+ } else if (event.key === "Enter" && currentlyInContextQuery) {
+ // Handle "Enter" for Context Providers
+ selectContextItemFromDropdown(event);
+ } else if (
+ event.key === "Tab" &&
+ downshiftProps.isOpen &&
+ items.length > 0 &&
+ items[downshiftProps.highlightedIndex]?.name.startsWith("/")
) {
+ downshiftProps.setInputValue(items[0].name);
+ event.preventDefault();
+ } else if (event.key === "Tab") {
(event.nativeEvent as any).preventDownshiftDefault = true;
- return;
} else if (
- positionInHistory == history.length &&
- (history.length === 0 ||
- history[history.length - 1] !== event.currentTarget.value)
+ (event.key === "ArrowUp" || event.key === "ArrowDown") &&
+ items.length > 0
) {
- setHistory([...history, event.currentTarget.value]);
- }
- downshiftProps.setInputValue(history[positionInHistory - 1]);
- setPositionInHistory((prev) => prev - 1);
- setCurrentlyInContextQuery(false);
- } else if (event.key === "ArrowDown") {
- if (
- positionInHistory === history.length ||
- event.currentTarget.selectionStart !==
- event.currentTarget.value.length
- ) {
- (event.nativeEvent as any).preventDownshiftDefault = true;
return;
- }
+ } else if (event.key === "ArrowUp") {
+ // Only go back in history if selectionStart is 0
+ // (i.e. the cursor is at the beginning of the input)
+ if (
+ positionInHistory == 0 ||
+ event.currentTarget.selectionStart !== 0
+ ) {
+ (event.nativeEvent as any).preventDownshiftDefault = true;
+ return;
+ } else if (
+ positionInHistory == history.length &&
+ (history.length === 0 ||
+ history[history.length - 1] !== event.currentTarget.value)
+ ) {
+ setHistory([...history, event.currentTarget.value]);
+ }
+ downshiftProps.setInputValue(history[positionInHistory - 1]);
+ setPositionInHistory((prev) => prev - 1);
+ setCurrentlyInContextQuery(false);
+ } else if (event.key === "ArrowDown") {
+ if (
+ positionInHistory === history.length ||
+ event.currentTarget.selectionStart !==
+ event.currentTarget.value.length
+ ) {
+ (event.nativeEvent as any).preventDownshiftDefault = true;
+ return;
+ }
- if (positionInHistory < history.length) {
- downshiftProps.setInputValue(history[positionInHistory + 1]);
- }
- setPositionInHistory((prev) =>
- Math.min(prev + 1, history.length)
- );
- setCurrentlyInContextQuery(false);
- } else if (event.key === "Escape") {
- if (nestedContextProvider) {
- goBackToContextProviders();
- (event.nativeEvent as any).preventDownshiftDefault = true;
- return;
- } else if (inQueryForContextProvider) {
- goBackToContextProviders();
- (event.nativeEvent as any).preventDownshiftDefault = true;
- return;
- }
+ if (positionInHistory < history.length) {
+ downshiftProps.setInputValue(
+ history[positionInHistory + 1]
+ );
+ }
+ setPositionInHistory((prev) =>
+ Math.min(prev + 1, history.length)
+ );
+ setCurrentlyInContextQuery(false);
+ } else if (event.key === "Escape") {
+ if (nestedContextProvider) {
+ goBackToContextProviders();
+ (event.nativeEvent as any).preventDownshiftDefault = true;
+ return;
+ } else if (inQueryForContextProvider) {
+ goBackToContextProviders();
+ (event.nativeEvent as any).preventDownshiftDefault = true;
+ return;
+ }
- setCurrentlyInContextQuery(false);
- if (downshiftProps.isOpen && items.length > 0) {
- downshiftProps.closeMenu();
+ setCurrentlyInContextQuery(false);
+ if (downshiftProps.isOpen && items.length > 0) {
+ downshiftProps.closeMenu();
+ (event.nativeEvent as any).preventDownshiftDefault = true;
+ } else {
+ (event.nativeEvent as any).preventDownshiftDefault = true;
+ // Remove focus from the input
+ inputRef.current?.blur();
+ // Move cursor back over to the editor
+ postVscMessage("focusEditor", {});
+ }
+ }
+ // Home and end keys
+ else if (event.key === "Home") {
(event.nativeEvent as any).preventDownshiftDefault = true;
- } else {
+ } else if (event.key === "End") {
(event.nativeEvent as any).preventDownshiftDefault = true;
- // Remove focus from the input
- inputRef.current?.blur();
- // Move cursor back over to the editor
- postVscMessage("focusEditor", {});
}
- }
- // Home and end keys
- else if (event.key === "Home") {
- (event.nativeEvent as any).preventDownshiftDefault = true;
- } else if (event.key === "End") {
- (event.nativeEvent as any).preventDownshiftDefault = true;
- }
- },
- onClick: () => {
- dispatch(setBottomMessage(undefined));
- },
- ref: inputRef,
- })}
- />
+ },
+ onClick: () => {
+ dispatch(setBottomMessage(undefined));
+ },
+ ref: inputRef,
+ })}
+ />
+ {props.isMainInput || (
+ <DeleteButtonDiv ref={deleteButtonDivRef}>
+ {isHovered && (
+ <div className="flex">
+ <>
+ {timeline
+ .filter(
+ (h, i: number) =>
+ props.groupIndices?.includes(i) && h.logs
+ )
+ .some((h) => h.logs!.length > 0) && (
+ <HeaderButtonWithText
+ onClick={(e) => {
+ e.stopPropagation();
+ if (props.groupIndices)
+ client?.showLogsAtIndex(props.groupIndices[1]);
+ }}
+ text="Inspect Prompt"
+ >
+ <ArrowUpLeftIcon width="1.3em" height="1.3em" />
+ </HeaderButtonWithText>
+ )}
+ <HeaderButtonWithText
+ onClick={(e) => {
+ e.stopPropagation();
+ if (props.active && props.groupIndices) {
+ client?.deleteAtIndex(props.groupIndices[1]);
+ } else {
+ props.onDelete?.();
+ }
+ }}
+ text={
+ props.active ? `Stop (${getMetaKeyLabel()}⌫)` : "Delete"
+ }
+ >
+ {props.active ? (
+ <StopCircleIcon width="1.4em" height="1.4em" />
+ ) : (
+ <XMarkIcon width="1.4em" height="1.4em" />
+ )}
+ </HeaderButtonWithText>
+ </>
+ </div>
+ )}
+ </DeleteButtonDiv>
+ )}
+ </GradientBorder>
{inQueryForContextProvider && (
<DynamicQueryTitleDiv>
Enter {inQueryForContextProvider.display_title} Query
@@ -807,6 +1130,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
{...downshiftProps.getMenuProps({
ref: ulRef,
})}
+ isMainInput={props.isMainInput}
showAbove={showAbove()}
ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0}
hidden={
@@ -832,8 +1156,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
width="1.4em"
height="1.4em"
className="cursor-pointer"
- onClick={() => {
+ onClick={(e) => {
goBackToContextProviders();
+ inputRef.current?.focus();
}}
/>
{nestedContextProvider.display_title} -{" "}
@@ -888,18 +1213,23 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
</div>
{selectedContextItems.length === 0 &&
(downshiftProps.inputValue?.startsWith("/edit") ||
- (focused &&
+ (inputFocused &&
metaKeyPressed &&
downshiftProps.inputValue?.length > 0)) && (
- <div className="text-trueGray-400 pr-4 text-xs text-right">
+ <div
+ className="text-trueGray-400 pr-4 text-xs text-right"
+ style={{ backgroundColor: vscBackground }}
+ >
Inserting at cursor
</div>
)}
- <ContinueButton
- disabled={!(inputRef.current as any)?.value}
- onClick={() => props.onEnter(undefined)}
- />
- </>
+ {props.isMainInput && (
+ <ContinueButton
+ disabled={!(inputRef.current as any)?.value}
+ onClick={() => props.onEnter?.(undefined)}
+ />
+ )}
+ </div>
);
});