Move recipe actions to bottom bar icon and edit goosehints to settings (#5864)
This commit is contained in:
+5
-42
@@ -13,7 +13,6 @@ import { type SharedSessionDetails } from './sharedSessions';
|
|||||||
import { ErrorUI } from './components/ErrorBoundary';
|
import { ErrorUI } from './components/ErrorBoundary';
|
||||||
import { ExtensionInstallModal } from './components/ExtensionInstallModal';
|
import { ExtensionInstallModal } from './components/ExtensionInstallModal';
|
||||||
import { ToastContainer } from 'react-toastify';
|
import { ToastContainer } from 'react-toastify';
|
||||||
import { GoosehintsModal } from './components/GoosehintsModal';
|
|
||||||
import AnnouncementModal from './components/AnnouncementModal';
|
import AnnouncementModal from './components/AnnouncementModal';
|
||||||
import ProviderGuard from './components/ProviderGuard';
|
import ProviderGuard from './components/ProviderGuard';
|
||||||
import { createSession } from './sessions';
|
import { createSession } from './sessions';
|
||||||
@@ -43,34 +42,20 @@ import { useNavigation } from './hooks/useNavigation';
|
|||||||
import { errorMessage } from './utils/conversionUtils';
|
import { errorMessage } from './utils/conversionUtils';
|
||||||
|
|
||||||
// Route Components
|
// Route Components
|
||||||
const HubRouteWrapper = ({
|
const HubRouteWrapper = ({ isExtensionsLoading }: { isExtensionsLoading: boolean }) => {
|
||||||
setIsGoosehintsModalOpen,
|
|
||||||
isExtensionsLoading,
|
|
||||||
}: {
|
|
||||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
|
||||||
isExtensionsLoading: boolean;
|
|
||||||
}) => {
|
|
||||||
const setView = useNavigation();
|
const setView = useNavigation();
|
||||||
|
|
||||||
return (
|
return <Hub setView={setView} isExtensionsLoading={isExtensionsLoading} />;
|
||||||
<Hub
|
|
||||||
setView={setView}
|
|
||||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
|
||||||
isExtensionsLoading={isExtensionsLoading}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PairRouteWrapper = ({
|
const PairRouteWrapper = ({
|
||||||
chat,
|
chat,
|
||||||
setChat,
|
setChat,
|
||||||
setIsGoosehintsModalOpen,
|
|
||||||
activeSessionId,
|
activeSessionId,
|
||||||
setActiveSessionId,
|
setActiveSessionId,
|
||||||
}: {
|
}: {
|
||||||
chat: ChatType;
|
chat: ChatType;
|
||||||
setChat: (chat: ChatType) => void;
|
setChat: (chat: ChatType) => void;
|
||||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
|
||||||
activeSessionId: string | null;
|
activeSessionId: string | null;
|
||||||
setActiveSessionId: (id: string | null) => void;
|
setActiveSessionId: (id: string | null) => void;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -178,13 +163,7 @@ const PairRouteWrapper = ({
|
|||||||
}, [sessionId, activeSessionId, setActiveSessionId]);
|
}, [sessionId, activeSessionId, setActiveSessionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pair
|
<Pair key={sessionId} setChat={setChat} sessionId={sessionId} initialMessage={initialMessage} />
|
||||||
key={sessionId}
|
|
||||||
setChat={setChat}
|
|
||||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
|
||||||
sessionId={sessionId}
|
|
||||||
initialMessage={initialMessage}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -356,7 +335,6 @@ const ExtensionsRoute = () => {
|
|||||||
|
|
||||||
export function AppInner() {
|
export function AppInner() {
|
||||||
const [fatalError, setFatalError] = useState<string | null>(null);
|
const [fatalError, setFatalError] = useState<string | null>(null);
|
||||||
const [isGoosehintsModalOpen, setIsGoosehintsModalOpen] = useState(false);
|
|
||||||
const [agentWaitingMessage, setAgentWaitingMessage] = useState<string | null>(null);
|
const [agentWaitingMessage, setAgentWaitingMessage] = useState<string | null>(null);
|
||||||
const [isLoadingSharedSession, setIsLoadingSharedSession] = useState(false);
|
const [isLoadingSharedSession, setIsLoadingSharedSession] = useState(false);
|
||||||
const [sharedSessionError, setSharedSessionError] = useState<string | null>(null);
|
const [sharedSessionError, setSharedSessionError] = useState<string | null>(null);
|
||||||
@@ -652,27 +630,18 @@ export function AppInner() {
|
|||||||
contextKey="hub"
|
contextKey="hub"
|
||||||
agentWaitingMessage={agentWaitingMessage}
|
agentWaitingMessage={agentWaitingMessage}
|
||||||
>
|
>
|
||||||
<AppLayout setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
|
<AppLayout />
|
||||||
</ChatProvider>
|
</ChatProvider>
|
||||||
</ProviderGuard>
|
</ProviderGuard>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Route
|
<Route index element={<HubRouteWrapper isExtensionsLoading={isExtensionsLoading} />} />
|
||||||
index
|
|
||||||
element={
|
|
||||||
<HubRouteWrapper
|
|
||||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
|
||||||
isExtensionsLoading={isExtensionsLoading}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
path="pair"
|
path="pair"
|
||||||
element={
|
element={
|
||||||
<PairRouteWrapper
|
<PairRouteWrapper
|
||||||
chat={chat}
|
chat={chat}
|
||||||
setChat={setChat}
|
setChat={setChat}
|
||||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
|
||||||
activeSessionId={activeSessionId}
|
activeSessionId={activeSessionId}
|
||||||
setActiveSessionId={setActiveSessionId}
|
setActiveSessionId={setActiveSessionId}
|
||||||
/>
|
/>
|
||||||
@@ -709,12 +678,6 @@ export function AppInner() {
|
|||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
{isGoosehintsModalOpen && (
|
|
||||||
<GoosehintsModal
|
|
||||||
directory={window.appConfig?.get('GOOSE_WORKING_DIR') as string}
|
|
||||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export const useCurrentModelInfo = () => useContext(CurrentModelContext);
|
|||||||
|
|
||||||
interface BaseChatProps {
|
interface BaseChatProps {
|
||||||
setChat: (chat: ChatType) => void;
|
setChat: (chat: ChatType) => void;
|
||||||
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
|
|
||||||
onMessageSubmit?: (message: string) => void;
|
onMessageSubmit?: (message: string) => void;
|
||||||
renderHeader?: () => React.ReactNode;
|
renderHeader?: () => React.ReactNode;
|
||||||
customChatInputProps?: Record<string, unknown>;
|
customChatInputProps?: Record<string, unknown>;
|
||||||
@@ -57,7 +56,6 @@ interface BaseChatProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BaseChatContent({
|
function BaseChatContent({
|
||||||
setIsGoosehintsModalOpen,
|
|
||||||
renderHeader,
|
renderHeader,
|
||||||
customChatInputProps = {},
|
customChatInputProps = {},
|
||||||
customMainLayoutProps = {},
|
customMainLayoutProps = {},
|
||||||
@@ -423,7 +421,6 @@ function BaseChatContent({
|
|||||||
messages={messages}
|
messages={messages}
|
||||||
disableAnimation={disableAnimation}
|
disableAnimation={disableAnimation}
|
||||||
sessionCosts={sessionCosts}
|
sessionCosts={sessionCosts}
|
||||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
|
||||||
recipe={recipe}
|
recipe={recipe}
|
||||||
recipeAccepted={!hasNotAcceptedRecipe}
|
recipeAccepted={!hasNotAcceptedRecipe}
|
||||||
initialPrompt={initialPrompt}
|
initialPrompt={initialPrompt}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react';
|
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 { Tooltip, TooltipContent, TooltipTrigger } from './ui/Tooltip';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import type { View } from '../utils/navigationUtils';
|
import type { View } from '../utils/navigationUtils';
|
||||||
@@ -28,6 +28,8 @@ import MessageQueue from './MessageQueue';
|
|||||||
import { detectInterruption } from '../utils/interruptionDetector';
|
import { detectInterruption } from '../utils/interruptionDetector';
|
||||||
import { DiagnosticsModal } from './ui/DownloadDiagnostics';
|
import { DiagnosticsModal } from './ui/DownloadDiagnostics';
|
||||||
import { Message } from '../api';
|
import { Message } from '../api';
|
||||||
|
import CreateRecipeFromSessionModal from './recipes/CreateRecipeFromSessionModal';
|
||||||
|
import CreateEditRecipeModal from './recipes/CreateEditRecipeModal';
|
||||||
|
|
||||||
interface QueuedMessage {
|
interface QueuedMessage {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -80,7 +82,6 @@ interface ChatInputProps {
|
|||||||
totalCost: number;
|
totalCost: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
|
|
||||||
disableAnimation?: boolean;
|
disableAnimation?: boolean;
|
||||||
recipe?: Recipe | null;
|
recipe?: Recipe | null;
|
||||||
recipeId?: string | null;
|
recipeId?: string | null;
|
||||||
@@ -107,7 +108,6 @@ export default function ChatInput({
|
|||||||
messages = [],
|
messages = [],
|
||||||
disableAnimation = false,
|
disableAnimation = false,
|
||||||
sessionCosts,
|
sessionCosts,
|
||||||
setIsGoosehintsModalOpen,
|
|
||||||
recipe,
|
recipe,
|
||||||
recipeId,
|
recipeId,
|
||||||
recipeAccepted,
|
recipeAccepted,
|
||||||
@@ -140,6 +140,8 @@ export default function ChatInput({
|
|||||||
const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT);
|
const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT);
|
||||||
const [isTokenLimitLoaded, setIsTokenLimitLoaded] = useState(false);
|
const [isTokenLimitLoaded, setIsTokenLimitLoaded] = useState(false);
|
||||||
const [diagnosticsOpen, setDiagnosticsOpen] = useState(false);
|
const [diagnosticsOpen, setDiagnosticsOpen] = useState(false);
|
||||||
|
const [showCreateRecipeModal, setShowCreateRecipeModal] = useState(false);
|
||||||
|
const [showEditRecipeModal, setShowEditRecipeModal] = useState(false);
|
||||||
|
|
||||||
// Save queue state (paused/interrupted) to storage
|
// Save queue state (paused/interrupted) to storage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1486,9 +1488,6 @@ export default function ChatInput({
|
|||||||
dropdownRef={dropdownRef}
|
dropdownRef={dropdownRef}
|
||||||
setView={setView}
|
setView={setView}
|
||||||
alerts={alerts}
|
alerts={alerts}
|
||||||
recipe={recipe}
|
|
||||||
recipeId={recipeId}
|
|
||||||
hasMessages={messages.length > 0}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -1500,21 +1499,34 @@ export default function ChatInput({
|
|||||||
<BottomMenuExtensionSelection sessionId={sessionId} />
|
<BottomMenuExtensionSelection sessionId={sessionId} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center h-full">
|
{sessionId && (
|
||||||
<Tooltip>
|
<>
|
||||||
<TooltipTrigger asChild>
|
<div className="w-px h-4 bg-border-default mx-2" />
|
||||||
<Button
|
<div className="flex items-center h-full">
|
||||||
onClick={() => setIsGoosehintsModalOpen?.(true)}
|
<Tooltip>
|
||||||
variant="ghost"
|
<TooltipTrigger asChild>
|
||||||
size="sm"
|
<Button
|
||||||
className="flex items-center justify-center text-text-default/70 hover:text-text-default text-xs cursor-pointer"
|
onClick={() => {
|
||||||
>
|
if (recipe) {
|
||||||
<FolderKey size={16} />
|
setShowEditRecipeModal(true);
|
||||||
</Button>
|
} else {
|
||||||
</TooltipTrigger>
|
setShowCreateRecipeModal(true);
|
||||||
<TooltipContent>Configure goosehints</TooltipContent>
|
}
|
||||||
</Tooltip>
|
}}
|
||||||
</div>
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="flex items-center justify-center text-text-default/70 hover:text-text-default text-xs cursor-pointer"
|
||||||
|
>
|
||||||
|
<ChefHat size={16} />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{recipe ? 'View/Edit Recipe' : 'Create Recipe from Session'}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{sessionId && (
|
{sessionId && (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@@ -1552,6 +1564,23 @@ export default function ChatInput({
|
|||||||
setMentionPopover((prev) => ({ ...prev, selectedIndex: index }))
|
setMentionPopover((prev) => ({ ...prev, selectedIndex: index }))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{sessionId && showCreateRecipeModal && (
|
||||||
|
<CreateRecipeFromSessionModal
|
||||||
|
isOpen={showCreateRecipeModal}
|
||||||
|
onClose={() => setShowCreateRecipeModal(false)}
|
||||||
|
sessionId={sessionId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{recipe && showEditRecipeModal && (
|
||||||
|
<CreateEditRecipeModal
|
||||||
|
isOpen={showEditRecipeModal}
|
||||||
|
onClose={() => setShowEditRecipeModal(false)}
|
||||||
|
recipe={recipe}
|
||||||
|
recipeId={recipeId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ interface SidebarProps {
|
|||||||
onSelectSession: (sessionId: string) => void;
|
onSelectSession: (sessionId: string) => void;
|
||||||
refreshTrigger?: number;
|
refreshTrigger?: number;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
|
|
||||||
setView?: (view: View, viewOptions?: ViewOptions) => void;
|
setView?: (view: View, viewOptions?: ViewOptions) => void;
|
||||||
currentPath?: string;
|
currentPath?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = () => (
|
|
||||||
<div className="text-sm flex-col space-y-4">
|
|
||||||
<p>
|
|
||||||
.goosehints is a text file used to provide additional context about your project and improve
|
|
||||||
the communication with Goose.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Please make sure <span className="font-bold">Developer</span> 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.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
See{' '}
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
className="text-blue-500 hover:text-blue-600 p-0 h-auto"
|
|
||||||
onClick={() =>
|
|
||||||
window.open('https://block.github.io/goose/docs/guides/using-goosehints/', '_blank')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
using .goosehints
|
|
||||||
</Button>{' '}
|
|
||||||
for more information.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const ModalError = ({ error }: { error: Error }) => (
|
|
||||||
<div className="text-sm text-textSubtle">
|
|
||||||
<div className="text-red-600">Error reading .goosehints file: {JSON.stringify(error)}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const ModalFileInfo = ({ filePath, found }: { filePath: string; found: boolean }) => (
|
|
||||||
<div className="text-sm font-medium">
|
|
||||||
{found ? (
|
|
||||||
<div className="text-green-600">
|
|
||||||
<Check className="w-4 h-4 inline-block" /> .goosehints file found at: {filePath}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>Creating new .goosehints file at: {filePath}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
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<string>('');
|
|
||||||
const [goosehintsFileFound, setGoosehintsFileFound] = useState<boolean>(false);
|
|
||||||
const [goosehintsFileReadError, setGoosehintsFileReadError] = useState<string>('');
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Dialog open={true} onOpenChange={handleClose}>
|
|
||||||
<DialogContent className="sm:max-w-[80%] sm:max-h-[80%] overflow-auto">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Configure .goosehints</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Configure your project's .goosehints file to provide additional context to Goose.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<ModalHelpText />
|
|
||||||
|
|
||||||
<div className="py-4">
|
|
||||||
{goosehintsFileReadError ? (
|
|
||||||
<ModalError error={new Error(goosehintsFileReadError)} />
|
|
||||||
) : (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<ModalFileInfo filePath={goosehintsFilePath} found={goosehintsFileFound} />
|
|
||||||
<textarea
|
|
||||||
defaultValue={goosehintsFile}
|
|
||||||
autoFocus
|
|
||||||
className="w-full h-80 border rounded-md p-2 text-sm resize-none bg-background-default text-textStandard border-borderStandard focus:outline-none"
|
|
||||||
onChange={(event) => setGoosehintsFile(event.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter className="pt-2">
|
|
||||||
<Button variant="outline" onClick={handleClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button onClick={writeFile}>Save</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -23,11 +23,9 @@ import { startNewSession } from '../sessions';
|
|||||||
|
|
||||||
export default function Hub({
|
export default function Hub({
|
||||||
setView,
|
setView,
|
||||||
setIsGoosehintsModalOpen,
|
|
||||||
isExtensionsLoading,
|
isExtensionsLoading,
|
||||||
}: {
|
}: {
|
||||||
setView: (view: View, viewOptions?: ViewOptions) => void;
|
setView: (view: View, viewOptions?: ViewOptions) => void;
|
||||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
|
||||||
isExtensionsLoading: boolean;
|
isExtensionsLoading: boolean;
|
||||||
}) {
|
}) {
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
@@ -61,7 +59,6 @@ export default function Hub({
|
|||||||
messages={[]}
|
messages={[]}
|
||||||
disableAnimation={false}
|
disableAnimation={false}
|
||||||
sessionCosts={undefined}
|
sessionCosts={undefined}
|
||||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
|
||||||
isExtensionsLoading={isExtensionsLoading}
|
isExtensionsLoading={isExtensionsLoading}
|
||||||
toolCount={0}
|
toolCount={0}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ import { AppWindowMac, AppWindow } from 'lucide-react';
|
|||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { Sidebar, SidebarInset, SidebarProvider, SidebarTrigger, useSidebar } from '../ui/sidebar';
|
import { Sidebar, SidebarInset, SidebarProvider, SidebarTrigger, useSidebar } from '../ui/sidebar';
|
||||||
|
|
||||||
interface AppLayoutProps {
|
const AppLayoutContent: React.FC = () => {
|
||||||
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inner component that uses useSidebar within SidebarProvider context
|
|
||||||
const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }) => {
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const safeIsMacOS = (window?.electron?.platform || 'darwin') === 'darwin';
|
const safeIsMacOS = (window?.electron?.platform || 'darwin') === 'darwin';
|
||||||
@@ -99,7 +94,6 @@ const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }
|
|||||||
<AppSidebar
|
<AppSidebar
|
||||||
onSelectSession={handleSelectSession}
|
onSelectSession={handleSelectSession}
|
||||||
setView={setView}
|
setView={setView}
|
||||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
|
||||||
currentPath={location.pathname}
|
currentPath={location.pathname}
|
||||||
/>
|
/>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
@@ -110,10 +104,10 @@ const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppLayout: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }) => {
|
export const AppLayout: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<AppLayoutContent setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
|
<AppLayoutContent />
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,21 +10,14 @@ export interface PairRouteState {
|
|||||||
|
|
||||||
interface PairProps {
|
interface PairProps {
|
||||||
setChat: (chat: ChatType) => void;
|
setChat: (chat: ChatType) => void;
|
||||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
initialMessage?: string;
|
initialMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Pair({
|
export default function Pair({ setChat, sessionId, initialMessage }: PairProps) {
|
||||||
setChat,
|
|
||||||
setIsGoosehintsModalOpen,
|
|
||||||
sessionId,
|
|
||||||
initialMessage,
|
|
||||||
}: PairProps) {
|
|
||||||
return (
|
return (
|
||||||
<BaseChat
|
<BaseChat
|
||||||
setChat={setChat}
|
setChat={setChat}
|
||||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
initialMessage={initialMessage}
|
initialMessage={initialMessage}
|
||||||
suppressEmptyState={false}
|
suppressEmptyState={false}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ToolSelectionStrategySection } from '../tool_selection_strategy/ToolSel
|
|||||||
import DictationSection from '../dictation/DictationSection';
|
import DictationSection from '../dictation/DictationSection';
|
||||||
import { SecurityToggle } from '../security/SecurityToggle';
|
import { SecurityToggle } from '../security/SecurityToggle';
|
||||||
import { ResponseStylesSection } from '../response_styles/ResponseStylesSection';
|
import { ResponseStylesSection } from '../response_styles/ResponseStylesSection';
|
||||||
|
import { GoosehintsSection } from './GoosehintsSection';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
||||||
|
|
||||||
export default function ChatSettingsSection() {
|
export default function ChatSettingsSection() {
|
||||||
@@ -40,6 +41,12 @@ export default function ChatSettingsSection() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card className="pb-2 rounded-lg">
|
||||||
|
<CardContent className="px-2">
|
||||||
|
<GoosehintsSection />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card className="pb-2 rounded-lg">
|
<Card className="pb-2 rounded-lg">
|
||||||
<CardHeader className="pb-0">
|
<CardHeader className="pb-0">
|
||||||
<CardTitle className="">Tool Selection Strategy (preview)</CardTitle>
|
<CardTitle className="">Tool Selection Strategy (preview)</CardTitle>
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
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 HelpText = () => (
|
||||||
|
<div className="text-sm flex-col space-y-4 text-textSubtle">
|
||||||
|
<p>
|
||||||
|
.goosehints is a text file used to provide additional context about your project and improve
|
||||||
|
the communication with Goose.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please make sure <span className="font-bold">Developer</span> extension is enabled in the
|
||||||
|
extensions page. This extension is required to use .goosehints. You'll need to restart your
|
||||||
|
session for .goosehints updates to take effect.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
See{' '}
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
className="text-blue-500 hover:text-blue-600 p-0 h-auto"
|
||||||
|
onClick={() =>
|
||||||
|
window.open('https://block.github.io/goose/docs/guides/using-goosehints/', '_blank')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
using .goosehints
|
||||||
|
</Button>{' '}
|
||||||
|
for more information.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ErrorDisplay = ({ error }: { error: Error }) => (
|
||||||
|
<div className="text-sm text-textSubtle">
|
||||||
|
<div className="text-red-600">Error reading .goosehints file: {JSON.stringify(error)}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FileInfo = ({ filePath, found }: { filePath: string; found: boolean }) => (
|
||||||
|
<div className="text-sm font-medium mb-2">
|
||||||
|
{found ? (
|
||||||
|
<div className="text-green-600">
|
||||||
|
<Check className="w-4 h-4 inline-block" /> .goosehints file found at: {filePath}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>Creating new .goosehints file at: {filePath}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const getGoosehintsFile = async (filePath: string) => await window.electron.readFile(filePath);
|
||||||
|
|
||||||
|
interface GoosehintsModalProps {
|
||||||
|
directory: string;
|
||||||
|
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GoosehintsModal = ({ directory, setIsGoosehintsModalOpen }: GoosehintsModalProps) => {
|
||||||
|
const goosehintsFilePath = `${directory}/.goosehints`;
|
||||||
|
const [goosehintsFile, setGoosehintsFile] = useState<string>('');
|
||||||
|
const [goosehintsFileFound, setGoosehintsFileFound] = useState<boolean>(false);
|
||||||
|
const [goosehintsFileReadError, setGoosehintsFileReadError] = useState<string>('');
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [saveSuccess, setSaveSuccess] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchGoosehintsFile = async () => {
|
||||||
|
try {
|
||||||
|
const { file, error, found } = await getGoosehintsFile(goosehintsFilePath);
|
||||||
|
setGoosehintsFile(file);
|
||||||
|
setGoosehintsFileFound(found);
|
||||||
|
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 () => {
|
||||||
|
setIsSaving(true);
|
||||||
|
setSaveSuccess(false);
|
||||||
|
try {
|
||||||
|
await window.electron.writeFile(goosehintsFilePath, goosehintsFile);
|
||||||
|
setSaveSuccess(true);
|
||||||
|
setGoosehintsFileFound(true);
|
||||||
|
setTimeout(() => setSaveSuccess(false), 3000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error writing .goosehints file:', error);
|
||||||
|
setGoosehintsFileReadError('Failed to save .goosehints file');
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={true} onOpenChange={(open) => setIsGoosehintsModalOpen(open)}>
|
||||||
|
<DialogContent className="w-[80vw] max-w-[80vw] sm:max-w-[80vw] max-h-[90vh] flex flex-col">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Configure Project Hints (.goosehints)</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Provide additional context about your project to improve communication with Goose
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto space-y-4 pt-2 pb-4">
|
||||||
|
<HelpText />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{goosehintsFileReadError ? (
|
||||||
|
<ErrorDisplay error={new Error(goosehintsFileReadError)} />
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<FileInfo filePath={goosehintsFilePath} found={goosehintsFileFound} />
|
||||||
|
<textarea
|
||||||
|
value={goosehintsFile}
|
||||||
|
className="w-full h-80 border rounded-md p-2 text-sm resize-none bg-background-default text-textStandard border-borderStandard focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
onChange={(event) => setGoosehintsFile(event.target.value)}
|
||||||
|
placeholder="Enter project hints here..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
{saveSuccess && (
|
||||||
|
<span className="text-green-600 text-sm flex items-center gap-1 mr-auto">
|
||||||
|
<Check className="w-4 h-4" />
|
||||||
|
Saved successfully
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<Button variant="outline" onClick={() => setIsGoosehintsModalOpen(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button onClick={writeFile} disabled={isSaving}>
|
||||||
|
{isSaving ? 'Saving...' : 'Save'}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '../../ui/button';
|
||||||
|
import { FolderKey } from 'lucide-react';
|
||||||
|
import { GoosehintsModal } from './GoosehintsModal';
|
||||||
|
|
||||||
|
export const GoosehintsSection = () => {
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const directory = window.appConfig?.get('GOOSE_WORKING_DIR') as string;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center justify-between px-2 py-2">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-text-default">Project Hints (.goosehints)</h3>
|
||||||
|
<p className="text-xs text-text-muted mt-[2px]">
|
||||||
|
Configure your project's .goosehints file to provide additional context to Goose
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<FolderKey size={16} />
|
||||||
|
Configure
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{isModalOpen && (
|
||||||
|
<GoosehintsModal directory={directory} setIsGoosehintsModalOpen={setIsModalOpen} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -77,7 +77,7 @@ export const VoiceDictationToggle = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`overflow-visible transition-all duration-300 ease-in-out ${
|
className={`overflow-hidden transition-all duration-300 ease-in-out ${
|
||||||
settings.enabled ? 'max-h-96 opacity-100 mt-2' : 'max-h-0 opacity-0 mt-0'
|
settings.enabled ? 'max-h-96 opacity-100 mt-2' : 'max-h-0 opacity-0 mt-0'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const ConversationLimitsDropdown = ({
|
|||||||
onClick={toggleExpanded}
|
onClick={toggleExpanded}
|
||||||
className="w-full flex items-center justify-between py-2 px-2 hover:bg-background-muted rounded-lg transition-all group"
|
className="w-full flex items-center justify-between py-2 px-2 hover:bg-background-muted rounded-lg transition-all group"
|
||||||
>
|
>
|
||||||
<h3 className="text-text-default font-medium">Conversation Limits</h3>
|
<h3 className="text-text-default">Conversation Limits</h3>
|
||||||
|
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`w-4 h-4 text-text-muted transition-transform duration-200 ease-in-out ${
|
className={`w-4 h-4 text-text-muted transition-transform duration-200 ease-in-out ${
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Sliders, ChefHat, Bot, Eye } from 'lucide-react';
|
import { Sliders, Bot } from 'lucide-react';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useModelAndProvider } from '../../../ModelAndProviderContext';
|
import { useModelAndProvider } from '../../../ModelAndProviderContext';
|
||||||
import { SwitchModelModal } from '../subcomponents/SwitchModelModal';
|
import { SwitchModelModal } from '../subcomponents/SwitchModelModal';
|
||||||
@@ -9,24 +9,18 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
DropdownMenuSeparator,
|
|
||||||
} from '../../../ui/dropdown-menu';
|
} from '../../../ui/dropdown-menu';
|
||||||
import { useCurrentModelInfo } from '../../../BaseChat';
|
import { useCurrentModelInfo } from '../../../BaseChat';
|
||||||
import { useConfig } from '../../../ConfigContext';
|
import { useConfig } from '../../../ConfigContext';
|
||||||
import { getProviderMetadata } from '../modelInterface';
|
import { getProviderMetadata } from '../modelInterface';
|
||||||
import { Alert } from '../../../alerts';
|
import { Alert } from '../../../alerts';
|
||||||
import BottomMenuAlertPopover from '../../../bottom_menu/BottomMenuAlertPopover';
|
import BottomMenuAlertPopover from '../../../bottom_menu/BottomMenuAlertPopover';
|
||||||
import { Recipe } from '../../../../recipe';
|
|
||||||
import CreateEditRecipeModal from '../../../recipes/CreateEditRecipeModal';
|
|
||||||
|
|
||||||
interface ModelsBottomBarProps {
|
interface ModelsBottomBarProps {
|
||||||
sessionId: string | null;
|
sessionId: string | null;
|
||||||
dropdownRef: React.RefObject<HTMLDivElement>;
|
dropdownRef: React.RefObject<HTMLDivElement>;
|
||||||
setView: (view: View) => void;
|
setView: (view: View) => void;
|
||||||
alerts: Alert[];
|
alerts: Alert[];
|
||||||
recipe?: Recipe | null;
|
|
||||||
recipeId?: string | null;
|
|
||||||
hasMessages?: boolean; // Add prop to know if there are messages to create a recipe from
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ModelsBottomBar({
|
export default function ModelsBottomBar({
|
||||||
@@ -34,9 +28,6 @@ export default function ModelsBottomBar({
|
|||||||
dropdownRef,
|
dropdownRef,
|
||||||
setView,
|
setView,
|
||||||
alerts,
|
alerts,
|
||||||
recipe,
|
|
||||||
recipeId,
|
|
||||||
hasMessages = false,
|
|
||||||
}: ModelsBottomBarProps) {
|
}: ModelsBottomBarProps) {
|
||||||
const {
|
const {
|
||||||
currentModel,
|
currentModel,
|
||||||
@@ -54,9 +45,6 @@ export default function ModelsBottomBar({
|
|||||||
const [isLeadWorkerActive, setIsLeadWorkerActive] = useState(false);
|
const [isLeadWorkerActive, setIsLeadWorkerActive] = useState(false);
|
||||||
const [providerDefaultModel, setProviderDefaultModel] = useState<string | null>(null);
|
const [providerDefaultModel, setProviderDefaultModel] = useState<string | null>(null);
|
||||||
|
|
||||||
// View recipe modal state
|
|
||||||
const [showViewRecipeModal, setShowViewRecipeModal] = useState(false);
|
|
||||||
|
|
||||||
// Check if lead/worker mode is active
|
// Check if lead/worker mode is active
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkLeadWorker = async () => {
|
const checkLeadWorker = async () => {
|
||||||
@@ -164,13 +152,6 @@ export default function ModelsBottomBar({
|
|||||||
})();
|
})();
|
||||||
}, [currentModel, getCurrentModelDisplayName]);
|
}, [currentModel, getCurrentModelDisplayName]);
|
||||||
|
|
||||||
// Handle view recipe - open modal instead of navigating
|
|
||||||
const handleViewRecipe = () => {
|
|
||||||
if (recipe) {
|
|
||||||
setShowViewRecipeModal(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-center" ref={dropdownRef}>
|
<div className="relative flex items-center" ref={dropdownRef}>
|
||||||
<BottomMenuAlertPopover alerts={alerts} />
|
<BottomMenuAlertPopover alerts={alerts} />
|
||||||
@@ -200,33 +181,6 @@ export default function ModelsBottomBar({
|
|||||||
<span>Lead/Worker Settings</span>
|
<span>Lead/Worker Settings</span>
|
||||||
<Sliders className="ml-auto h-4 w-4" />
|
<Sliders className="ml-auto h-4 w-4" />
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
{/* Recipe-specific menu items - only show when actively using a recipe */}
|
|
||||||
{recipe && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem onClick={handleViewRecipe}>
|
|
||||||
<span>View/Edit Recipe</span>
|
|
||||||
<Eye className="ml-auto h-4 w-4" />
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Only show "Create a recipe from this session" when there are messages AND no recipe is currently active */}
|
|
||||||
{hasMessages && !recipe && (
|
|
||||||
<>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
// Signal to create an agent from the current chat
|
|
||||||
window.dispatchEvent(new CustomEvent('make-agent-from-chat'));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>Create a recipe from this session</span>
|
|
||||||
<ChefHat className="ml-auto h-4 w-4" />
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
@@ -241,17 +195,6 @@ export default function ModelsBottomBar({
|
|||||||
{isLeadWorkerModalOpen ? (
|
{isLeadWorkerModalOpen ? (
|
||||||
<LeadWorkerSettings isOpen={isLeadWorkerModalOpen} onClose={handleLeadWorkerModalClose} />
|
<LeadWorkerSettings isOpen={isLeadWorkerModalOpen} onClose={handleLeadWorkerModalClose} />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* View Recipe Modal */}
|
|
||||||
{/* todo: we don't have the actual recipe name when in chat only in recipes list view so we generate it for now */}
|
|
||||||
{recipe && (
|
|
||||||
<CreateEditRecipeModal
|
|
||||||
isOpen={showViewRecipeModal}
|
|
||||||
onClose={() => setShowViewRecipeModal(false)}
|
|
||||||
recipe={recipe}
|
|
||||||
recipeId={recipeId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user