diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 9980462d..c9e9dc3a 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -21,6 +21,7 @@ import MoreModelsView from './components/settings/models/MoreModelsView'; import ConfigureProvidersView from './components/settings/providers/ConfigureProvidersView'; import SessionsView from './components/sessions/SessionsView'; import ProviderSettings from './components/settings_v2/providers/ProviderSettingsPage'; +import { useChat } from './hooks/useChat'; import 'react-toastify/dist/ReactToastify.css'; @@ -151,38 +152,7 @@ export default function App() { setupStoredProvider(); }, []); - // Check for resumeSessionId in URL parameters - useEffect(() => { - const checkForResumeSession = async () => { - const urlParams = new URLSearchParams(window.location.search); - const resumeSessionId = urlParams.get('resumeSessionId'); - - if (!resumeSessionId) { - return; - } - - setIsLoadingSession(true); - try { - const sessionDetails = await fetchSessionDetails(resumeSessionId); - - // Only set view if we have valid session details - if (sessionDetails && sessionDetails.session_id) { - setView('chat', { - resumedSession: sessionDetails, - }); - } else { - console.error('Invalid session details received'); - } - } catch (error) { - console.error('Failed to fetch session details:', error); - } finally { - // Always clear the loading state - setIsLoadingSession(false); - } - }; - - checkForResumeSession(); - }, []); + const { chat, setChat } = useChat({ setView, setIsLoadingSession }); useEffect(() => { const handleFatalError = (_: any, errorMessage: string) => { @@ -233,6 +203,13 @@ export default function App() { return window.electron.reloadApp()} />; } + if (isLoadingSession) + return ( +
+
+
+ ); + return ( <> )} - {view === 'chat' && isLoadingSession && ( -
-
-
- )} {view === 'sessions' && } diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index 096e214b..efcea424 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -27,7 +27,7 @@ import { } from '../types/message'; export interface ChatType { - id: number; + id: string; title: string; // messages up to this index are presumed to be "history" from a resumed session, this is used to track older tool confirmation requests // anything before this index should not render any buttons, but anything after should @@ -36,40 +36,16 @@ export interface ChatType { } export default function ChatView({ + chat, + setChat, setView, - viewOptions, setIsGoosehintsModalOpen, }: { + chat: ChatType; + setChat: (chat: ChatType) => void; setView: (view: View, viewOptions?: Record) => void; - viewOptions?: Record; setIsGoosehintsModalOpen: (isOpen: boolean) => void; }) { - // Check if we're resuming a session - const resumedSession = viewOptions?.resumedSession; - - // Generate or retrieve session ID - // The session ID should not change for the duration of the chat - const sessionId = resumedSession?.session_id || generateSessionId(); - - const [chat, setChat] = useState(() => { - // If resuming a session, convert the session messages to our format - if (resumedSession) { - return { - id: resumedSession.session_id, - title: resumedSession.metadata?.description || `ID: ${resumedSession.session_id}`, - messages: resumedSession.messages, - messageHistoryIndex: resumedSession.messages.length, - }; - } - - return { - id: sessionId, - title: 'New Chat', - messages: [], - messageHistoryIndex: 0, - }; - }); - const [messageMetadata, setMessageMetadata] = useState>({}); const [hasMessages, setHasMessages] = useState(false); const [lastInteractionTime, setLastInteractionTime] = useState(Date.now()); @@ -89,8 +65,8 @@ export default function ChatView({ handleSubmit: _submitMessage, } = useMessageStream({ api: getApiUrl('/reply'), - initialMessages: resumedSession ? resumedSession.messages : chat?.messages || [], - body: { session_id: sessionId, session_working_dir: window.appConfig.get('GOOSE_WORKING_DIR') }, + initialMessages: chat.messages, + body: { session_id: chat.id, session_working_dir: window.appConfig.get('GOOSE_WORKING_DIR') }, onFinish: async (message, _reason) => { window.electron.stopPowerSaveBlocker(); @@ -122,7 +98,7 @@ export default function ChatView({ const updatedChat = { ...prevChat, messages }; return updatedChat; }); - }, [messages, sessionId, resumedSession]); + }, [messages]); useEffect(() => { if (messages.length > 0) { diff --git a/ui/desktop/src/hooks/useChat.ts b/ui/desktop/src/hooks/useChat.ts new file mode 100644 index 00000000..44cf4f9e --- /dev/null +++ b/ui/desktop/src/hooks/useChat.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react'; +import { ChatType } from '../components/ChatView'; +import { fetchSessionDetails, generateSessionId } from '../sessions'; + +type UseChatArgs = { + setIsLoadingSession: (isLoading: boolean) => void; + setView: (view: string) => void; +}; +export const useChat = ({ setIsLoadingSession, setView }: UseChatArgs) => { + const [chat, setChat] = useState({ + id: generateSessionId(), + title: 'New Chat', + messages: [], + messageHistoryIndex: 0, + }); + + // Check for resumeSessionId in URL parameters + useEffect(() => { + const checkForResumeSession = async () => { + const urlParams = new URLSearchParams(window.location.search); + const resumeSessionId = urlParams.get('resumeSessionId'); + + if (!resumeSessionId) { + return; + } + + setIsLoadingSession(true); + try { + const sessionDetails = await fetchSessionDetails(resumeSessionId); + + // Only set view if we have valid session details + if (sessionDetails && sessionDetails.session_id) { + setChat({ + id: sessionDetails.session_id, + title: sessionDetails.metadata?.description || `ID: ${sessionDetails.session_id}`, + messages: sessionDetails.messages, + messageHistoryIndex: sessionDetails.messages.length, + }); + setView('chat'); + } else { + console.error('Invalid session details received'); + } + } catch (error) { + console.error('Failed to fetch session details:', error); + } finally { + // Always clear the loading state + setIsLoadingSession(false); + } + }; + + checkForResumeSession(); + }, []); + + return { chat, setChat }; +}; diff --git a/ui/desktop/src/sessions.ts b/ui/desktop/src/sessions.ts index 2e1797a5..08a7bd7a 100644 --- a/ui/desktop/src/sessions.ts +++ b/ui/desktop/src/sessions.ts @@ -1,4 +1,5 @@ import { getApiUrl, getSecretKey } from './config'; +import { Message } from './types/message'; export interface SessionMetadata { description: string; @@ -28,19 +29,10 @@ export interface SessionsResponse { sessions: Session[]; } -export interface SessionMessage { - role: 'user' | 'assistant'; - created: number; - content: { - type: string; - text: string; - }[]; -} - export interface SessionDetails { session_id: string; metadata: SessionMetadata; - messages: SessionMessage[]; + messages: Message[]; } /**