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.
- 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
...@@ -11,3 +11,11 @@ ...@@ -11,3 +11,11 @@
- 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"
- 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
...@@ -12,3 +12,5 @@ ...@@ -12,3 +12,5 @@
- img - img
- textbox "node_modules/**/*" - textbox "node_modules/**/*"
- button "Add" - 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
...@@ -23,3 +23,5 @@ ...@@ -23,3 +23,5 @@
- text: manual/exclude/** 0 files, ~0 tokens - text: manual/exclude/** 0 files, ~0 tokens
- button: - button:
- img - img
- 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
...@@ -23,3 +23,5 @@ ...@@ -23,3 +23,5 @@
- text: /src\/\*\* 7 files, ~\d+ tokens/ - text: /src\/\*\* 7 files, ~\d+ tokens/
- button: - button:
- img - img
- 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
...@@ -18,3 +18,5 @@ ...@@ -18,3 +18,5 @@
- img - img
- textbox "src/**/*.config.ts" - textbox "src/**/*.config.ts"
- button "Add" - 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
...@@ -35,3 +35,5 @@ ...@@ -35,3 +35,5 @@
- text: /exclude\/\*\* 2 files, ~\d+ tokens/ - text: /exclude\/\*\* 2 files, ~\d+ tokens/
- button: - button:
- img - img
- 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
- 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"
- 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.
...@@ -18,3 +24,5 @@ ...@@ -18,3 +24,5 @@
- text: /manual\/\*\* 3 files, ~\d+ tokens/ - text: /manual\/\*\* 3 files, ~\d+ tokens/
- button: - button:
- img - img
- 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
- 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"
- 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.
...@@ -23,3 +29,5 @@ ...@@ -23,3 +29,5 @@
- text: /manual\/\*\* 3 files, ~\d+ tokens/ - text: /manual\/\*\* 3 files, ~\d+ tokens/
- button: - button:
- img - img
- button "Close":
- 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"
size="sm"
data-testid="codebase-context-button"
> >
<Settings2 className="size-4" /> <Settings2 className="size-4 mr-2" />
</Button> Codebase context
</PopoverTrigger> </div>
</TooltipTrigger> </DialogTrigger>
<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,13 +230,12 @@ export function ContextFilesPicker() { ...@@ -237,13 +230,12 @@ 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">
<span className="flex items-center gap-1">
These files will be excluded from the context.{" "}
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<span className="flex items-center gap-1 cursor-help"> <InfoIcon className="size-4 cursor-help" />
These files will be excluded from the context.{" "}
<InfoIcon className="ml-2 size-4" />
</span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-[300px]"> <TooltipContent className="max-w-[300px]">
<p> <p>
...@@ -253,6 +245,7 @@ export function ContextFilesPicker() { ...@@ -253,6 +245,7 @@ export function ContextFilesPicker() {
</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">
<span className="flex items-center gap-1">
These files will always be included in the context.{" "}
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<span className="flex items-center gap-1 cursor-help"> <InfoIcon className="size-4 cursor-help" />
These files will always be included in the context.{" "}
<InfoIcon className="ml-2 size-4" />
</span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-[300px]"> <TooltipContent className="max-w-[300px]">
<p> <p>
Auto-include files are always included in the context Auto-include files are always included in the
in addition to the files selected as relevant by Smart context in addition to the files selected as
Context. relevant by Smart 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,35 +471,16 @@ export function ChatInput({ chatId }: { chatId?: number }) { ...@@ -472,35 +471,16 @@ 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 */} </div>
<FileAttachmentDropdown
<AuxiliaryActionsMenu
onFileSelect={handleFileSelect} onFileSelect={handleFileSelect}
disabled={isStreaming} disabled={isStreaming}
showTokenBar={showTokenBar}
toggleShowTokenBar={toggleShowTokenBar}
/> />
</div> </div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={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>
{/* TokenBar is only displayed when showTokenBar is true */} {/* TokenBar is only displayed when showTokenBar is true */}
{showTokenBar && <TokenBar chatId={chatId} />} {showTokenBar && <TokenBar chatId={chatId} />}
</div> </div>
......
...@@ -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,25 +52,8 @@ export function FileAttachmentDropdown({ ...@@ -50,25 +52,8 @@ export function FileAttachmentDropdown({
} }
}; };
return ( const menuItems = (
<> <>
<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">
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
...@@ -81,8 +66,7 @@ export function FileAttachmentDropdown({ ...@@ -81,8 +66,7 @@ export function FileAttachmentDropdown({
</DropdownMenuItem> </DropdownMenuItem>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="right"> <TooltipContent side="right">
Example use case: screenshot of the app to point out a UI Example use case: screenshot of the app to point out a UI issue
issue
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
...@@ -103,13 +87,11 @@ export function FileAttachmentDropdown({ ...@@ -103,13 +87,11 @@ export function FileAttachmentDropdown({
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
</DropdownMenuContent> </>
</DropdownMenu> );
<TooltipContent>Attach files</TooltipContent>
</Tooltip>
</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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论