Unverified 提交 e99cf759 authored 作者: Mohamed Aziz Mejri's avatar Mohamed Aziz Mejri 提交者: GitHub

Fixing regression and adding plus button to HomeChatInput (#2135)

closes #2127 This PR includes - Fixing the regression introduced in the beta release - Adding the plus button to HomeChatInput I will create a follow-up PR that makes the e2e test more realistic <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds a plus actions menu to HomeChatInput for attachments and fixes the beta regression that prevented attaching files in home chat. - **New Features** - Added AuxiliaryActionsMenu to HomeChatInput with an “Attach files” submenu; hides context picker and token toggle when not needed. - Made menu props optional (showTokenBar/toggle) and added hideContextFilesPicker, improving reuse across inputs. - **Bug Fixes** - Restored file upload by handling menu item onSelect and closing the submenu after selection; inputs reset to allow re-uploading the same file. - Updated the e2e test to open the menu, hover “Attach files,” upload via the file input, and close with Escape. <sup>Written for commit 3db5abc98c9f968cb04eb355a586209e4725be30. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Introduces a consolidated plus “AuxiliaryActionsMenu” for file/context actions in both chat inputs and fixes attachment menu interactions. > > - Adds `AuxiliaryActionsMenu` with optional `showTokenBar/toggle`, `showContextFilesPicker`, and `isStreaming`; disables attach submenu while streaming and conditionally shows context picker and token toggle > - Refactors `FileAttachmentDropdown` to render only menu items + hidden inputs, use `onSelect` to trigger file pickers, clear input values, and close parent via `onMenuClose` > - Integrates the new menu into `HomeChatInput` and `ChatInput`; keeps `ChatInputControls` and hides context picker where not needed > - Updates e2e tests to interact with the new menu, including attach-as-context, upload-to-codebase, and drag-and-drop flows; standardizes snapshots and closes menus via Escape > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6b0d1f24b069a9e9b066f8dba59a8f43af318a08. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
上级 dce37cd6
...@@ -15,10 +15,23 @@ const SNAPSHOT_NAME = "attach-image"; ...@@ -15,10 +15,23 @@ const SNAPSHOT_NAME = "attach-image";
test("attach image - home chat", async ({ po }) => { test("attach image - home chat", async ({ po }) => {
await po.setUp(); await po.setUp();
// Open auxiliary actions menu
await po await po
.getHomeChatInputContainer() .getHomeChatInputContainer()
.getByTestId("auxiliary-actions-menu")
.click();
// Hover over "Attach files" to open submenu
await po.page.getByRole("menuitem", { name: "Attach files" }).hover();
// attach via file input (click-to-upload)
await po.page
.getByTestId("chat-context-file-input") .getByTestId("chat-context-file-input")
.setInputFiles("e2e-tests/fixtures/images/logo.png"); .setInputFiles("e2e-tests/fixtures/images/logo.png");
// Close the menu by pressing Escape
await po.page.keyboard.press("Escape");
await po.sendPrompt("[dump]"); await po.sendPrompt("[dump]");
await po.snapshotServerDump("last-message", { name: SNAPSHOT_NAME }); await po.snapshotServerDump("last-message", { name: SNAPSHOT_NAME });
await po.snapshotMessages({ replaceDumpPath: true }); await po.snapshotMessages({ replaceDumpPath: true });
......
...@@ -19,14 +19,16 @@ interface AuxiliaryActionsMenuProps { ...@@ -19,14 +19,16 @@ interface AuxiliaryActionsMenuProps {
files: FileList, files: FileList,
type: "chat-context" | "upload-to-codebase", type: "chat-context" | "upload-to-codebase",
) => void; ) => void;
showTokenBar: boolean; showTokenBar?: boolean;
toggleShowTokenBar: () => void; toggleShowTokenBar?: () => void;
hideContextFilesPicker?: boolean;
} }
export function AuxiliaryActionsMenu({ export function AuxiliaryActionsMenu({
onFileSelect, onFileSelect,
showTokenBar, showTokenBar,
toggleShowTokenBar, toggleShowTokenBar,
hideContextFilesPicker,
}: AuxiliaryActionsMenuProps) { }: AuxiliaryActionsMenuProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
...@@ -47,7 +49,7 @@ export function AuxiliaryActionsMenu({ ...@@ -47,7 +49,7 @@ export function AuxiliaryActionsMenu({
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
{/* Codebase Context */} {/* Codebase Context */}
<ContextFilesPicker /> {!hideContextFilesPicker && <ContextFilesPicker />}
{/* Attach Files Submenu */} {/* Attach Files Submenu */}
<DropdownMenuSub> <DropdownMenuSub>
...@@ -58,14 +60,15 @@ export function AuxiliaryActionsMenu({ ...@@ -58,14 +60,15 @@ export function AuxiliaryActionsMenu({
<DropdownMenuSubContent> <DropdownMenuSubContent>
<FileAttachmentDropdown <FileAttachmentDropdown
onFileSelect={onFileSelect} onFileSelect={onFileSelect}
renderAsMenuItems={true} closeMenu={() => setIsOpen(false)}
/> />
</DropdownMenuSubContent> </DropdownMenuSubContent>
</DropdownMenuSub> </DropdownMenuSub>
<DropdownMenuSeparator />
{/* Toggle Token Usage */} {/* Toggle Token Usage */}
{toggleShowTokenBar && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
onClick={toggleShowTokenBar} onClick={toggleShowTokenBar}
className={`py-2 px-3 group ${showTokenBar ? "bg-primary/10 text-primary" : ""}`} className={`py-2 px-3 group ${showTokenBar ? "bg-primary/10 text-primary" : ""}`}
...@@ -83,6 +86,8 @@ export function AuxiliaryActionsMenu({ ...@@ -83,6 +86,8 @@ export function AuxiliaryActionsMenu({
{showTokenBar ? "Hide" : "Show"} token usage {showTokenBar ? "Hide" : "Show"} token usage
</span> </span>
</DropdownMenuItem> </DropdownMenuItem>
</>
)}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );
......
import { Paperclip, MessageSquare, Upload } from "lucide-react"; import { MessageSquare, Upload } from "lucide-react";
import { import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
import { useRef } from "react"; import { useRef } from "react";
interface FileAttachmentDropdownProps { interface FileAttachmentDropdownProps {
...@@ -19,16 +13,12 @@ interface FileAttachmentDropdownProps { ...@@ -19,16 +13,12 @@ interface FileAttachmentDropdownProps {
files: FileList, files: FileList,
type: "chat-context" | "upload-to-codebase", type: "chat-context" | "upload-to-codebase",
) => void; ) => void;
disabled?: boolean; closeMenu?: () => void;
className?: string;
renderAsMenuItems?: boolean;
} }
export function FileAttachmentDropdown({ export function FileAttachmentDropdown({
onFileSelect, onFileSelect,
disabled, closeMenu,
className,
renderAsMenuItems = false,
}: FileAttachmentDropdownProps) { }: FileAttachmentDropdownProps) {
const chatContextFileInputRef = useRef<HTMLInputElement>(null); const chatContextFileInputRef = useRef<HTMLInputElement>(null);
const uploadToCodebaseFileInputRef = useRef<HTMLInputElement>(null); const uploadToCodebaseFileInputRef = useRef<HTMLInputElement>(null);
...@@ -49,6 +39,8 @@ export function FileAttachmentDropdown({ ...@@ -49,6 +39,8 @@ export function FileAttachmentDropdown({
onFileSelect(e.target.files, type); onFileSelect(e.target.files, type);
// Clear the input value so the same file can be selected again // Clear the input value so the same file can be selected again
e.target.value = ""; e.target.value = "";
// Close the parent menu after file selection
closeMenu?.();
} }
}; };
...@@ -58,7 +50,12 @@ export function FileAttachmentDropdown({ ...@@ -58,7 +50,12 @@ export function FileAttachmentDropdown({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<DropdownMenuItem <DropdownMenuItem
onClick={handleChatContextClick} onSelect={(e) => {
// Prevent default so menu doesn't close in order to keep the hidden inputs in the DOM
// Manually close menu after file selection
e.preventDefault();
handleChatContextClick();
}}
className="py-3 px-4" className="py-3 px-4"
> >
<MessageSquare size={16} className="mr-2" /> <MessageSquare size={16} className="mr-2" />
...@@ -75,7 +72,12 @@ export function FileAttachmentDropdown({ ...@@ -75,7 +72,12 @@ export function FileAttachmentDropdown({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<DropdownMenuItem <DropdownMenuItem
onClick={handleUploadToCodebaseClick} onSelect={(e) => {
// Prevent default so menu doesn't close in order to keep the hidden inputs in the DOM
// Manually close menu after file selection
e.preventDefault();
handleUploadToCodebaseClick();
}}
className="py-3 px-4" className="py-3 px-4"
> >
<Upload size={16} className="mr-2" /> <Upload size={16} className="mr-2" />
...@@ -113,41 +115,9 @@ export function FileAttachmentDropdown({ ...@@ -113,41 +115,9 @@ export function FileAttachmentDropdown({
</> </>
); );
// If rendering as menu items only, return just the items and hidden inputs
if (renderAsMenuItems) {
return ( return (
<> <>
{menuItems} {menuItems}
{hiddenInputs}
</>
);
}
// Otherwise, render the full dropdown with button trigger
return (
<>
<TooltipProvider>
<Tooltip>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
disabled={disabled}
title="Attach files"
className={className}
>
<Paperclip size={20} />
</Button>
</TooltipTrigger>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">{menuItems}</DropdownMenuContent>
</DropdownMenu>
<TooltipContent>Attach files</TooltipContent>
</Tooltip>
</TooltipProvider>
{hiddenInputs} {hiddenInputs}
</> </>
); );
......
...@@ -7,13 +7,14 @@ import { useStreamChat } from "@/hooks/useStreamChat"; ...@@ -7,13 +7,14 @@ import { useStreamChat } from "@/hooks/useStreamChat";
import { useAttachments } from "@/hooks/useAttachments"; import { useAttachments } from "@/hooks/useAttachments";
import { AttachmentsList } from "./AttachmentsList"; import { AttachmentsList } from "./AttachmentsList";
import { DragDropOverlay } from "./DragDropOverlay"; import { DragDropOverlay } from "./DragDropOverlay";
import { FileAttachmentDropdown } from "./FileAttachmentDropdown";
import { usePostHog } from "posthog-js/react"; import { usePostHog } from "posthog-js/react";
import { HomeSubmitOptions } from "@/pages/home"; import { HomeSubmitOptions } from "@/pages/home";
import { ChatInputControls } from "../ChatInputControls"; import { ChatInputControls } from "../ChatInputControls";
import { LexicalChatInput } from "./LexicalChatInput"; import { LexicalChatInput } from "./LexicalChatInput";
import { useChatModeToggle } from "@/hooks/useChatModeToggle"; import { useChatModeToggle } from "@/hooks/useChatModeToggle";
import { useTypingPlaceholder } from "@/hooks/useTypingPlaceholder"; import { useTypingPlaceholder } from "@/hooks/useTypingPlaceholder";
import { AuxiliaryActionsMenu } from "./AuxiliaryActionsMenu";
export function HomeChatInput({ export function HomeChatInput({
onSubmit, onSubmit,
}: { }: {
...@@ -97,16 +98,9 @@ export function HomeChatInput({ ...@@ -97,16 +98,9 @@ export function HomeChatInput({
disableSendButton={false} disableSendButton={false}
/> />
{/* File attachment dropdown */}
<FileAttachmentDropdown
className="mt-1 mr-1"
onFileSelect={handleFileSelect}
disabled={isStreaming}
/>
{isStreaming ? ( {isStreaming ? (
<button <button
className="px-2 py-2 mt-1 mr-2 text-(--sidebar-accent-fg) rounded-lg opacity-50 cursor-not-allowed" // Indicate disabled state className="px-2 py-2 mt-1 mr-1 text-(--sidebar-accent-fg) rounded-lg opacity-50 cursor-not-allowed" // Indicate disabled state
title="Cancel generation (unavailable here)" title="Cancel generation (unavailable here)"
> >
<StopCircleIcon size={20} /> <StopCircleIcon size={20} />
...@@ -115,15 +109,22 @@ export function HomeChatInput({ ...@@ -115,15 +109,22 @@ export function HomeChatInput({
<button <button
onClick={handleCustomSubmit} onClick={handleCustomSubmit}
disabled={!inputValue.trim() && attachments.length === 0} disabled={!inputValue.trim() && attachments.length === 0}
className="px-2 py-2 mt-1 mr-2 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50" className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
title="Send message" title="Send message"
> >
<SendIcon size={20} /> <SendIcon size={20} />
</button> </button>
)} )}
</div> </div>
<div className="px-2 pb-2"> <div className="pl-2 pr-1 flex items-center justify-between pb-2">
<ChatInputControls /> <div className="flex items-center">
<ChatInputControls showContextFilesPicker={false} />
</div>
<AuxiliaryActionsMenu
onFileSelect={handleFileSelect}
hideContextFilesPicker
/>
</div> </div>
</div> </div>
</div> </div>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论