summaryrefslogtreecommitdiff
path: root/extension
diff options
context:
space:
mode:
authorNate Sesti <sestinj@gmail.com>2023-09-24 01:00:42 -0700
committerNate Sesti <sestinj@gmail.com>2023-09-24 01:00:42 -0700
commitf9a84bd2d65b3142cbcfcdd8e1e9394c9d4b458e (patch)
treebf986f4dfbf89881ca2a1d0d925ae846d0b9c5c0 /extension
parent2cc90f0eedee7056ca03cef946d734b365ab33f4 (diff)
downloadsncontinue-f9a84bd2d65b3142cbcfcdd8e1e9394c9d4b458e.tar.gz
sncontinue-f9a84bd2d65b3142cbcfcdd8e1e9394c9d4b458e.tar.bz2
sncontinue-f9a84bd2d65b3142cbcfcdd8e1e9394c9d4b458e.zip
feat: :lipstick: more ui improvements
Diffstat (limited to 'extension')
-rw-r--r--extension/react-app/src/components/HeaderButtonWithText.tsx8
-rw-r--r--extension/react-app/src/components/Layout.tsx6
-rw-r--r--extension/react-app/src/components/PillButton.tsx1
-rw-r--r--extension/react-app/src/components/Suggestions.tsx30
-rw-r--r--extension/react-app/src/components/UserInputContainer.tsx35
-rw-r--r--extension/react-app/src/components/index.ts2
-rw-r--r--extension/react-app/src/pages/gui.tsx19
-rw-r--r--extension/react-app/src/pages/history.tsx124
-rw-r--r--extension/react-app/src/pages/models.tsx1
-rw-r--r--extension/react-app/src/redux/slices/serverStateReducer.ts4
10 files changed, 159 insertions, 71 deletions
diff --git a/extension/react-app/src/components/HeaderButtonWithText.tsx b/extension/react-app/src/components/HeaderButtonWithText.tsx
index ca359250..84e6118c 100644
--- a/extension/react-app/src/components/HeaderButtonWithText.tsx
+++ b/extension/react-app/src/components/HeaderButtonWithText.tsx
@@ -13,7 +13,10 @@ interface HeaderButtonWithTextProps {
onKeyDown?: (e: any) => void;
}
-const HeaderButtonWithText = (props: HeaderButtonWithTextProps) => {
+const HeaderButtonWithText = React.forwardRef<
+ HTMLButtonElement,
+ HeaderButtonWithTextProps
+>((props: HeaderButtonWithTextProps, ref) => {
const [hover, setHover] = useState(false);
const tooltipPortalDiv = document.getElementById("tooltip-portal-div");
@@ -35,6 +38,7 @@ const HeaderButtonWithText = (props: HeaderButtonWithTextProps) => {
onClick={props.onClick}
onKeyDown={props.onKeyDown}
className={props.className}
+ ref={ref}
>
{props.children}
</HeaderButton>
@@ -47,6 +51,6 @@ const HeaderButtonWithText = (props: HeaderButtonWithTextProps) => {
)}
</>
);
-};
+});
export default HeaderButtonWithText;
diff --git a/extension/react-app/src/components/Layout.tsx b/extension/react-app/src/components/Layout.tsx
index 9ec2e671..6dbd348f 100644
--- a/extension/react-app/src/components/Layout.tsx
+++ b/extension/react-app/src/components/Layout.tsx
@@ -2,7 +2,7 @@ import styled from "styled-components";
import { defaultBorderRadius, secondaryDark, vscForeground } from ".";
import { Outlet } from "react-router-dom";
import TextDialog from "./TextDialog";
-import { useContext, useEffect, useState } from "react";
+import { useContext, useEffect } from "react";
import { GUIClientContext } from "../App";
import { useDispatch, useSelector } from "react-redux";
import { RootStore } from "../redux/store";
@@ -12,8 +12,6 @@ import {
setShowDialog,
} from "../redux/slices/uiStateSlice";
import {
- PlusIcon,
- FolderIcon,
SparklesIcon,
Cog6ToothIcon,
QuestionMarkCircleIcon,
@@ -22,6 +20,7 @@ import HeaderButtonWithText from "./HeaderButtonWithText";
import { useNavigate, useLocation } from "react-router-dom";
import ModelSelect from "./ModelSelect";
import ProgressBar from "./ProgressBar";
+import { temporarilyClearSession } from "../redux/slices/serverStateReducer";
// #region Styled Components
const FOOTER_HEIGHT = "1.8em";
@@ -112,6 +111,7 @@ const Layout = () => {
event.code === "KeyN" &&
timeline.filter((n) => !n.step.hide).length > 0
) {
+ dispatch(temporarilyClearSession(false));
client?.loadSession(undefined);
}
if ((event.metaKey || event.ctrlKey) && event.code === "KeyC") {
diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx
index 4b602619..4e13428d 100644
--- a/extension/react-app/src/components/PillButton.tsx
+++ b/extension/react-app/src/components/PillButton.tsx
@@ -196,6 +196,7 @@ const PillButton = (props: PillButtonProps) => {
<>
<CircleDiv
data-tooltip-id={`circle-div-${props.item.description.name}`}
+ className="z-10"
>
<ExclamationTriangleIcon
style={{ margin: "auto" }}
diff --git a/extension/react-app/src/components/Suggestions.tsx b/extension/react-app/src/components/Suggestions.tsx
index 1709288c..c9d30de6 100644
--- a/extension/react-app/src/components/Suggestions.tsx
+++ b/extension/react-app/src/components/Suggestions.tsx
@@ -91,16 +91,20 @@ function SuggestionsDiv(props: SuggestionsDivProps) {
const stageDescriptions = [
<p>Ask a question</p>,
- <ol>
- <li>Highlight code in the editor</li>
- <li>Press cmd+M to select the code</li>
- <li>Ask a question</li>
- </ol>,
- <ol>
- <li>Highlight code in the editor</li>
- <li>Press cmd+shift+M to select the code</li>
- <li>Request and edit</li>
- </ol>,
+ <p>
+ 1. Highlight code in the editor
+ <br />
+ 2. Press cmd+M to select the code
+ <br />
+ 3. Ask a question
+ </p>,
+ <p>
+ 1. Highlight code in the editor
+ <br />
+ 2. Press cmd+shift+M to select the code
+ <br />
+ 3. Request an edit
+ </p>,
];
const suggestionsStages: any[][] = [
@@ -178,7 +182,7 @@ function SuggestionsArea(props: { onClick: (textInput: string) => void }) {
const inputs = timeline.filter(
(node) => !node.step.hide && node.step.name === "User Input"
);
- return inputs.length - numTutorialInputs === 0;
+ return inputs.length - numTutorialInputs <= 0;
}, [timeline, numTutorialInputs]);
return (
@@ -187,9 +191,9 @@ function SuggestionsArea(props: { onClick: (textInput: string) => void }) {
<TutorialDiv>
<div className="flex">
<SparklesIcon width="1.3em" height="1.3em" color="yellow" />
- <b className="ml-1">Tutorial</b>
+ <b className="ml-1">Tutorial ({stage + 1}/3)</b>
</div>
- <p style={{ color: lightGray }}>
+ <p style={{ color: vscForeground, paddingLeft: "4px" }}>
{stage < suggestionsStages.length &&
suggestionsStages[stage][0]?.title}
</p>
diff --git a/extension/react-app/src/components/UserInputContainer.tsx b/extension/react-app/src/components/UserInputContainer.tsx
index 76a3c615..15f1752f 100644
--- a/extension/react-app/src/components/UserInputContainer.tsx
+++ b/extension/react-app/src/components/UserInputContainer.tsx
@@ -103,7 +103,7 @@ const StyledDiv = styled.div<{ editing: boolean }>`
z-index: 1;
overflow: hidden;
display: grid;
- grid-template-columns: auto 1fr;
+ grid-template-columns: 1fr auto;
outline: ${(props) => (props.editing ? `1px solid ${lightGray}` : "none")};
cursor: text;
@@ -114,7 +114,7 @@ const DeleteButtonDiv = styled.div`
top: 8px;
right: 8px;
background-color: ${secondaryDark};
- box-shadow: 2px 2px 10px ${secondaryDark};
+ box-shadow: 4px 4px 10px ${secondaryDark};
border-radius: ${defaultBorderRadius};
`;
@@ -123,6 +123,7 @@ const GridDiv = styled.div`
grid-template-columns: auto 1fr;
grid-gap: 8px;
align-items: center;
+ width: 100%;
`;
function stringWithEllipsis(str: string, maxLen: number) {
@@ -165,7 +166,7 @@ const UserInputContainer = (props: UserInputContainerProps) => {
divRef.current.innerText = prevContent;
divRef.current.blur();
}
- }, [divRef.current]);
+ }, [divRef.current, prevContent]);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@@ -190,6 +191,10 @@ const UserInputContainer = (props: UserInputContainerProps) => {
divRef.current?.blur();
};
+ const [divTextContent, setDivTextContent] = useState("");
+
+ const checkButtonRef = useRef<HTMLButtonElement>(null);
+
return (
<GradientBorder
loading={props.active}
@@ -243,12 +248,13 @@ const UserInputContainer = (props: UserInputContainerProps) => {
padding: "8px",
paddingTop: "4px",
paddingBottom: "4px",
+ width: "100%",
}}
>
<div
ref={divRef}
- onBlur={() => {
- onBlur();
+ onBlur={(e) => {
+ if (e.relatedTarget !== checkButtonRef.current) onBlur();
}}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
@@ -256,6 +262,9 @@ const UserInputContainer = (props: UserInputContainerProps) => {
doneEditing(e);
}
}}
+ onInput={(e) => {
+ setDivTextContent((e.target as any).innerText);
+ }}
contentEditable={true}
suppressContentEditableWarning={true}
className="mr-6 ml-1 cursor-text w-full py-2 flex items-center content-center outline-none"
@@ -271,10 +280,24 @@ const UserInputContainer = (props: UserInputContainerProps) => {
<HeaderButtonWithText
onClick={(e) => {
doneEditing(e);
+ e.stopPropagation();
}}
text="Done"
+ disabled={
+ divTextContent === "" || divTextContent === prevContent
+ }
+ ref={checkButtonRef}
>
- <CheckIcon width="1.4em" height="1.4em" />
+ <CheckIcon
+ width="1.4em"
+ height="1.4em"
+ color={
+ divTextContent === "" ||
+ divTextContent === prevContent
+ ? lightGray
+ : vscForeground
+ }
+ />
</HeaderButtonWithText>
) : (
<>
diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts
index 6f5a2f37..510740f8 100644
--- a/extension/react-app/src/components/index.ts
+++ b/extension/react-app/src/components/index.ts
@@ -178,7 +178,7 @@ export const HeaderButton = styled.button<{ inverted: boolean | undefined }>`
border: none;
border-radius: ${defaultBorderRadius};
- cursor: pointer;
+ cursor: ${({ disabled }) => (disabled ? "default" : "pointer")};
&:hover {
background-color: ${({ inverted }) =>
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index 78b7a970..f6a09bbc 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -29,6 +29,7 @@ import {
import RingLoader from "../components/RingLoader";
import {
setServerState,
+ temporarilyClearSession,
temporarilyPushToUserInputQueue,
} from "../redux/slices/serverStateReducer";
import TimelineItem from "../components/TimelineItem";
@@ -68,7 +69,7 @@ const TitleTextInput = styled(TextInput)`
padding-bottom: 6px;
&:focus {
- outline: 1px solid ${lightGray};
+ outline: none;
}
`;
@@ -86,11 +87,11 @@ const StepsDiv = styled.div`
&::before {
content: "";
position: absolute;
- height: calc(100% - 24px);
+ height: calc(100% - 12px);
border-left: 2px solid ${lightGray};
left: 28px;
z-index: 0;
- bottom: 24px;
+ bottom: 12px;
}
`;
@@ -517,20 +518,30 @@ function GUI(props: GUIProps) {
value={sessionTitleInput}
onChange={(e) => setSessionTitleInput(e.target.value)}
onBlur={(e) => {
+ if (
+ e.target.value === sessionTitle ||
+ (typeof sessionTitle === "undefined" &&
+ e.target.value === "New Session")
+ )
+ return;
client?.setCurrentSessionTitle(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
(e.target as any).blur();
+ } else if (e.key === "Escape") {
+ setSessionTitleInput(sessionTitle || "New Session");
+ (e.target as any).blur();
}
}}
/>
- <div className="flex">
+ <div className="flex gap-2">
{history.timeline.filter((n) => !n.step.hide).length > 0 && (
<HeaderButtonWithText
onClick={() => {
if (history.timeline.filter((n) => !n.step.hide).length > 0) {
+ dispatch(temporarilyClearSession(false));
client?.loadSession(undefined);
}
}}
diff --git a/extension/react-app/src/pages/history.tsx b/extension/react-app/src/pages/history.tsx
index b6de0520..b4f80d70 100644
--- a/extension/react-app/src/pages/history.tsx
+++ b/extension/react-app/src/pages/history.tsx
@@ -26,6 +26,17 @@ const parseDate = (date: string): Date => {
return dateObj;
};
+const SectionHeader = styled.tr`
+ padding: 4px;
+ padding-left: 16px;
+ padding-right: 16px;
+ background-color: ${secondaryDark};
+ width: 100%;
+ font-weight: bold;
+ text-align: center;
+ margin: 0;
+`;
+
const TdDiv = styled.div`
cursor: pointer;
padding-left: 1rem;
@@ -44,6 +55,9 @@ function History() {
const navigate = useNavigate();
const dispatch = useDispatch();
const [sessions, setSessions] = useState<SessionInfo[]>([]);
+ const [filteredAndSortedSessions, setFilteredAndSortedSessions] = useState<
+ SessionInfo[]
+ >([]);
const client = useContext(GUIClientContext);
const apiUrl = useSelector((state: RootStore) => state.config.apiUrl);
const workspacePaths = useSelector(
@@ -69,6 +83,32 @@ function History() {
fetchSessions();
}, [client]);
+ useEffect(() => {
+ setFilteredAndSortedSessions(
+ sessions
+ .filter((session) => {
+ if (
+ !filteringByWorkspace ||
+ typeof workspacePaths === "undefined" ||
+ typeof session.workspace_directory === "undefined"
+ ) {
+ return true;
+ }
+ return workspacePaths.includes(session.workspace_directory);
+ })
+ .sort(
+ (a, b) =>
+ parseDate(b.date_created).getTime() -
+ parseDate(a.date_created).getTime()
+ )
+ );
+ }, [filteringByWorkspace, sessions]);
+
+ const yesterday = new Date(Date.now() - 1000 * 60 * 60 * 24);
+ const lastWeek = new Date(Date.now() - 1000 * 60 * 60 * 24 * 7);
+ const lastMonth = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30);
+ const earlier = new Date(0);
+
return (
<div className="overflow-y-scroll">
<div className="sticky top-0" style={{ backgroundColor: vscBackground }}>
@@ -116,52 +156,56 @@ function History() {
<div>
<table className="w-full">
<tbody>
- {sessions
- .filter((session) => {
- if (
- !filteringByWorkspace ||
- typeof workspacePaths === "undefined" ||
- typeof session.workspace_directory === "undefined"
- ) {
- return true;
- }
- return workspacePaths.includes(session.workspace_directory);
- })
- .sort(
- (a, b) =>
- parseDate(b.date_created).getTime() -
- parseDate(a.date_created).getTime()
- )
- .map((session, index) => (
- <Tr key={index}>
- <td>
- <TdDiv
- onClick={() => {
- client?.loadSession(session.session_id);
- dispatch(temporarilyClearSession());
- navigate("/");
- }}
- >
- <div className="text-md">{session.title}</div>
- <div className="text-gray-400">
- {parseDate(session.date_created).toLocaleString(
- "en-US",
- {
+ {filteredAndSortedSessions.map((session, index) => {
+ const prevDate =
+ index > 0
+ ? parseDate(filteredAndSortedSessions[index - 1].date_created)
+ : earlier;
+ const date = parseDate(session.date_created);
+ return (
+ <>
+ {index === 0 && date > yesterday && (
+ <SectionHeader>Today</SectionHeader>
+ )}
+ {date < yesterday &&
+ date > lastWeek &&
+ prevDate > yesterday && (
+ <SectionHeader>This Week</SectionHeader>
+ )}
+ {date < lastWeek &&
+ date > lastMonth &&
+ prevDate > lastWeek && (
+ <SectionHeader>This Month</SectionHeader>
+ )}
+
+ <Tr key={index}>
+ <td>
+ <TdDiv
+ onClick={() => {
+ client?.loadSession(session.session_id);
+ dispatch(temporarilyClearSession(true));
+ navigate("/");
+ }}
+ >
+ <div className="text-md">{session.title}</div>
+ <div className="text-gray-400">
+ {date.toLocaleString("en-US", {
year: "2-digit",
month: "2-digit",
day: "2-digit",
hour: "numeric",
minute: "2-digit",
hour12: true,
- }
- )}
- {" | "}
- {lastPartOfPath(session.workspace_directory || "")}/
- </div>
- </TdDiv>
- </td>
- </Tr>
- ))}
+ })}
+ {" | "}
+ {lastPartOfPath(session.workspace_directory || "")}/
+ </div>
+ </TdDiv>
+ </td>
+ </Tr>
+ </>
+ );
+ })}
</tbody>
</table>
<br />
diff --git a/extension/react-app/src/pages/models.tsx b/extension/react-app/src/pages/models.tsx
index 1a6f275b..ea8e28d6 100644
--- a/extension/react-app/src/pages/models.tsx
+++ b/extension/react-app/src/pages/models.tsx
@@ -145,6 +145,7 @@ function Models() {
style={{
borderBottom: `0.5px solid ${lightGray}`,
backgroundColor: vscBackground,
+ zIndex: 2,
}}
>
<ArrowLeftIcon
diff --git a/extension/react-app/src/redux/slices/serverStateReducer.ts b/extension/react-app/src/redux/slices/serverStateReducer.ts
index 3a2e455a..9b3a780c 100644
--- a/extension/react-app/src/redux/slices/serverStateReducer.ts
+++ b/extension/react-app/src/redux/slices/serverStateReducer.ts
@@ -98,11 +98,11 @@ export const serverStateSlice = createSlice({
temporarilyPushToUserInputQueue: (state, action) => {
state.user_input_queue = [...state.user_input_queue, action.payload];
},
- temporarilyClearSession: (state) => {
+ temporarilyClearSession: (state, action) => {
state.history.timeline = [];
state.selected_context_items = [];
state.session_info = {
- title: "Loading session...",
+ title: action.payload ? "Loading session..." : "New Session",
session_id: "",
date_created: "",
};