diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 33a8d222f4d8..fa79d6f68a60 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -13,7 +13,6 @@ import { type SharedSessionDetails } from './sharedSessions'; import { ErrorUI } from './components/ErrorBoundary'; import { ExtensionInstallModal } from './components/ExtensionInstallModal'; import { ToastContainer } from 'react-toastify'; -import { GoosehintsModal } from './components/GoosehintsModal'; import AnnouncementModal from './components/AnnouncementModal'; import ProviderGuard from './components/ProviderGuard'; import { createSession } from './sessions'; @@ -43,34 +42,20 @@ import { useNavigation } from './hooks/useNavigation'; import { errorMessage } from './utils/conversionUtils'; // Route Components -const HubRouteWrapper = ({ - setIsGoosehintsModalOpen, - isExtensionsLoading, -}: { - setIsGoosehintsModalOpen: (isOpen: boolean) => void; - isExtensionsLoading: boolean; -}) => { +const HubRouteWrapper = ({ isExtensionsLoading }: { isExtensionsLoading: boolean }) => { const setView = useNavigation(); - return ( - - ); + return ; }; const PairRouteWrapper = ({ chat, setChat, - setIsGoosehintsModalOpen, activeSessionId, setActiveSessionId, }: { chat: ChatType; setChat: (chat: ChatType) => void; - setIsGoosehintsModalOpen: (isOpen: boolean) => void; activeSessionId: string | null; setActiveSessionId: (id: string | null) => void; }) => { @@ -178,13 +163,7 @@ const PairRouteWrapper = ({ }, [sessionId, activeSessionId, setActiveSessionId]); return ( - + ); }; @@ -356,7 +335,6 @@ const ExtensionsRoute = () => { export function AppInner() { const [fatalError, setFatalError] = useState(null); - const [isGoosehintsModalOpen, setIsGoosehintsModalOpen] = useState(false); const [agentWaitingMessage, setAgentWaitingMessage] = useState(null); const [isLoadingSharedSession, setIsLoadingSharedSession] = useState(false); const [sharedSessionError, setSharedSessionError] = useState(null); @@ -652,27 +630,18 @@ export function AppInner() { contextKey="hub" agentWaitingMessage={agentWaitingMessage} > - + } > - - } - /> + } /> @@ -709,12 +678,6 @@ export function AppInner() { - {isGoosehintsModalOpen && ( - - )} ); } diff --git a/ui/desktop/src/components/BaseChat.tsx b/ui/desktop/src/components/BaseChat.tsx index 07d0e8e36699..9adee7177fd2 100644 --- a/ui/desktop/src/components/BaseChat.tsx +++ b/ui/desktop/src/components/BaseChat.tsx @@ -43,7 +43,6 @@ export const useCurrentModelInfo = () => useContext(CurrentModelContext); interface BaseChatProps { setChat: (chat: ChatType) => void; - setIsGoosehintsModalOpen?: (isOpen: boolean) => void; onMessageSubmit?: (message: string) => void; renderHeader?: () => React.ReactNode; customChatInputProps?: Record; @@ -57,7 +56,6 @@ interface BaseChatProps { } function BaseChatContent({ - setIsGoosehintsModalOpen, renderHeader, customChatInputProps = {}, customMainLayoutProps = {}, @@ -423,7 +421,6 @@ function BaseChatContent({ messages={messages} disableAnimation={disableAnimation} sessionCosts={sessionCosts} - setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} recipe={recipe} recipeAccepted={!hasNotAcceptedRecipe} initialPrompt={initialPrompt} diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index 0cb0ea5e06aa..66f513bbf06a 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -1,5 +1,5 @@ import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react'; -import { Bug, FolderKey, ScrollText } from 'lucide-react'; +import { Bug, ScrollText, ChefHat } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/Tooltip'; import { Button } from './ui/button'; import type { View } from '../utils/navigationUtils'; @@ -28,6 +28,8 @@ import MessageQueue from './MessageQueue'; import { detectInterruption } from '../utils/interruptionDetector'; import { DiagnosticsModal } from './ui/DownloadDiagnostics'; import { Message } from '../api'; +import CreateRecipeFromSessionModal from './recipes/CreateRecipeFromSessionModal'; +import CreateEditRecipeModal from './recipes/CreateEditRecipeModal'; interface QueuedMessage { id: string; @@ -80,7 +82,6 @@ interface ChatInputProps { totalCost: number; }; }; - setIsGoosehintsModalOpen?: (isOpen: boolean) => void; disableAnimation?: boolean; recipe?: Recipe | null; recipeId?: string | null; @@ -107,7 +108,6 @@ export default function ChatInput({ messages = [], disableAnimation = false, sessionCosts, - setIsGoosehintsModalOpen, recipe, recipeId, recipeAccepted, @@ -140,6 +140,8 @@ export default function ChatInput({ const [tokenLimit, setTokenLimit] = useState(TOKEN_LIMIT_DEFAULT); const [isTokenLimitLoaded, setIsTokenLimitLoaded] = useState(false); const [diagnosticsOpen, setDiagnosticsOpen] = useState(false); + const [showCreateRecipeModal, setShowCreateRecipeModal] = useState(false); + const [showEditRecipeModal, setShowEditRecipeModal] = useState(false); // Save queue state (paused/interrupted) to storage useEffect(() => { @@ -1486,9 +1488,6 @@ export default function ChatInput({ dropdownRef={dropdownRef} setView={setView} alerts={alerts} - recipe={recipe} - recipeId={recipeId} - hasMessages={messages.length > 0} /> @@ -1500,21 +1499,34 @@ export default function ChatInput({ )} -
- - - - - Configure goosehints - -
+ {sessionId && ( + <> +
+
+ + + + + + {recipe ? 'View/Edit Recipe' : 'Create Recipe from Session'} + + +
+ + )} {sessionId && ( @@ -1552,6 +1564,23 @@ export default function ChatInput({ setMentionPopover((prev) => ({ ...prev, selectedIndex: index })) } /> + + {sessionId && showCreateRecipeModal && ( + setShowCreateRecipeModal(false)} + sessionId={sessionId} + /> + )} + + {recipe && showEditRecipeModal && ( + setShowEditRecipeModal(false)} + recipe={recipe} + recipeId={recipeId} + /> + )}
); diff --git a/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx b/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx index 67d59bd18e8a..e99bfb0584d9 100644 --- a/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx +++ b/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx @@ -21,7 +21,6 @@ interface SidebarProps { onSelectSession: (sessionId: string) => void; refreshTrigger?: number; children?: React.ReactNode; - setIsGoosehintsModalOpen?: (isOpen: boolean) => void; setView?: (view: View, viewOptions?: ViewOptions) => void; currentPath?: string; } diff --git a/ui/desktop/src/components/GoosehintsModal.tsx b/ui/desktop/src/components/GoosehintsModal.tsx deleted file mode 100644 index eafa97b3fe83..000000000000 --- a/ui/desktop/src/components/GoosehintsModal.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Button } from './ui/button'; -import { Check } from './icons'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from './ui/dialog'; - -const ModalHelpText = () => ( -
-

- .goosehints is a text file used to provide additional context about your project and improve - the communication with Goose. -

-

- Please make sure Developer extension is enabled in the - settings page. This extension is required to use .goosehints. You'll need to restart your - session for .goosehints updates to take effect. -

-

- See{' '} - {' '} - for more information. -

-
-); - -const ModalError = ({ error }: { error: Error }) => ( -
-
Error reading .goosehints file: {JSON.stringify(error)}
-
-); - -const ModalFileInfo = ({ filePath, found }: { filePath: string; found: boolean }) => ( -
- {found ? ( -
- .goosehints file found at: {filePath} -
- ) : ( -
Creating new .goosehints file at: {filePath}
- )} -
-); - -const getGoosehintsFile = async (filePath: string) => await window.electron.readFile(filePath); - -type GoosehintsModalProps = { - directory: string; - setIsGoosehintsModalOpen: (isOpen: boolean) => void; -}; - -export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: GoosehintsModalProps) => { - const goosehintsFilePath = `${directory}/.goosehints`; - const [goosehintsFile, setGoosehintsFile] = useState(''); - const [goosehintsFileFound, setGoosehintsFileFound] = useState(false); - const [goosehintsFileReadError, setGoosehintsFileReadError] = useState(''); - - useEffect(() => { - const fetchGoosehintsFile = async () => { - try { - const { file, error, found } = await getGoosehintsFile(goosehintsFilePath); - setGoosehintsFile(file); - setGoosehintsFileFound(found); - // Only set error if file was found but there was an actual read error - // If file is not found, treat it as creating a new file (no error) - setGoosehintsFileReadError(found && error ? error : ''); - } catch (error) { - console.error('Error fetching .goosehints file:', error); - setGoosehintsFileReadError('Failed to access .goosehints file'); - } - }; - if (directory) fetchGoosehintsFile(); - }, [directory, goosehintsFilePath]); - - const writeFile = async () => { - await window.electron.writeFile(goosehintsFilePath, goosehintsFile); - setIsGoosehintsModalOpen(false); - }; - - const handleClose = () => { - setIsGoosehintsModalOpen(false); - }; - - return ( - - - - Configure .goosehints - - Configure your project's .goosehints file to provide additional context to Goose. - - - - - -
- {goosehintsFileReadError ? ( - - ) : ( -
- -