Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 11 additions & 38 deletions ui/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -233,6 +203,13 @@ export default function App() {
return <ErrorScreen error={fatalError} onReload={() => window.electron.reloadApp()} />;
}

if (isLoadingSession)
return (
<div className="flex justify-center items-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-textStandard"></div>
</div>
);

return (
<>
<ToastContainer
Expand Down Expand Up @@ -300,16 +277,12 @@ export default function App() {
)}
{view === 'chat' && !isLoadingSession && (
<ChatView
chat={chat}
setChat={setChat}
setView={setView}
viewOptions={viewOptions}
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
/>
)}
{view === 'chat' && isLoadingSession && (
<div className="flex justify-center items-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-textStandard"></div>
</div>
)}
{view === 'sessions' && <SessionsView setView={setView} />}
</div>
</div>
Expand Down
40 changes: 8 additions & 32 deletions ui/desktop/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<any, any>) => void;
viewOptions?: Record<any, any>;
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<ChatType>(() => {
// 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<Record<string, string[]>>({});
const [hasMessages, setHasMessages] = useState(false);
const [lastInteractionTime, setLastInteractionTime] = useState<number>(Date.now());
Expand All @@ -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();

Expand Down Expand Up @@ -122,7 +98,7 @@ export default function ChatView({
const updatedChat = { ...prevChat, messages };
return updatedChat;
});
}, [messages, sessionId, resumedSession]);
}, [messages]);

useEffect(() => {
if (messages.length > 0) {
Expand Down
55 changes: 55 additions & 0 deletions ui/desktop/src/hooks/useChat.ts
Original file line number Diff line number Diff line change
@@ -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<ChatType>({
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 };
};
12 changes: 2 additions & 10 deletions ui/desktop/src/sessions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getApiUrl, getSecretKey } from './config';
import { Message } from './types/message';

export interface SessionMetadata {
description: string;
Expand Down Expand Up @@ -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[];
Comment thread
matthewdiamant marked this conversation as resolved.
messages: Message[];
}

/**
Expand Down