Skip to content

Commit 7edeb0d

Browse files
committed
fix: preserve user messages at choice prompts in history (fixes #7316)
- Added local message history buffer in usePromptHistory hook to store messages immediately upon sending - Modified ChatTextArea to save messages to local history before clearing input field - Ensured messages typed at follow-up question prompts are preserved and accessible via up-arrow navigation - Added deduplication logic to prevent duplicate entries when messages are confirmed by backend This fix ensures that user messages are never lost, even if typed at choice prompts or if backend processing fails.
1 parent 9b8f3b9 commit 7edeb0d

File tree

2 files changed

+57
-10
lines changed

2 files changed

+57
-10
lines changed

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,14 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
215215
const [isFocused, setIsFocused] = useState(false)
216216

217217
// Use custom hook for prompt history navigation
218-
const { handleHistoryNavigation, resetHistoryNavigation, resetOnInputChange } = usePromptHistory({
219-
clineMessages,
220-
taskHistory,
221-
cwd,
222-
inputValue,
223-
setInputValue,
224-
})
218+
const { handleHistoryNavigation, resetHistoryNavigation, resetOnInputChange, addToLocalHistory } =
219+
usePromptHistory({
220+
clineMessages,
221+
taskHistory,
222+
cwd,
223+
inputValue,
224+
setInputValue,
225+
})
225226

226227
// Fetch git commits when Git is selected or when typing a hash.
227228
useEffect(() => {
@@ -465,6 +466,11 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
465466
if (event.key === "Enter" && !event.shiftKey && !isComposing) {
466467
event.preventDefault()
467468

469+
// Add to local history before sending
470+
if (inputValue.trim()) {
471+
addToLocalHistory(inputValue.trim())
472+
}
473+
468474
// Always call onSend - let ChatView handle queueing when disabled
469475
resetHistoryNavigation()
470476
onSend()
@@ -531,6 +537,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
531537
handleHistoryNavigation,
532538
resetHistoryNavigation,
533539
commands,
540+
addToLocalHistory,
534541
],
535542
)
536543

webview-ui/src/components/chat/hooks/usePromptHistory.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface UsePromptHistoryReturn {
2222
) => boolean
2323
resetHistoryNavigation: () => void
2424
resetOnInputChange: () => void
25+
addToLocalHistory: (message: string) => void
2526
}
2627

2728
export const usePromptHistory = ({
@@ -38,6 +39,8 @@ export const usePromptHistory = ({
3839
const [historyIndex, setHistoryIndex] = useState(-1)
3940
const [tempInput, setTempInput] = useState("")
4041
const [promptHistory, setPromptHistory] = useState<string[]>([])
42+
// Local sent messages that haven't been confirmed by backend yet
43+
const [localSentMessages, setLocalSentMessages] = useState<string[]>([])
4144

4245
// Initialize prompt history with hybrid approach: conversation messages if in task, otherwise task history
4346
const filteredPromptHistory = useMemo(() => {
@@ -46,9 +49,30 @@ export const usePromptHistory = ({
4649
?.filter((message) => message.type === "say" && message.say === "user_feedback" && message.text?.trim())
4750
.map((message) => message.text!)
4851

49-
// If we have conversation messages, use those (newest first when navigating up)
52+
// Combine conversation prompts with local sent messages (deduplicated)
53+
const allPrompts: string[] = []
54+
const seen = new Set<string>()
55+
56+
// Add conversation prompts first
5057
if (conversationPrompts?.length) {
51-
return conversationPrompts.slice(-MAX_PROMPT_HISTORY_SIZE).reverse()
58+
conversationPrompts.forEach((prompt) => {
59+
if (!seen.has(prompt)) {
60+
seen.add(prompt)
61+
allPrompts.push(prompt)
62+
}
63+
})
64+
}
65+
66+
// Add local sent messages that aren't in conversation yet
67+
localSentMessages.forEach((msg) => {
68+
if (!seen.has(msg)) {
69+
allPrompts.push(msg)
70+
}
71+
})
72+
73+
// If we have any prompts, use those (newest first when navigating up)
74+
if (allPrompts.length) {
75+
return allPrompts.slice(-MAX_PROMPT_HISTORY_SIZE).reverse()
5276
}
5377

5478
// If we have clineMessages array (meaning we're in an active task), don't fall back to task history
@@ -67,7 +91,7 @@ export const usePromptHistory = ({
6791
.filter((item) => item.task?.trim() && (!item.workspace || item.workspace === cwd))
6892
.map((item) => item.task)
6993
.slice(0, MAX_PROMPT_HISTORY_SIZE)
70-
}, [clineMessages, taskHistory, cwd])
94+
}, [clineMessages, taskHistory, cwd, localSentMessages])
7195

7296
// Update prompt history when filtered history changes and reset navigation
7397
useEffect(() => {
@@ -175,6 +199,21 @@ export const usePromptHistory = ({
175199
setTempInput("")
176200
}, [])
177201

202+
// Add a message to local sent history
203+
const addToLocalHistory = useCallback((message: string) => {
204+
if (message?.trim()) {
205+
setLocalSentMessages((prev) => [...prev, message].slice(-MAX_PROMPT_HISTORY_SIZE))
206+
}
207+
}, [])
208+
209+
// Clear local sent messages when task changes
210+
useEffect(() => {
211+
// When clineMessages changes significantly (new task), clear local messages
212+
if (!clineMessages?.length) {
213+
setLocalSentMessages([])
214+
}
215+
}, [clineMessages?.length])
216+
178217
return {
179218
historyIndex,
180219
setHistoryIndex,
@@ -184,5 +223,6 @@ export const usePromptHistory = ({
184223
handleHistoryNavigation,
185224
resetHistoryNavigation,
186225
resetOnInputChange,
226+
addToLocalHistory,
187227
}
188228
}

0 commit comments

Comments
 (0)