Skip to content

Commit cb67ebd

Browse files
committed
Add prompt history to task input and fix command center text selection
1 parent 369295d commit cb67ebd

7 files changed

Lines changed: 88 additions & 27 deletions

File tree

apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ function GridCell({
6060

6161
const handleCellClick = useCallback(() => {
6262
setActiveTask(cell.taskId);
63+
const selection = window.getSelection();
64+
if (selection && !selection.isCollapsed) return;
6365
const actionSelector =
6466
cellRef.current?.querySelector<HTMLElement>("[tabindex='0']");
6567
actionSelector?.focus();

apps/code/src/renderer/features/message-editor/components/MessageEditor.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import "./message-editor.css";
22
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
33
import { BranchSelector } from "@features/git-interaction/components/BranchSelector";
44
import { useGitQueries } from "@features/git-interaction/hooks/useGitQueries";
5+
import { getUserPromptsForTask } from "@features/sessions/stores/sessionStore";
56
import { useConnectivity } from "@hooks/useConnectivity";
67
import { ArrowUp, Circle, Stop } from "@phosphor-icons/react";
78
import { Flex, IconButton, Text, Tooltip } from "@radix-ui/themes";
89
import { EditorContent } from "@tiptap/react";
910
import { hasOpenOverlay } from "@utils/overlay";
10-
import { forwardRef, useEffect, useImperativeHandle } from "react";
11+
import { forwardRef, useCallback, useEffect, useImperativeHandle } from "react";
1112
import { useHotkeys } from "react-hotkeys-hook";
1213
import { useDraftStore } from "../stores/draftStore";
1314
import { useTiptapEditor } from "../tiptap/useTiptapEditor";
@@ -167,6 +168,11 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
167168
const cloudDiffStats = context?.cloudDiffStats;
168169
const isSubmitDisabled = disabled || !isOnline;
169170

171+
const getPromptHistory = useCallback(
172+
() => getUserPromptsForTask(taskId),
173+
[taskId],
174+
);
175+
170176
const {
171177
editor,
172178
isReady,
@@ -192,6 +198,7 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
192198
isLoading,
193199
autoFocus,
194200
context: { taskId, repoPath },
201+
getPromptHistory,
195202
onSubmit,
196203
onBashCommand,
197204
onBashModeChange,

apps/code/src/renderer/features/message-editor/stores/promptHistoryStore.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
import { getUserPromptsForTask } from "@features/sessions/stores/sessionStore";
21
import { create } from "zustand";
32

43
interface PromptHistoryStore {
54
index: number;
65
savedInput: string;
7-
navigateUp: (taskId: string, currentInput: string) => string | null;
8-
navigateDown: (taskId: string) => string | null;
6+
navigateUp: (history: string[], currentInput: string) => string | null;
7+
navigateDown: (history: string[]) => string | null;
98
reset: () => void;
109
}
1110

1211
export const usePromptHistoryStore = create<PromptHistoryStore>((set, get) => ({
1312
index: -1,
1413
savedInput: "",
1514

16-
navigateUp: (taskId, currentInput) => {
17-
const history = getUserPromptsForTask(taskId);
15+
navigateUp: (history, currentInput) => {
1816
if (history.length === 0) return null;
1917

2018
const { index } = get();
@@ -31,12 +29,10 @@ export const usePromptHistoryStore = create<PromptHistoryStore>((set, get) => ({
3129
return history[history.length - 1 - newIndex] ?? null;
3230
},
3331

34-
navigateDown: (taskId) => {
32+
navigateDown: (history) => {
3533
const { index, savedInput } = get();
3634
if (index === -1) return null;
3735

38-
const history = getUserPromptsForTask(taskId);
39-
4036
if (index > 0) {
4137
const newIndex = index - 1;
4238
set({ index: newIndex });
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { create } from "zustand";
2+
import { persist } from "zustand/middleware";
3+
4+
interface TaskInputHistoryState {
5+
prompts: string[];
6+
}
7+
8+
interface TaskInputHistoryActions {
9+
addPrompt: (prompt: string) => void;
10+
}
11+
12+
type TaskInputHistoryStore = TaskInputHistoryState & TaskInputHistoryActions;
13+
14+
const MAX_HISTORY = 50;
15+
16+
export const useTaskInputHistoryStore = create<TaskInputHistoryStore>()(
17+
persist(
18+
(set) => ({
19+
prompts: [],
20+
addPrompt: (prompt) =>
21+
set((state) => {
22+
const trimmed = prompt.trim();
23+
if (!trimmed) return state;
24+
const filtered = state.prompts.filter((p) => p !== trimmed);
25+
const updated = [...filtered, trimmed].slice(-MAX_HISTORY);
26+
return { prompts: updated };
27+
}),
28+
}),
29+
{
30+
name: "task-input-history",
31+
partialize: (state) => ({ prompts: state.prompts }),
32+
},
33+
),
34+
);

apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface UseTiptapEditorOptions {
2828
bashMode?: boolean;
2929
};
3030
clearOnSubmit?: boolean;
31+
getPromptHistory?: () => string[];
3132
onSubmit?: (text: string) => void;
3233
onBashCommand?: (command: string) => void;
3334
onBashModeChange?: (isBashMode: boolean) => void;
@@ -82,6 +83,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
8283
context,
8384
capabilities = {},
8485
clearOnSubmit = true,
86+
getPromptHistory,
8587
onSubmit,
8688
onBashCommand,
8789
onBashModeChange,
@@ -116,6 +118,9 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
116118
const submitDisabledRef = useRef(submitDisabled);
117119
submitDisabledRef.current = submitDisabled;
118120

121+
const getPromptHistoryRef = useRef(getPromptHistory);
122+
getPromptHistoryRef.current = getPromptHistory;
123+
119124
const prevBashModeRef = useRef(false);
120125
const prevIsEmptyRef = useRef(true);
121126
const submitRef = useRef<() => void>(() => {});
@@ -192,35 +197,38 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
192197
}
193198
}
194199

195-
if (
196-
taskId &&
197-
(event.key === "ArrowUp" || event.key === "ArrowDown")
198-
) {
200+
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
201+
const historyGetter = getPromptHistoryRef.current;
202+
if (!taskId && !historyGetter) return false;
203+
199204
const currentText = view.state.doc.textContent;
200205
const isEmpty = !currentText.trim();
201206
const { from } = view.state.selection;
202207
const isAtStart = from === 1;
203208
const isAtEnd = from === view.state.doc.content.size - 1;
204209

205210
const forceNavigate = event.shiftKey;
211+
const history = historyGetter?.() ?? [];
206212

207213
if (
208214
event.key === "ArrowUp" &&
209215
(forceNavigate || isEmpty || isAtStart)
210216
) {
211-
const queuedContent =
212-
sessionStoreSetters.dequeueMessagesAsText(taskId);
213-
if (queuedContent !== null && queuedContent !== undefined) {
214-
event.preventDefault();
215-
view.dispatch(
216-
view.state.tr
217-
.delete(1, view.state.doc.content.size - 1)
218-
.insertText(queuedContent, 1),
219-
);
220-
return true;
217+
if (taskId) {
218+
const queuedContent =
219+
sessionStoreSetters.dequeueMessagesAsText(taskId);
220+
if (queuedContent !== null && queuedContent !== undefined) {
221+
event.preventDefault();
222+
view.dispatch(
223+
view.state.tr
224+
.delete(1, view.state.doc.content.size - 1)
225+
.insertText(queuedContent, 1),
226+
);
227+
return true;
228+
}
221229
}
222230

223-
const newText = historyActions.navigateUp(taskId, currentText);
231+
const newText = historyActions.navigateUp(history, currentText);
224232
if (newText !== null) {
225233
event.preventDefault();
226234
view.dispatch(
@@ -236,7 +244,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
236244
event.key === "ArrowDown" &&
237245
(forceNavigate || isEmpty || isAtEnd)
238246
) {
239-
const newText = historyActions.navigateDown(taskId);
247+
const newText = historyActions.navigateDown(history);
240248
if (newText !== null) {
241249
event.preventDefault();
242250
view.dispatch(

apps/code/src/renderer/features/task-detail/components/TaskInputEditor.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { TourHighlight } from "@components/TourHighlight";
33
import { AttachmentsBar } from "@features/message-editor/components/AttachmentsBar";
44
import { EditorToolbar } from "@features/message-editor/components/EditorToolbar";
55
import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor";
6+
import { useTaskInputHistoryStore } from "@features/message-editor/stores/taskInputHistoryStore";
67
import { useTiptapEditor } from "@features/message-editor/tiptap/useTiptapEditor";
78
import { ReasoningLevelSelector } from "@features/sessions/components/ReasoningLevelSelector";
89
import { UnifiedModelSelector } from "@features/sessions/components/UnifiedModelSelector";
@@ -11,7 +12,7 @@ import { useConnectivity } from "@hooks/useConnectivity";
1112
import { ArrowUp } from "@phosphor-icons/react";
1213
import { Box, Flex, IconButton, Text, Tooltip } from "@radix-ui/themes";
1314
import { EditorContent } from "@tiptap/react";
14-
import { forwardRef, useImperativeHandle } from "react";
15+
import { forwardRef, useCallback, useImperativeHandle } from "react";
1516
import "./TaskInput.css";
1617

1718
interface TaskInputEditorProps {
@@ -59,6 +60,11 @@ export const TaskInputEditor = forwardRef<
5960
const { isOnline } = useConnectivity();
6061
const isSubmitDisabled = isCreatingTask || !isOnline;
6162

63+
const getPromptHistory = useCallback(
64+
() => useTaskInputHistoryStore.getState().prompts,
65+
[],
66+
);
67+
6268
const {
6369
editor,
6470
isEmpty,
@@ -74,14 +80,16 @@ export const TaskInputEditor = forwardRef<
7480
removeAttachment,
7581
} = useTiptapEditor({
7682
sessionId,
77-
placeholder: "What do you want to work on? - @ to add context",
83+
placeholder:
84+
"What do you want to work on? \u2191\u2193 for history, @ to add context",
7885
disabled: isCreatingTask,
7986
submitDisabled: !isOnline,
8087
isLoading: isCreatingTask,
8188
autoFocus,
8289
context: { repoPath, taskId: previewTaskId },
8390
capabilities: { commands: true, bashMode: false },
8491
clearOnSubmit: false,
92+
getPromptHistory,
8593
onSubmit: (text) => {
8694
if (text && canSubmit) {
8795
onSubmit();

apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useAuthStore } from "@features/auth/stores/authStore";
22
import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor";
3+
import { useTaskInputHistoryStore } from "@features/message-editor/stores/taskInputHistoryStore";
34
import {
45
contentToXml,
56
extractFilePaths,
@@ -123,6 +124,11 @@ export function useTaskCreation({
123124

124125
log.info("Submitting task", { workspaceMode, selectedDirectory });
125126

127+
const plainText = editor.getText()?.trim();
128+
if (plainText) {
129+
useTaskInputHistoryStore.getState().addPrompt(plainText);
130+
}
131+
126132
const input = prepareTaskInput(content, {
127133
selectedDirectory,
128134
selectedRepository,

0 commit comments

Comments
 (0)