Skip to content

feat: prompt blocks #4982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 4, 2025
1 change: 1 addition & 0 deletions core/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DataDestination, ModelRole, Rule } from "@continuedev/config-yaml";
import Parser from "web-tree-sitter";
import { GetGhTokenArgs } from "./protocol/ide";

declare global {
interface Window {
ide?: "vscode";
Expand Down
9 changes: 6 additions & 3 deletions gui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RouterProvider, createMemoryRouter } from "react-router-dom";
import Layout from "./components/Layout";
import { MainEditorProvider } from "./components/mainInput/TipTapEditor";
import { SubmenuContextProvidersProvider } from "./context/SubmenuContextProviders";
import { VscThemeProvider } from "./context/VscTheme";
import useSetup from "./hooks/useSetup";
Expand Down Expand Up @@ -57,9 +58,11 @@ function SetupListeners() {
function App() {
return (
<VscThemeProvider>
<SubmenuContextProvidersProvider>
<RouterProvider router={router} />
</SubmenuContextProvidersProvider>
<MainEditorProvider>
<SubmenuContextProvidersProvider>
<RouterProvider router={router} />
</SubmenuContextProvidersProvider>
</MainEditorProvider>
<SetupListeners />
</VscThemeProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import { SlashCommandDescription } from "core";
import { useState } from "react";
import { useBookmarkedSlashCommands } from "../../hooks/useBookmarkedSlashCommands";
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
import { setMainEditorContentTrigger } from "../../redux/slices/sessionSlice";
import { getParagraphNodeFromString } from "../mainInput/utils";
import { useMainEditor } from "../mainInput/TipTapEditor";
import { ConversationStarterCard } from "./ConversationStarterCard";

const NUM_CARDS_TO_RENDER = 5;

/**
* Displays a grid of conversation starter cards for bookmarked slash commands
*/
export function ConversationStarterCards() {
const dispatch = useAppDispatch();
const { mainEditor } = useMainEditor();
const [showAll, setShowAll] = useState(false);
const { isCommandBookmarked } = useBookmarkedSlashCommands();
const slashCommands = useAppSelector(
Expand All @@ -21,11 +24,7 @@ export function ConversationStarterCards() {
);

function onClick(command: SlashCommandDescription) {
if (command.prompt) {
dispatch(
setMainEditorContentTrigger(getParagraphNodeFromString(command.prompt)),
);
}
mainEditor?.commands.insertPrompt(command);
}

if (bookmarkedSlashCommands.length === 0) {
Expand Down
5 changes: 3 additions & 2 deletions gui/src/components/FileIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { themeIcons } from "seti-file-icons";

export interface FileIconProps {
filename: string;
height: string;
width: string;
height: `${number}px`;
width: `${number}px`;
}

export default function FileIcon({ filename, height, width }: FileIconProps) {
const file = useMemo(() => {
if (filename.includes(" (")) {
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/gui/HeaderButtonWithToolTip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const HeaderButtonWithToolTip = React.forwardRef<
</HeaderButton>

<ToolTip id={tooltipId} place={props.tooltipPlacement ?? "bottom"}>
{props.text}
<span className="text-xs">{props.text}</span>
</ToolTip>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/mainInput/ContinueInputBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { selectSlashCommandComboBoxInputs } from "../../redux/selectors";
import ContextItemsPeek from "./belowMainInput/ContextItemsPeek";
import { ToolbarOptions } from "./InputToolbar";
import { Lump } from "./Lump";
import TipTapEditor from "./tiptap/TipTapEditor";
import { TipTapEditor } from "./TipTapEditor";

interface ContinueInputBoxProps {
isEditMode?: boolean;
Expand Down
37 changes: 17 additions & 20 deletions gui/src/components/mainInput/Lump/BlockSettingsTopToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import {
WrenchScrewdriverIcon,
} from "@heroicons/react/24/outline";
import { vscBadgeBackground, vscBadgeForeground } from "../..";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
import { toggleBlockSettingsToolbar } from "../../../redux/slices/uiSlice";
import { fontSize } from "../../../util";
import AssistantSelect from "../../modelSelection/platform/AssistantSelect";
import HoverItem from "../InputToolbar/HoverItem";
import { useLump } from "./LumpContext";

interface BlockSettingsToolbarIcon {
tooltip: string;
Expand Down Expand Up @@ -81,47 +80,45 @@ function BlockSettingsToolbarIcon(props: BlockSettingsToolbarIcon) {
);
}

interface BlockSettingsTopToolbarProps {
selectedSection: string | null;
setSelectedSection: (value: string | null) => void;
}
export function BlockSettingsTopToolbar() {
const {
isToolbarExpanded,
toggleToolbar,
selectedSection,
setSelectedSection,
} = useLump();

export function BlockSettingsTopToolbar(props: BlockSettingsTopToolbarProps) {
const isExpanded = useAppSelector(
(state) => state.ui.isBlockSettingsToolbarExpanded,
);
const dispatch = useAppDispatch();
const handleEllipsisClick = () => {
if (isExpanded) {
props.setSelectedSection(null);
if (isToolbarExpanded) {
setSelectedSection(null);
}
dispatch(toggleBlockSettingsToolbar());
toggleToolbar();
};

return (
<div className="flex w-full items-center justify-between">
<div className="xs:flex hidden items-center justify-center text-gray-400">
<BlockSettingsToolbarIcon
className="-ml-1.5"
icon={isExpanded ? ChevronLeftIcon : EllipsisHorizontalIcon}
tooltip={isExpanded ? "Collapse sections" : "Expand sections"}
icon={isToolbarExpanded ? ChevronLeftIcon : EllipsisHorizontalIcon}
tooltip={isToolbarExpanded ? "Collapse sections" : "Expand sections"}
isSelected={false}
onClick={handleEllipsisClick}
/>
<div
className="flex overflow-hidden transition-all duration-200"
style={{ width: isExpanded ? `160px` : "0px" }}
style={{ width: isToolbarExpanded ? `160px` : "0px" }}
>
<div className="flex">
{sections.map((section) => (
<BlockSettingsToolbarIcon
key={section.id}
icon={section.icon}
tooltip={section.tooltip}
isSelected={props.selectedSection === section.id}
isSelected={selectedSection === section.id}
onClick={() =>
props.setSelectedSection(
props.selectedSection === section.id ? null : section.id,
setSelectedSection(
selectedSection === section.id ? null : section.id,
)
}
/>
Expand Down
115 changes: 115 additions & 0 deletions gui/src/components/mainInput/Lump/LumpContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Provides context for controlling the Lump component's visibility state
*/
import {
createContext,
ReactNode,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { useAppSelector } from "../../../redux/hooks";

interface LumpContextType {
isLumpVisible: boolean;
selectedSection: string | null;
displayedSection: string | null;
isToolbarExpanded: boolean;
hideLump: () => void;
setSelectedSection: (section: string | null) => void;
toggleToolbar: () => void;
}

const LumpContext = createContext<LumpContextType | undefined>(undefined);

interface LumpProviderProps {
children: ReactNode;
}

/**
* Provider component that makes Lump state available to any child component
*/
export function LumpProvider({ children }: LumpProviderProps) {
const [selectedSection, setSelectedSection] = useState<string | null>(null);
const [displayedSection, setDisplayedSection] = useState<string | null>(null);
const [isVisible, setIsVisible] = useState(false);
const [isToolbarExpanded, setIsToolbarExpanded] = useState(true);
const isStreaming = useAppSelector((state) => state.session.isStreaming);

// Handle keyboard escape
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
if (event.key === "Escape" && selectedSection) {
setSelectedSection(null);
}
},
[selectedSection],
);

// Function to hide the lump
const hideLump = useCallback(() => {
setSelectedSection(null);
}, []);

// Function to toggle toolbar expanded state
const toggleToolbar = useCallback(() => {
setIsToolbarExpanded((prev) => !prev);
}, []);

// Update displayedSection and visibility when selectedSection changes
useEffect(() => {
if (selectedSection) {
setDisplayedSection(selectedSection);
setIsVisible(true);
} else {
setIsVisible(false);
const timeout = setTimeout(() => {
setDisplayedSection(null);
}, 300);
return () => clearTimeout(timeout);
}
}, [selectedSection]);

// Reset when streaming starts
useEffect(() => {
if (isStreaming && selectedSection) {
setSelectedSection(null);
}
}, [isStreaming, selectedSection]);

// Set up keyboard listener
useEffect(() => {
if (selectedSection) {
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}
}, [selectedSection, handleKeyDown]);

return (
<LumpContext.Provider
value={{
isLumpVisible: isVisible,
selectedSection,
displayedSection,
isToolbarExpanded,
hideLump,
setSelectedSection,
toggleToolbar,
}}
>
{children}
</LumpContext.Provider>
);
}

/**
* Hook that lets any component access the Lump context
*/
export function useLump() {
const context = useContext(LumpContext);
if (context === undefined) {
throw new Error("useLump must be used within a LumpProvider");
}
return context;
}
14 changes: 2 additions & 12 deletions gui/src/components/mainInput/Lump/LumpToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ const StopButton = styled.div`
cursor: pointer;
`;

interface TopToolbarProps {
selectedSection: string | null;
setSelectedSection: (value: string | null) => void;
}

function GeneratingIndicator() {
return (
<div className="text-xs text-gray-400">
Expand All @@ -35,7 +30,7 @@ function GeneratingIndicator() {
);
}

export function LumpToolbar(props: TopToolbarProps) {
export function LumpToolbar() {
const dispatch = useAppDispatch();
const ideMessenger = useContext(IdeMessengerContext);
const ttsActive = useAppSelector((state) => state.ui.ttsActive);
Expand Down Expand Up @@ -73,10 +68,5 @@ export function LumpToolbar(props: TopToolbarProps) {
);
}

return (
<BlockSettingsTopToolbar
selectedSection={props.selectedSection}
setSelectedSection={props.setSelectedSection}
/>
);
return <BlockSettingsTopToolbar />;
}
Loading
Loading