mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
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 { 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 (
|
||||
<Hub
|
||||
setView={setView}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
isExtensionsLoading={isExtensionsLoading}
|
||||
/>
|
||||
);
|
||||
return <Hub setView={setView} isExtensionsLoading={isExtensionsLoading} />;
|
||||
};
|
||||
|
||||
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 (
|
||||
<Pair
|
||||
key={sessionId}
|
||||
setChat={setChat}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
sessionId={sessionId}
|
||||
initialMessage={initialMessage}
|
||||
/>
|
||||
<Pair key={sessionId} setChat={setChat} sessionId={sessionId} initialMessage={initialMessage} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -356,7 +335,6 @@ const ExtensionsRoute = () => {
|
||||
|
||||
export function AppInner() {
|
||||
const [fatalError, setFatalError] = useState<string | null>(null);
|
||||
const [isGoosehintsModalOpen, setIsGoosehintsModalOpen] = useState(false);
|
||||
const [agentWaitingMessage, setAgentWaitingMessage] = useState<string | null>(null);
|
||||
const [isLoadingSharedSession, setIsLoadingSharedSession] = useState(false);
|
||||
const [sharedSessionError, setSharedSessionError] = useState<string | null>(null);
|
||||
@@ -652,27 +630,18 @@ export function AppInner() {
|
||||
contextKey="hub"
|
||||
agentWaitingMessage={agentWaitingMessage}
|
||||
>
|
||||
<AppLayout setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
|
||||
<AppLayout />
|
||||
</ChatProvider>
|
||||
</ProviderGuard>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<HubRouteWrapper
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
isExtensionsLoading={isExtensionsLoading}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route index element={<HubRouteWrapper isExtensionsLoading={isExtensionsLoading} />} />
|
||||
<Route
|
||||
path="pair"
|
||||
element={
|
||||
<PairRouteWrapper
|
||||
chat={chat}
|
||||
setChat={setChat}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
activeSessionId={activeSessionId}
|
||||
setActiveSessionId={setActiveSessionId}
|
||||
/>
|
||||
@@ -709,12 +678,6 @@ export function AppInner() {
|
||||
</Route>
|
||||
</Routes>
|
||||
</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 {
|
||||
setChat: (chat: ChatType) => void;
|
||||
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
|
||||
onMessageSubmit?: (message: string) => void;
|
||||
renderHeader?: () => React.ReactNode;
|
||||
customChatInputProps?: Record<string, unknown>;
|
||||
@@ -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}
|
||||
|
||||
@@ -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<number>(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}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -1500,21 +1499,34 @@ export default function ChatInput({
|
||||
<BottomMenuExtensionSelection sessionId={sessionId} />
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center h-full">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onClick={() => setIsGoosehintsModalOpen?.(true)}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex items-center justify-center text-text-default/70 hover:text-text-default text-xs cursor-pointer"
|
||||
>
|
||||
<FolderKey size={16} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Configure goosehints</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{sessionId && (
|
||||
<>
|
||||
<div className="w-px h-4 bg-border-default mx-2" />
|
||||
<div className="flex items-center h-full">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (recipe) {
|
||||
setShowEditRecipeModal(true);
|
||||
} else {
|
||||
setShowCreateRecipeModal(true);
|
||||
}
|
||||
}}
|
||||
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 && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -1552,6 +1564,23 @@ export default function ChatInput({
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
setView,
|
||||
setIsGoosehintsModalOpen,
|
||||
isExtensionsLoading,
|
||||
}: {
|
||||
setView: (view: View, viewOptions?: ViewOptions) => void;
|
||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
||||
isExtensionsLoading: boolean;
|
||||
}) {
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
@@ -61,7 +59,6 @@ export default function Hub({
|
||||
messages={[]}
|
||||
disableAnimation={false}
|
||||
sessionCosts={undefined}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
isExtensionsLoading={isExtensionsLoading}
|
||||
toolCount={0}
|
||||
/>
|
||||
|
||||
@@ -6,12 +6,7 @@ import { AppWindowMac, AppWindow } from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
import { Sidebar, SidebarInset, SidebarProvider, SidebarTrigger, useSidebar } from '../ui/sidebar';
|
||||
|
||||
interface AppLayoutProps {
|
||||
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
// Inner component that uses useSidebar within SidebarProvider context
|
||||
const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }) => {
|
||||
const AppLayoutContent: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const safeIsMacOS = (window?.electron?.platform || 'darwin') === 'darwin';
|
||||
@@ -99,7 +94,6 @@ const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }
|
||||
<AppSidebar
|
||||
onSelectSession={handleSelectSession}
|
||||
setView={setView}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
currentPath={location.pathname}
|
||||
/>
|
||||
</Sidebar>
|
||||
@@ -110,10 +104,10 @@ const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }
|
||||
);
|
||||
};
|
||||
|
||||
export const AppLayout: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }) => {
|
||||
export const AppLayout: React.FC = () => {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppLayoutContent setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
|
||||
<AppLayoutContent />
|
||||
</SidebarProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,21 +10,14 @@ export interface PairRouteState {
|
||||
|
||||
interface PairProps {
|
||||
setChat: (chat: ChatType) => void;
|
||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
||||
sessionId: string;
|
||||
initialMessage?: string;
|
||||
}
|
||||
|
||||
export default function Pair({
|
||||
setChat,
|
||||
setIsGoosehintsModalOpen,
|
||||
sessionId,
|
||||
initialMessage,
|
||||
}: PairProps) {
|
||||
export default function Pair({ setChat, sessionId, initialMessage }: PairProps) {
|
||||
return (
|
||||
<BaseChat
|
||||
setChat={setChat}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
sessionId={sessionId}
|
||||
initialMessage={initialMessage}
|
||||
suppressEmptyState={false}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ToolSelectionStrategySection } from '../tool_selection_strategy/ToolSel
|
||||
import DictationSection from '../dictation/DictationSection';
|
||||
import { SecurityToggle } from '../security/SecurityToggle';
|
||||
import { ResponseStylesSection } from '../response_styles/ResponseStylesSection';
|
||||
import { GoosehintsSection } from './GoosehintsSection';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
||||
|
||||
export default function ChatSettingsSection() {
|
||||
@@ -40,6 +41,12 @@ export default function ChatSettingsSection() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="pb-2 rounded-lg">
|
||||
<CardContent className="px-2">
|
||||
<GoosehintsSection />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="pb-2 rounded-lg">
|
||||
<CardHeader className="pb-0">
|
||||
<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
|
||||
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'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -23,7 +23,7 @@ export const ConversationLimitsDropdown = ({
|
||||
onClick={toggleExpanded}
|
||||
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
|
||||
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 { useModelAndProvider } from '../../../ModelAndProviderContext';
|
||||
import { SwitchModelModal } from '../subcomponents/SwitchModelModal';
|
||||
@@ -9,24 +9,18 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from '../../../ui/dropdown-menu';
|
||||
import { useCurrentModelInfo } from '../../../BaseChat';
|
||||
import { useConfig } from '../../../ConfigContext';
|
||||
import { getProviderMetadata } from '../modelInterface';
|
||||
import { Alert } from '../../../alerts';
|
||||
import BottomMenuAlertPopover from '../../../bottom_menu/BottomMenuAlertPopover';
|
||||
import { Recipe } from '../../../../recipe';
|
||||
import CreateEditRecipeModal from '../../../recipes/CreateEditRecipeModal';
|
||||
|
||||
interface ModelsBottomBarProps {
|
||||
sessionId: string | null;
|
||||
dropdownRef: React.RefObject<HTMLDivElement>;
|
||||
setView: (view: View) => void;
|
||||
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({
|
||||
@@ -34,9 +28,6 @@ export default function ModelsBottomBar({
|
||||
dropdownRef,
|
||||
setView,
|
||||
alerts,
|
||||
recipe,
|
||||
recipeId,
|
||||
hasMessages = false,
|
||||
}: ModelsBottomBarProps) {
|
||||
const {
|
||||
currentModel,
|
||||
@@ -54,9 +45,6 @@ export default function ModelsBottomBar({
|
||||
const [isLeadWorkerActive, setIsLeadWorkerActive] = useState(false);
|
||||
const [providerDefaultModel, setProviderDefaultModel] = useState<string | null>(null);
|
||||
|
||||
// View recipe modal state
|
||||
const [showViewRecipeModal, setShowViewRecipeModal] = useState(false);
|
||||
|
||||
// Check if lead/worker mode is active
|
||||
useEffect(() => {
|
||||
const checkLeadWorker = async () => {
|
||||
@@ -164,13 +152,6 @@ export default function ModelsBottomBar({
|
||||
})();
|
||||
}, [currentModel, getCurrentModelDisplayName]);
|
||||
|
||||
// Handle view recipe - open modal instead of navigating
|
||||
const handleViewRecipe = () => {
|
||||
if (recipe) {
|
||||
setShowViewRecipeModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center" ref={dropdownRef}>
|
||||
<BottomMenuAlertPopover alerts={alerts} />
|
||||
@@ -200,33 +181,6 @@ export default function ModelsBottomBar({
|
||||
<span>Lead/Worker Settings</span>
|
||||
<Sliders className="ml-auto h-4 w-4" />
|
||||
</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>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -241,17 +195,6 @@ export default function ModelsBottomBar({
|
||||
{isLeadWorkerModalOpen ? (
|
||||
<LeadWorkerSettings isOpen={isLeadWorkerModalOpen} onClose={handleLeadWorkerModalClose} />
|
||||
) : 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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user