From 78e35d3c3a553dcc601f4558244e74c51de3168f Mon Sep 17 00:00:00 2001 From: spencrmartin Date: Thu, 3 Jul 2025 08:55:43 -0400 Subject: [PATCH 01/16] Add fuzzy file search functionality - Implement FuzzyFileSearch component with fuzzy matching algorithm - Add file scanning with directory traversal and ignore patterns - Include keyboard navigation (arrow keys, enter, escape) - Support highlighting of matched characters in search results - Integrate with ChatInput component for file selection - Add loading states and error handling - Limit results to 50 items for performance --- demo.json | 17 + demo.txt | 17 + package-lock.json | 6 + ui/desktop/src/components/ChatInput.tsx | 423 ++++++++++-------- ui/desktop/src/components/FuzzyFileSearch.tsx | 361 +++++++++++++++ ui/desktop/src/floating-button-script.js | 28 ++ 6 files changed, 653 insertions(+), 199 deletions(-) create mode 100644 demo.json create mode 100644 demo.txt create mode 100644 package-lock.json create mode 100644 ui/desktop/src/components/FuzzyFileSearch.tsx create mode 100644 ui/desktop/src/floating-button-script.js diff --git a/demo.json b/demo.json new file mode 100644 index 000000000000..bfbe0d8d653c --- /dev/null +++ b/demo.json @@ -0,0 +1,17 @@ +{ + "demo": { + "title": "Goose File Operations Demo", + "version": "1.0.0", + "features": [ + "File creation", + "File reading", + "Content modification", + "Multiple formats" + ], + "metadata": { + "created_by": "Goose AI Assistant", + "created_at": "2025-07-02T17:33:32Z", + "file_type": "demonstration" + } + } +} diff --git a/demo.txt b/demo.txt new file mode 100644 index 000000000000..a1e539c81009 --- /dev/null +++ b/demo.txt @@ -0,0 +1,17 @@ +Hello, World! + +This is a demonstration of file operations in Goose. + +Here are some key points: +- Files can be created and edited +- Content can be viewed and modified +- Multiple file formats are supported + +Current timestamp: 2025-07-02 17:33:32 + +--- UPDATE --- +This content was added after the initial file creation! +File modification operations include: +- String replacement +- Line insertion +- Content appending diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000000..6deca24a71b8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "goose", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index a785d5eeb4b1..98445108c0de 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -10,6 +10,7 @@ import { Message } from '../types/message'; import { useWhisper } from '../hooks/useWhisper'; import { WaveformVisualizer } from './WaveformVisualizer'; import { toastError } from '../toasts'; +import FuzzyFileSearch from './FuzzyFileSearch'; interface PastedImage { id: string; @@ -65,6 +66,7 @@ export default function ChatInput({ const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback const [isFocused, setIsFocused] = useState(false); const [pastedImages, setPastedImages] = useState([]); + const [isFuzzySearchOpen, setIsFuzzySearchOpen] = useState(false); // Whisper hook for voice dictation const { @@ -428,6 +430,13 @@ export default function ChatInput({ // Handle history navigation first handleHistoryNavigation(evt); + // Handle fuzzy file search trigger (Cmd/Ctrl + P) + if (evt.key === 'p' && (evt.metaKey || evt.ctrlKey) && !evt.shiftKey && !evt.altKey) { + evt.preventDefault(); + setIsFuzzySearchOpen(true); + return; + } + if (evt.key === 'Enter') { // should not trigger submit on Enter if it's composing (IME input in progress) or shift/alt(option) is pressed if (evt.shiftKey || isComposing) { @@ -474,223 +483,239 @@ export default function ChatInput({ } }; + const handleFuzzyFileSelect = (filePath: string) => { + const newValue = displayValue.trim() ? `${displayValue.trim()} ${filePath}` : filePath; + setDisplayValue(newValue); + setValue(newValue); + textAreaRef.current?.focus(); + }; + const hasSubmittableContent = displayValue.trim() || pastedImages.some((img) => img.filePath && !img.error && !img.isLoading); const isAnyImageLoading = pastedImages.some((img) => img.isLoading); return ( -
-
-
-