-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat: preserve unsent chat input across reloads #8237
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -227,6 +227,9 @@ export interface WebviewMessage { | |
| | "editQueuedMessage" | ||
| | "dismissUpsell" | ||
| | "getDismissedUpsells" | ||
| | "persistDraft" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's an inconsistency in the naming of these message types: the new key is |
||
| | "clearPersistedDraft" | ||
| | "requestPersistedDraft" | ||
| text?: string | ||
| editedMessageContent?: string | ||
| tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -171,6 +171,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro | |
| // Has to be after api_req_finished are all reduced into api_req_started messages. | ||
| const apiMetrics = useMemo(() => getApiMetrics(modifiedMessages), [modifiedMessages]) | ||
|
|
||
| // Initialize input value from persisted draft if available | ||
| const [inputValue, setInputValue] = useState("") | ||
| const inputValueRef = useRef(inputValue) | ||
| const textAreaRef = useRef<HTMLTextAreaElement>(null) | ||
|
|
@@ -224,6 +225,28 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro | |
| inputValueRef.current = inputValue | ||
| }, [inputValue]) | ||
|
|
||
| // Load persisted draft on mount | ||
| useEffect(() => { | ||
| // Request the persisted draft from the extension | ||
| vscode.postMessage({ type: "requestPersistedDraft" }) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Race condition risk: There's no guarantee the extension will respond before the user starts typing, potentially overwriting their input with an older draft. Consider adding a flag to prevent overwriting if the user has already started typing. |
||
| }, []) | ||
|
|
||
| // Save draft when input changes (debounced) | ||
| useEffect(() => { | ||
| // Don't save empty drafts or when there's an active task | ||
| if (!task && (inputValue.trim() || selectedImages.length > 0)) { | ||
| const timer = setTimeout(() => { | ||
| vscode.postMessage({ | ||
| type: "persistDraft", | ||
| text: inputValue, | ||
| images: selectedImages, | ||
| }) | ||
| }, 500) // Debounce for 500ms | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential memory leak: This useEffect creates a new timeout on every inputValue or selectedImages change but only cleans up the last one. If the user types quickly, multiple timeouts could accumulate. Consider using a ref to track the timeout or a proper debounce hook.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded magic number: Consider extracting this debounce delay as a named constant for better maintainability: |
||
|
|
||
| return () => clearTimeout(timer) | ||
| } | ||
| }, [inputValue, selectedImages, task]) | ||
|
|
||
| useEffect(() => { | ||
| isMountedRef.current = true | ||
| return () => { | ||
|
|
@@ -582,6 +605,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro | |
| // setPrimaryButtonText(undefined) | ||
| // setSecondaryButtonText(undefined) | ||
| disableAutoScrollRef.current = false | ||
|
|
||
| // Clear the persisted draft when chat is reset (task started) | ||
| vscode.postMessage({ type: "clearPersistedDraft" }) | ||
| }, []) | ||
|
|
||
| /** | ||
|
|
@@ -793,6 +819,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro | |
| break | ||
| } | ||
| break | ||
| case "persistedDraft": | ||
| // Restore the persisted draft if we don't have an active task | ||
| if (!task && message.text !== undefined) { | ||
| setInputValue(message.text) | ||
| if (message.images && message.images.length > 0) { | ||
| setSelectedImages(message.images) | ||
| } | ||
| } | ||
| break | ||
| case "selectedImages": | ||
| // Only handle selectedImages if it's not for editing context | ||
| // When context is "edit", ChatRow will handle the images | ||
|
|
@@ -845,6 +880,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro | |
| handleSetChatBoxMessage, | ||
| handlePrimaryButtonClick, | ||
| handleSecondaryButtonClick, | ||
| task, | ||
| ], | ||
| ) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling: These draft persistence operations should have try-catch blocks to handle potential failures gracefully.