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

Clean-up chat input buttons (#2016)

<!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Unified secondary chat input actions into a single “+” dropdown to declutter the UI and make codebase context, file attachments, and token usage easier to access. - **Refactors** - Added AuxiliaryActionsMenu with “Codebase context”, “Attach files”, and “Show/Hide token usage”. - Moved token usage toggle into the menu; removed the inline toggle button from ChatInput. - Updated FileAttachmentDropdown to render as menu items and support both chat-context and upload-to-codebase flows. - Converted ContextFilesPicker to a modal dialog and moved its trigger into the menu; removed the standalone context button from ChatInputControls. <sup>Written for commit 5cdb2737170edfc9aef97a52d3397295552ad2ab. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR consolidates secondary chat input actions into a unified "+" dropdown menu, improving UI organization and discoverability. The refactoring moves codebase context, file attachments, and token usage toggle from separate buttons into a single menu with proper nesting (file attachments as a submenu). **Key changes:** - Created `AuxiliaryActionsMenu` component that consolidates three previously separate UI controls - Refactored `FileAttachmentDropdown` to support dual rendering modes (standalone or as menu items) - Converted `ContextFilesPicker` trigger from button to menu item - Updated `ChatInput` to use new menu and pass `showContextFilesPicker={false}` to `ChatInputControls` - Updated e2e tests and helpers to navigate the new nested menu structure **Code quality:** - Clean component composition with proper prop drilling - Maintained backward compatibility where needed (`renderAsMenuItems` prop) - E2E tests properly updated with menu navigation and cleanup - Consistent styling and accessibility preserved <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk - Well-structured UI refactoring with comprehensive test coverage updates, no logic changes to core functionality, and proper component reusability patterns - No files require special attention <h3>Important Files Changed</h3> | Filename | Overview | |----------|----------| | src/components/chat/AuxiliaryActionsMenu.tsx | Introduced new component to consolidate secondary actions (codebase context, file attachments, token usage) into a single "+" dropdown menu | | src/components/chat/FileAttachmentDropdown.tsx | Refactored to support rendering as both standalone dropdown and as menu items within parent dropdown via `renderAsMenuItems` prop | | src/components/ContextFilesPicker.tsx | Converted trigger from standalone button with tooltip to menu item for integration into parent dropdown menu | | src/components/chat/ChatInput.tsx | Replaced individual action buttons with `AuxiliaryActionsMenu` component, passing `showContextFilesPicker={false}` to `ChatInputControls` | </details> <h3>Sequence Diagram</h3> ```mermaid sequenceDiagram participant User participant AuxiliaryActionsMenu participant DropdownMenu participant ContextFilesPicker participant FileAttachmentDropdown participant ChatInput User->>AuxiliaryActionsMenu: Click "+" button AuxiliaryActionsMenu->>DropdownMenu: Open menu alt Codebase Context User->>ContextFilesPicker: Click "Codebase context" ContextFilesPicker->>ContextFilesPicker: Open popover with context settings User->>ContextFilesPicker: Configure paths/excludes ContextFilesPicker->>ChatInput: Update context configuration end alt Attach Files User->>DropdownMenu: Hover "Attach files" DropdownMenu->>FileAttachmentDropdown: Show submenu User->>FileAttachmentDropdown: Click attachment option FileAttachmentDropdown->>FileAttachmentDropdown: Trigger hidden file input User->>FileAttachmentDropdown: Select file(s) FileAttachmentDropdown->>ChatInput: onFileSelect(files, type) ChatInput->>ChatInput: Handle file attachment end alt Toggle Token Usage User->>AuxiliaryActionsMenu: Click "Show/Hide token usage" AuxiliaryActionsMenu->>ChatInput: toggleShowTokenBar() ChatInput->>ChatInput: Update showTokenBar state ChatInput->>ChatInput: Render/hide TokenBar end ``` <!-- greptile_other_comments_section --> <!-- /greptile_comment --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Streamlines chat input by consolidating secondary actions into a single menu and updating related components and tests. > > - Adds `AuxiliaryActionsMenu` with `Codebase context`, `Attach files` submenu, and token usage toggle > - Updates `ChatInput` to use the new menu; removes inline token toggle; sets `ChatInputControls` `showContextFilesPicker=false` > - Refactors `ContextFilesPicker` to a menu-item trigger (`codebase-context-trigger`) within its `Popover` > - Enhances `FileAttachmentDropdown` to optionally `renderAsMenuItems`, retaining hidden inputs for `chat-context` and `upload-to-codebase` > - Adjusts E2E tests and helpers to open the new menu, hover "Attach files", use new triggers, and close via Escape; verifies upload-to-codebase write > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 425a26e3f2a471f5b6850148eb8ddc3737db38b0. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
上级 2dc478cd
...@@ -28,11 +28,23 @@ test("attach image - chat", async ({ po }) => { ...@@ -28,11 +28,23 @@ test("attach image - chat", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("basic"); await po.sendPrompt("basic");
// attach via file input (click-to-upload) // Open auxiliary actions menu
await po await po
.getChatInputContainer() .getChatInputContainer()
.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 });
...@@ -42,11 +54,23 @@ test("attach image - chat - upload to codebase", async ({ po }) => { ...@@ -42,11 +54,23 @@ test("attach image - chat - upload to codebase", async ({ po }) => {
await po.setUp({ autoApprove: true }); await po.setUp({ autoApprove: true });
await po.sendPrompt("basic"); await po.sendPrompt("basic");
// attach via file input (click-to-upload) // Open auxiliary actions menu
await po await po
.getChatInputContainer() .getChatInputContainer()
.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("upload-to-codebase-file-input") .getByTestId("upload-to-codebase-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("[[UPLOAD_IMAGE_TO_CODEBASE]]"); await po.sendPrompt("[[UPLOAD_IMAGE_TO_CODEBASE]]");
await po.snapshotServerDump("last-message", { name: "upload-to-codebase" }); await po.snapshotServerDump("last-message", { name: "upload-to-codebase" });
......
...@@ -366,10 +366,27 @@ export class PageObject { ...@@ -366,10 +366,27 @@ export class PageObject {
} }
async openContextFilesPicker() { async openContextFilesPicker() {
const contextButton = this.page.getByTestId("codebase-context-button"); // Open the auxiliary actions menu
await contextButton.click(); await this.getChatInputContainer()
.getByTestId("auxiliary-actions-menu")
.click();
// Click on "Codebase context" to open the popover
await this.page.getByTestId("codebase-context-trigger").click();
// Wait for the popover content to be visible
await this.page
.getByTestId("manual-context-files-input")
.waitFor({ state: "visible" });
return new ContextFilesPickerDialog(this.page, async () => { return new ContextFilesPickerDialog(this.page, async () => {
await contextButton.click(); // Close the popover first
await this.page.keyboard.press("Escape");
// Wait a bit for the popover to close, then close the dropdown menu
await this.page
.getByTestId("manual-context-files-input")
.waitFor({ state: "hidden" });
await this.page.keyboard.press("Escape");
}); });
} }
......
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
- textbox "src/**/*.tsx" - textbox "src/**/*.tsx"
- button "Add" - button "Add"
- paragraph: Dyad will use the entire codebase as context. - paragraph: Dyad will use the entire codebase as context.
\ No newline at end of file - heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- button "Close":
- img
\ No newline at end of file
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
...@@ -10,4 +10,12 @@ ...@@ -10,4 +10,12 @@
- img - img
- text: /src\/sub\/\*\* 2 files, ~\d+ tokens/ - text: /src\/sub\/\*\* 2 files, ~\d+ tokens/
- button: - button:
- img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- button "Close":
- img - img
\ No newline at end of file
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
...@@ -11,4 +11,6 @@ ...@@ -11,4 +11,6 @@
- text: These files will be excluded from the context. - text: These files will be excluded from the context.
- img - img
- textbox "node_modules/**/*" - textbox "node_modules/**/*"
- button "Add" - button "Add"
\ No newline at end of file - button "Close":
- img
\ No newline at end of file
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
...@@ -22,4 +22,6 @@ ...@@ -22,4 +22,6 @@
- img - img
- text: manual/exclude/** 0 files, ~0 tokens - text: manual/exclude/** 0 files, ~0 tokens
- button: - button:
- img
- button "Close":
- img - img
\ No newline at end of file
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
...@@ -22,4 +22,6 @@ ...@@ -22,4 +22,6 @@
- img - img
- text: /src\/\*\* 7 files, ~\d+ tokens/ - text: /src\/\*\* 7 files, ~\d+ tokens/
- button: - button:
- img
- button "Close":
- img - img
\ No newline at end of file
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
...@@ -17,4 +17,6 @@ ...@@ -17,4 +17,6 @@
- text: These files will always be included in the context. - text: These files will always be included in the context.
- img - img
- textbox "src/**/*.config.ts" - textbox "src/**/*.config.ts"
- button "Add" - button "Add"
\ No newline at end of file - button "Close":
- img
\ No newline at end of file
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
...@@ -34,4 +34,6 @@ ...@@ -34,4 +34,6 @@
- img - img
- text: /exclude\/\*\* 2 files, ~\d+ tokens/ - text: /exclude\/\*\* 2 files, ~\d+ tokens/
- button: - button:
- img
- button "Close":
- img - img
\ No newline at end of file
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
- textbox "src/**/*.tsx" - textbox "src/**/*.tsx"
- button "Add" - button "Add"
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context. - paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3] - heading "Smart Context Auto-includes" [level=3]
- paragraph: - paragraph:
- text: These files will always be included in the context. - text: These files will always be included in the context.
- img - img
- textbox "src/**/*.config.ts" - textbox "src/**/*.config.ts"
- button "Add" - button "Add"
\ No newline at end of file - button "Close":
- img
\ No newline at end of file
...@@ -23,19 +23,19 @@ ...@@ -23,19 +23,19 @@
"versioned_files": { "versioned_files": {
"fileIdToContent": { "fileIdToContent": {
"fd0f9b44a8d543177b124c3b9b5e62da7d0e2683379352e9e2c587d285c36686": "// File contents excluded from context", "fd0f9b44a8d543177b124c3b9b5e62da7d0e2683379352e9e2c587d285c36686": "// File contents excluded from context",
"732a1ebb3b9b602d7104636b4009f8bb3f1d840c9fed3c3eb0dd56a3d299cbf2": "// a.ts\n", "27c40a1c2df213eb19562166c763e05c3b7f5107445e39005cfc452f521c66d3": "// a.ts\n",
"9a5461c208c95126e54da61c3159980e6447678d96cd065aaff1b87c3a9d1f95": "# AI_RULES.md\n", "b994bc3c3d3943db57855232d61fbc1ae6b4ce3f9847bb512c9a55f5ba089502": "# AI_RULES.md\n",
"86524c67667ac0b50cc96ad10d0e35630e81c9eee53c90bdd4576779c146773d": "// exclude.ts: this file is not in any of the globs\n", "76d20807a244c4f18ab58337e44f961ae397b2c844b538af9c7bcf64a3d07c8f": "// exclude.ts: this file is not in any of the globs\n",
"923470fd746b2d90ec177920cc2ac10f9ecefff6272e8f6fa69ea714909478ab": "// exclude.tsx: this file is not in any of the globs\n", "eb844b26f375fa895c77d30eeea5d5d7135d9658eaa731a396faf6b504652018": "// exclude.tsx: this file is not in any of the globs\n",
"075b6697b2282dfd4cce18f78bcbc51bf5f0d9c0c0911c889b9d72243b9187c3": "[\"even json is included\"]\n", "f14173ab2694d5fd20f5b010df975a1a13dbca937408e5f5674acd99b81b1c82": "[\"even json is included\"]\n",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": "",
"3d74d7280ec3f9cc2c7b73cbd4e201517b0a30f6a908cf7392adb49d61cef3fc": "// button.tsx\n", "76307ff927a50b09155f802e4443bdbcdc1d8e10eec968c1d14776f001ba2481": "// button.tsx\n",
"4478ae9a9ff89b3c68fc44309614668629664d86354a381662ffd07716ce20ce": "// helper.ts\n", "9ab36ced71e4350acb4d0ee6eac6f61ccb785050a5c9eb62a8c6bbfab9fcbbc4": "// helper.ts\n",
"2ba642df741f3bdeaf26894e4e0b56d2adacd4202977831e559ca1306e2cc039": "/* some.css */\n", "8ff2c0d1080114e4edba454b89f0d0912a11e60672b0d3b9ad964fd0c0ed3615": "/* some.css */\n",
"2f653d8827ab78f82263eb63f722ef7c6be026d4fa5630c3ce6fb94612751e0c": "// foo.ts\n", "69ca7e5750668e215f1a792ba7612d74a6f5d634ecd2f6e575c2b62377563dae": "// foo.ts\n",
"50d0d59f927dc56f5f47adc37de69fc9a3334546a54f86a08d742b6c5cef07c9": "// sub/sub1.ts\n", "0c5c20017d0aef7115d00715e4b5ab5a8b545eddfa3e981b0161bcd006902414": "// sub/sub1.ts\n",
"51304e7cdc9cba392b7ed7f7ae1b8e276bc757bfa15267214e2f20b75a2dac6f": "// sub/sub2.tsx\n", "27bce07ded1e6a9fce868247aa4dc4e4389cf0b8c810474992dc46f52638a8e9": "// sub/sub2.tsx\n",
"444fd8107293606e0d88acf691ba98fa30de47c04b58c7930b2ee3e24c6ea17d": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n" "64f67386de21966eef1092306303e8b98fe6df81bc265933e1b6650358d80f9d": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n"
}, },
"fileReferences": [ "fileReferences": [
{ {
...@@ -46,27 +46,27 @@ ...@@ -46,27 +46,27 @@
{ {
"path": "a.ts", "path": "a.ts",
"force": true, "force": true,
"fileId": "732a1ebb3b9b602d7104636b4009f8bb3f1d840c9fed3c3eb0dd56a3d299cbf2" "fileId": "27c40a1c2df213eb19562166c763e05c3b7f5107445e39005cfc452f521c66d3"
}, },
{ {
"path": "AI_RULES.md", "path": "AI_RULES.md",
"force": false, "force": false,
"fileId": "9a5461c208c95126e54da61c3159980e6447678d96cd065aaff1b87c3a9d1f95" "fileId": "b994bc3c3d3943db57855232d61fbc1ae6b4ce3f9847bb512c9a55f5ba089502"
}, },
{ {
"path": "exclude/exclude.ts", "path": "exclude/exclude.ts",
"force": false, "force": false,
"fileId": "86524c67667ac0b50cc96ad10d0e35630e81c9eee53c90bdd4576779c146773d" "fileId": "76d20807a244c4f18ab58337e44f961ae397b2c844b538af9c7bcf64a3d07c8f"
}, },
{ {
"path": "exclude/exclude.tsx", "path": "exclude/exclude.tsx",
"force": false, "force": false,
"fileId": "923470fd746b2d90ec177920cc2ac10f9ecefff6272e8f6fa69ea714909478ab" "fileId": "eb844b26f375fa895c77d30eeea5d5d7135d9658eaa731a396faf6b504652018"
}, },
{ {
"path": "manual/baz.json", "path": "manual/baz.json",
"force": true, "force": true,
"fileId": "075b6697b2282dfd4cce18f78bcbc51bf5f0d9c0c0911c889b9d72243b9187c3" "fileId": "f14173ab2694d5fd20f5b010df975a1a13dbca937408e5f5674acd99b81b1c82"
}, },
{ {
"path": "manual/file.ts", "path": "manual/file.ts",
...@@ -81,37 +81,37 @@ ...@@ -81,37 +81,37 @@
{ {
"path": "src/components/ui/button.tsx", "path": "src/components/ui/button.tsx",
"force": false, "force": false,
"fileId": "3d74d7280ec3f9cc2c7b73cbd4e201517b0a30f6a908cf7392adb49d61cef3fc" "fileId": "76307ff927a50b09155f802e4443bdbcdc1d8e10eec968c1d14776f001ba2481"
}, },
{ {
"path": "src/components/ui/helper.ts", "path": "src/components/ui/helper.ts",
"force": false, "force": false,
"fileId": "4478ae9a9ff89b3c68fc44309614668629664d86354a381662ffd07716ce20ce" "fileId": "9ab36ced71e4350acb4d0ee6eac6f61ccb785050a5c9eb62a8c6bbfab9fcbbc4"
}, },
{ {
"path": "src/dir/some.css", "path": "src/dir/some.css",
"force": false, "force": false,
"fileId": "2ba642df741f3bdeaf26894e4e0b56d2adacd4202977831e559ca1306e2cc039" "fileId": "8ff2c0d1080114e4edba454b89f0d0912a11e60672b0d3b9ad964fd0c0ed3615"
}, },
{ {
"path": "src/foo.ts", "path": "src/foo.ts",
"force": false, "force": false,
"fileId": "2f653d8827ab78f82263eb63f722ef7c6be026d4fa5630c3ce6fb94612751e0c" "fileId": "69ca7e5750668e215f1a792ba7612d74a6f5d634ecd2f6e575c2b62377563dae"
}, },
{ {
"path": "src/sub/sub1.ts", "path": "src/sub/sub1.ts",
"force": false, "force": false,
"fileId": "50d0d59f927dc56f5f47adc37de69fc9a3334546a54f86a08d742b6c5cef07c9" "fileId": "0c5c20017d0aef7115d00715e4b5ab5a8b545eddfa3e981b0161bcd006902414"
}, },
{ {
"path": "src/sub/sub2.tsx", "path": "src/sub/sub2.tsx",
"force": false, "force": false,
"fileId": "51304e7cdc9cba392b7ed7f7ae1b8e276bc757bfa15267214e2f20b75a2dac6f" "fileId": "27bce07ded1e6a9fce868247aa4dc4e4389cf0b8c810474992dc46f52638a8e9"
}, },
{ {
"path": "src/very-large-file.ts", "path": "src/very-large-file.ts",
"force": false, "force": false,
"fileId": "444fd8107293606e0d88acf691ba98fa30de47c04b58c7930b2ee3e24c6ea17d" "fileId": "64f67386de21966eef1092306303e8b98fe6df81bc265933e1b6650358d80f9d"
} }
], ],
"messageIndexToFilePathToFileId": { "messageIndexToFilePathToFileId": {
......
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
- textbox "src/**/*.tsx" - textbox "src/**/*.tsx"
- button "Add" - button "Add"
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context. - paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3] - heading "Smart Context Auto-includes" [level=3]
- paragraph: - paragraph:
- text: These files will always be included in the context. - text: These files will always be included in the context.
...@@ -17,4 +23,6 @@ ...@@ -17,4 +23,6 @@
- img - img
- text: /manual\/\*\* 3 files, ~\d+ tokens/ - text: /manual\/\*\* 3 files, ~\d+ tokens/
- button: - button:
- img
- button "Close":
- img - img
\ No newline at end of file
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
- textbox "src/**/*.tsx" - textbox "src/**/*.tsx"
- button "Add" - button "Add"
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context. - paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3] - heading "Smart Context Auto-includes" [level=3]
- paragraph: - paragraph:
- text: These files will always be included in the context. - text: These files will always be included in the context.
- img - img
- textbox "src/**/*.config.ts" - textbox "src/**/*.config.ts"
- button "Add" - button "Add"
\ No newline at end of file - button "Close":
- img
\ No newline at end of file
...@@ -22,25 +22,25 @@ ...@@ -22,25 +22,25 @@
"dyad_options": { "dyad_options": {
"versioned_files": { "versioned_files": {
"fileIdToContent": { "fileIdToContent": {
"732a1ebb3b9b602d7104636b4009f8bb3f1d840c9fed3c3eb0dd56a3d299cbf2": "// a.ts\n", "27c40a1c2df213eb19562166c763e05c3b7f5107445e39005cfc452f521c66d3": "// a.ts\n",
"075b6697b2282dfd4cce18f78bcbc51bf5f0d9c0c0911c889b9d72243b9187c3": "[\"even json is included\"]\n", "f14173ab2694d5fd20f5b010df975a1a13dbca937408e5f5674acd99b81b1c82": "[\"even json is included\"]\n",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": "",
"4478ae9a9ff89b3c68fc44309614668629664d86354a381662ffd07716ce20ce": "// helper.ts\n", "9ab36ced71e4350acb4d0ee6eac6f61ccb785050a5c9eb62a8c6bbfab9fcbbc4": "// helper.ts\n",
"2f653d8827ab78f82263eb63f722ef7c6be026d4fa5630c3ce6fb94612751e0c": "// foo.ts\n", "69ca7e5750668e215f1a792ba7612d74a6f5d634ecd2f6e575c2b62377563dae": "// foo.ts\n",
"50d0d59f927dc56f5f47adc37de69fc9a3334546a54f86a08d742b6c5cef07c9": "// sub/sub1.ts\n", "0c5c20017d0aef7115d00715e4b5ab5a8b545eddfa3e981b0161bcd006902414": "// sub/sub1.ts\n",
"51304e7cdc9cba392b7ed7f7ae1b8e276bc757bfa15267214e2f20b75a2dac6f": "// sub/sub2.tsx\n", "27bce07ded1e6a9fce868247aa4dc4e4389cf0b8c810474992dc46f52638a8e9": "// sub/sub2.tsx\n",
"444fd8107293606e0d88acf691ba98fa30de47c04b58c7930b2ee3e24c6ea17d": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n" "64f67386de21966eef1092306303e8b98fe6df81bc265933e1b6650358d80f9d": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n"
}, },
"fileReferences": [ "fileReferences": [
{ {
"path": "a.ts", "path": "a.ts",
"force": true, "force": true,
"fileId": "732a1ebb3b9b602d7104636b4009f8bb3f1d840c9fed3c3eb0dd56a3d299cbf2" "fileId": "27c40a1c2df213eb19562166c763e05c3b7f5107445e39005cfc452f521c66d3"
}, },
{ {
"path": "manual/baz.json", "path": "manual/baz.json",
"force": true, "force": true,
"fileId": "075b6697b2282dfd4cce18f78bcbc51bf5f0d9c0c0911c889b9d72243b9187c3" "fileId": "f14173ab2694d5fd20f5b010df975a1a13dbca937408e5f5674acd99b81b1c82"
}, },
{ {
"path": "manual/file.ts", "path": "manual/file.ts",
...@@ -55,27 +55,27 @@ ...@@ -55,27 +55,27 @@
{ {
"path": "src/components/ui/helper.ts", "path": "src/components/ui/helper.ts",
"force": false, "force": false,
"fileId": "4478ae9a9ff89b3c68fc44309614668629664d86354a381662ffd07716ce20ce" "fileId": "9ab36ced71e4350acb4d0ee6eac6f61ccb785050a5c9eb62a8c6bbfab9fcbbc4"
}, },
{ {
"path": "src/foo.ts", "path": "src/foo.ts",
"force": false, "force": false,
"fileId": "2f653d8827ab78f82263eb63f722ef7c6be026d4fa5630c3ce6fb94612751e0c" "fileId": "69ca7e5750668e215f1a792ba7612d74a6f5d634ecd2f6e575c2b62377563dae"
}, },
{ {
"path": "src/sub/sub1.ts", "path": "src/sub/sub1.ts",
"force": false, "force": false,
"fileId": "50d0d59f927dc56f5f47adc37de69fc9a3334546a54f86a08d742b6c5cef07c9" "fileId": "0c5c20017d0aef7115d00715e4b5ab5a8b545eddfa3e981b0161bcd006902414"
}, },
{ {
"path": "src/sub/sub2.tsx", "path": "src/sub/sub2.tsx",
"force": false, "force": false,
"fileId": "51304e7cdc9cba392b7ed7f7ae1b8e276bc757bfa15267214e2f20b75a2dac6f" "fileId": "27bce07ded1e6a9fce868247aa4dc4e4389cf0b8c810474992dc46f52638a8e9"
}, },
{ {
"path": "src/very-large-file.ts", "path": "src/very-large-file.ts",
"force": false, "force": false,
"fileId": "444fd8107293606e0d88acf691ba98fa30de47c04b58c7930b2ee3e24c6ea17d" "fileId": "64f67386de21966eef1092306303e8b98fe6df81bc265933e1b6650358d80f9d"
} }
], ],
"messageIndexToFilePathToFileId": { "messageIndexToFilePathToFileId": {
......
- dialog: - dialog "Codebase Context":
- heading "Codebase Context" [level=3] - heading "Codebase Context" [level=2]
- paragraph: - paragraph:
- text: Select the files to use as context. - text: Select the files to use as context.
- img - img
...@@ -11,6 +11,12 @@ ...@@ -11,6 +11,12 @@
- text: /src\/sub\/\*\* 2 files, ~\d+ tokens/ - text: /src\/sub\/\*\* 2 files, ~\d+ tokens/
- button: - button:
- img - img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3] - heading "Smart Context Auto-includes" [level=3]
- paragraph: - paragraph:
- text: These files will always be included in the context. - text: These files will always be included in the context.
...@@ -22,4 +28,6 @@ ...@@ -22,4 +28,6 @@
- img - img
- text: /manual\/\*\* 3 files, ~\d+ tokens/ - text: /manual\/\*\* 3 files, ~\d+ tokens/
- button: - button:
- img
- button "Close":
- img - img
\ No newline at end of file
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
Popover, Dialog,
PopoverContent, DialogContent,
PopoverTrigger, DialogHeader,
} from "@/components/ui/popover"; DialogTitle,
DialogDescription,
DialogTrigger,
} from "@/components/ui/dialog";
import { InfoIcon, Settings2, Trash2 } from "lucide-react"; import { InfoIcon, Settings2, Trash2 } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
...@@ -116,38 +119,27 @@ export function ContextFilesPicker() { ...@@ -116,38 +119,27 @@ export function ContextFilesPicker() {
settings?.enableDyadPro && settings?.enableProSmartFilesContextMode; settings?.enableDyadPro && settings?.enableProSmartFilesContextMode;
return ( return (
<Popover open={isOpen} onOpenChange={setIsOpen}> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<Tooltip> <DialogTrigger asChild>
<TooltipTrigger asChild> <div
<PopoverTrigger asChild> className="flex items-center py-2 px-3 hover:bg-accent hover:text-accent-foreground rounded-sm cursor-pointer text-sm"
<Button data-testid="codebase-context-trigger"
variant="ghost" >
className="has-[>svg]:px-2" <Settings2 className="size-4 mr-2" />
size="sm" Codebase context
data-testid="codebase-context-button" </div>
> </DialogTrigger>
<Settings2 className="size-4" />
</Button>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent>Codebase Context</TooltipContent>
</Tooltip>
<PopoverContent <DialogContent className="max-w-md max-h-[80vh] overflow-y-auto">
className="w-96 max-h-[80vh] overflow-y-auto" <DialogHeader>
align="start" <DialogTitle>Codebase Context</DialogTitle>
> <DialogDescription>
<div className="relative space-y-4"> <span className="flex items-center gap-1">
<div> Select the files to use as context.{" "}
<h3 className="font-medium">Codebase Context</h3>
<p className="text-sm text-muted-foreground">
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<span className="flex items-center gap-1 cursor-help"> <InfoIcon className="size-4 cursor-help" />
Select the files to use as context.{" "}
<InfoIcon className="size-4" />
</span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-[300px]"> <TooltipContent className="max-w-[300px]">
{isSmartContextEnabled ? ( {isSmartContextEnabled ? (
...@@ -161,9 +153,10 @@ export function ContextFilesPicker() { ...@@ -161,9 +153,10 @@ export function ContextFilesPicker() {
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
</p> </span>
</div> </DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="flex w-full max-w-sm items-center space-x-2"> <div className="flex w-full max-w-sm items-center space-x-2">
<Input <Input
data-testid="manual-context-files-input" data-testid="manual-context-files-input"
...@@ -237,22 +230,22 @@ export function ContextFilesPicker() { ...@@ -237,22 +230,22 @@ export function ContextFilesPicker() {
<div> <div>
<h3 className="font-medium">Exclude Paths</h3> <h3 className="font-medium">Exclude Paths</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
<TooltipProvider> <span className="flex items-center gap-1">
<Tooltip> These files will be excluded from the context.{" "}
<TooltipTrigger asChild> <TooltipProvider>
<span className="flex items-center gap-1 cursor-help"> <Tooltip>
These files will be excluded from the context.{" "} <TooltipTrigger asChild>
<InfoIcon className="ml-2 size-4" /> <InfoIcon className="size-4 cursor-help" />
</span> </TooltipTrigger>
</TooltipTrigger> <TooltipContent className="max-w-[300px]">
<TooltipContent className="max-w-[300px]"> <p>
<p> Exclude paths take precedence - files that match both
Exclude paths take precedence - files that match both include and exclude patterns will be excluded.
include and exclude patterns will be excluded. </p>
</p> </TooltipContent>
</TooltipContent> </Tooltip>
</Tooltip> </TooltipProvider>
</TooltipProvider> </span>
</p> </p>
</div> </div>
...@@ -323,23 +316,23 @@ export function ContextFilesPicker() { ...@@ -323,23 +316,23 @@ export function ContextFilesPicker() {
<div> <div>
<h3 className="font-medium">Smart Context Auto-includes</h3> <h3 className="font-medium">Smart Context Auto-includes</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
<TooltipProvider> <span className="flex items-center gap-1">
<Tooltip> These files will always be included in the context.{" "}
<TooltipTrigger asChild> <TooltipProvider>
<span className="flex items-center gap-1 cursor-help"> <Tooltip>
These files will always be included in the context.{" "} <TooltipTrigger asChild>
<InfoIcon className="ml-2 size-4" /> <InfoIcon className="size-4 cursor-help" />
</span> </TooltipTrigger>
</TooltipTrigger> <TooltipContent className="max-w-[300px]">
<TooltipContent className="max-w-[300px]"> <p>
<p> Auto-include files are always included in the
Auto-include files are always included in the context context in addition to the files selected as
in addition to the files selected as relevant by Smart relevant by Smart Context.
Context. </p>
</p> </TooltipContent>
</TooltipContent> </Tooltip>
</Tooltip> </TooltipProvider>
</TooltipProvider> </span>
</p> </p>
</div> </div>
...@@ -406,7 +399,7 @@ export function ContextFilesPicker() { ...@@ -406,7 +399,7 @@ export function ContextFilesPicker() {
</div> </div>
)} )}
</div> </div>
</PopoverContent> </DialogContent>
</Popover> </Dialog>
); );
} }
import { useState } from "react";
import { Plus, Paperclip, ChartColumnIncreasing } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
DropdownMenuSeparator,
DropdownMenuItem,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { ContextFilesPicker } from "@/components/ContextFilesPicker";
import { FileAttachmentDropdown } from "./FileAttachmentDropdown";
interface AuxiliaryActionsMenuProps {
onFileSelect: (
files: FileList,
type: "chat-context" | "upload-to-codebase",
) => void;
disabled?: boolean;
showTokenBar: boolean;
toggleShowTokenBar: () => void;
}
export function AuxiliaryActionsMenu({
onFileSelect,
disabled,
showTokenBar,
toggleShowTokenBar,
}: AuxiliaryActionsMenuProps) {
const [isOpen, setIsOpen] = useState(false);
return (
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
disabled={disabled}
className="has-[>svg]:px-2 hover:bg-muted bg-primary/10 text-primary cursor-pointer rounded-xl"
data-testid="auxiliary-actions-menu"
>
<Plus
size={20}
className={`transition-transform duration-200 ${isOpen ? "rotate-45" : "rotate-0"}`}
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{/* Codebase Context */}
<ContextFilesPicker />
{/* Attach Files Submenu */}
<DropdownMenuSub>
<DropdownMenuSubTrigger className="py-2 px-3">
<Paperclip size={16} className="mr-2" />
Attach files
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<FileAttachmentDropdown
onFileSelect={onFileSelect}
renderAsMenuItems={true}
/>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator />
{/* Toggle Token Usage */}
<DropdownMenuItem
onClick={toggleShowTokenBar}
className={`py-2 px-3 group ${showTokenBar ? "bg-primary/10 text-primary" : ""}`}
>
<ChartColumnIncreasing
size={16}
className={
showTokenBar
? "text-primary group-hover:text-accent-foreground"
: ""
}
/>
<span className="flex-1">
{showTokenBar ? "Hide" : "Show"} token usage
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
...@@ -14,7 +14,6 @@ import { ...@@ -14,7 +14,6 @@ import {
Database, Database,
ChevronsUpDown, ChevronsUpDown,
ChevronsDownUp, ChevronsDownUp,
ChartColumnIncreasing,
SendHorizontalIcon, SendHorizontalIcon,
Lock, Lock,
} from "lucide-react"; } from "lucide-react";
...@@ -59,7 +58,6 @@ import { useVersions } from "@/hooks/useVersions"; ...@@ -59,7 +58,6 @@ import { useVersions } from "@/hooks/useVersions";
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 { showExtraFilesToast } from "@/lib/toast"; import { showExtraFilesToast } from "@/lib/toast";
import { useSummarizeInNewChat } from "./SummarizeInNewChatButton"; import { useSummarizeInNewChat } from "./SummarizeInNewChatButton";
import { ChatInputControls } from "../ChatInputControls"; import { ChatInputControls } from "../ChatInputControls";
...@@ -75,6 +73,7 @@ import { ...@@ -75,6 +73,7 @@ import {
import { SelectedComponentsDisplay } from "./SelectedComponentDisplay"; import { SelectedComponentsDisplay } from "./SelectedComponentDisplay";
import { useCheckProblems } from "@/hooks/useCheckProblems"; import { useCheckProblems } from "@/hooks/useCheckProblems";
import { LexicalChatInput } from "./LexicalChatInput"; import { LexicalChatInput } from "./LexicalChatInput";
import { AuxiliaryActionsMenu } from "./AuxiliaryActionsMenu";
import { useChatModeToggle } from "@/hooks/useChatModeToggle"; import { useChatModeToggle } from "@/hooks/useChatModeToggle";
import { VisualEditingChangesDialog } from "@/components/preview_panel/VisualEditingChangesDialog"; import { VisualEditingChangesDialog } from "@/components/preview_panel/VisualEditingChangesDialog";
import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo"; import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo";
...@@ -472,34 +471,15 @@ export function ChatInput({ chatId }: { chatId?: number }) { ...@@ -472,34 +471,15 @@ export function ChatInput({ chatId }: { chatId?: number }) {
</div> </div>
<div className="pl-2 pr-1 flex items-center justify-between pb-2"> <div className="pl-2 pr-1 flex items-center justify-between pb-2">
<div className="flex items-center"> <div className="flex items-center">
<ChatInputControls showContextFilesPicker={true} /> <ChatInputControls showContextFilesPicker={false} />
{/* File attachment dropdown */}
<FileAttachmentDropdown
onFileSelect={handleFileSelect}
disabled={isStreaming}
/>
</div> </div>
<TooltipProvider> <AuxiliaryActionsMenu
<Tooltip> onFileSelect={handleFileSelect}
<TooltipTrigger asChild> disabled={isStreaming}
<Button showTokenBar={showTokenBar}
onClick={toggleShowTokenBar} toggleShowTokenBar={toggleShowTokenBar}
variant="ghost" />
className={`has-[>svg]:px-2 ${
showTokenBar ? "text-purple-500 bg-purple-100" : ""
}`}
size="sm"
data-testid="token-bar-toggle"
>
<ChartColumnIncreasing size={14} />
</Button>
</TooltipTrigger>
<TooltipContent>
{showTokenBar ? "Hide token usage" : "Show token usage"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div> </div>
{/* TokenBar is only displayed when showTokenBar is true */} {/* TokenBar is only displayed when showTokenBar is true */}
{showTokenBar && <TokenBar chatId={chatId} />} {showTokenBar && <TokenBar chatId={chatId} />}
......
...@@ -21,12 +21,14 @@ interface FileAttachmentDropdownProps { ...@@ -21,12 +21,14 @@ interface FileAttachmentDropdownProps {
) => void; ) => void;
disabled?: boolean; disabled?: boolean;
className?: string; className?: string;
renderAsMenuItems?: boolean;
} }
export function FileAttachmentDropdown({ export function FileAttachmentDropdown({
onFileSelect, onFileSelect,
disabled, disabled,
className, 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);
...@@ -50,66 +52,46 @@ export function FileAttachmentDropdown({ ...@@ -50,66 +52,46 @@ export function FileAttachmentDropdown({
} }
}; };
return ( const menuItems = (
<> <>
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<DropdownMenu> <TooltipTrigger asChild>
<DropdownMenuTrigger asChild> <DropdownMenuItem
<TooltipTrigger asChild> onClick={handleChatContextClick}
<Button className="py-3 px-4"
variant="ghost" >
size="sm" <MessageSquare size={16} className="mr-2" />
disabled={disabled} Attach file as chat context
title="Attach files" </DropdownMenuItem>
className={className} </TooltipTrigger>
> <TooltipContent side="right">
<Paperclip size={20} /> Example use case: screenshot of the app to point out a UI issue
</Button> </TooltipContent>
</TooltipTrigger> </Tooltip>
</DropdownMenuTrigger> </TooltipProvider>
<DropdownMenuContent align="start">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuItem
onClick={handleChatContextClick}
className="py-3 px-4"
>
<MessageSquare size={16} className="mr-2" />
Attach file as chat context
</DropdownMenuItem>
</TooltipTrigger>
<TooltipContent side="right">
Example use case: screenshot of the app to point out a UI
issue
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<DropdownMenuItem <DropdownMenuItem
onClick={handleUploadToCodebaseClick} onClick={handleUploadToCodebaseClick}
className="py-3 px-4" className="py-3 px-4"
> >
<Upload size={16} className="mr-2" /> <Upload size={16} className="mr-2" />
Upload file to codebase Upload file to codebase
</DropdownMenuItem> </DropdownMenuItem>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="right"> <TooltipContent side="right">
Example use case: add an image to use for your app Example use case: add an image to use for your app
</TooltipContent> </TooltipContent>
</Tooltip>
</TooltipProvider>
</DropdownMenuContent>
</DropdownMenu>
<TooltipContent>Attach files</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
</>
);
{/* Hidden file inputs */} const hiddenInputs = (
<>
<input <input
type="file" type="file"
data-testid="chat-context-file-input" data-testid="chat-context-file-input"
...@@ -130,4 +112,43 @@ export function FileAttachmentDropdown({ ...@@ -130,4 +112,43 @@ export function FileAttachmentDropdown({
/> />
</> </>
); );
// If rendering as menu items only, return just the items and hidden inputs
if (renderAsMenuItems) {
return (
<>
{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}
</>
);
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论