Files
tkmind_go/ui/desktop/src/components/pair.tsx
T

137 lines
4.4 KiB
TypeScript

import { useEffect, useState } from 'react';
import { View, ViewOptions } from '../utils/navigationUtils';
import BaseChat from './BaseChat';
import { useRecipeManager } from '../hooks/useRecipeManager';
import { useIsMobile } from '../hooks/use-mobile';
import { useSidebar } from './ui/sidebar';
import { AgentState, InitializationContext } from '../hooks/useAgent';
import 'react-toastify/dist/ReactToastify.css';
import { cn } from '../utils';
import { ChatType } from '../types/chat';
import { useSearchParams } from 'react-router-dom';
export interface PairRouteState {
resumeSessionId?: string;
initialMessage?: string;
}
interface PairProps {
chat: ChatType;
setChat: (chat: ChatType) => void;
setView: (view: View, viewOptions?: ViewOptions) => void;
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
setFatalError: (value: ((prevState: string | null) => string | null) | string | null) => void;
setAgentWaitingMessage: (msg: string | null) => void;
agentState: AgentState;
loadCurrentChat: (context: InitializationContext) => Promise<ChatType>;
}
export default function Pair({
chat,
setChat,
setView,
setIsGoosehintsModalOpen,
setFatalError,
setAgentWaitingMessage,
agentState,
loadCurrentChat,
resumeSessionId,
initialMessage,
}: PairProps & PairRouteState) {
const isMobile = useIsMobile();
const { state: sidebarState } = useSidebar();
const [hasProcessedInitialInput, setHasProcessedInitialInput] = useState(false);
const [shouldAutoSubmit, setShouldAutoSubmit] = useState(false);
const [messageToSubmit, setMessageToSubmit] = useState<string | null>(null);
const [isTransitioningFromHub, setIsTransitioningFromHub] = useState(false);
const [loadingChat, setLoadingChat] = useState(false);
const [_searchParams, setSearchParams] = useSearchParams();
useEffect(() => {
const initializeFromState = async () => {
setLoadingChat(true);
try {
const chat = await loadCurrentChat({
resumeSessionId,
setAgentWaitingMessage,
});
setChat(chat);
setSearchParams((prev) => {
prev.set('resumeSessionId', chat.sessionId);
return prev;
});
} catch (error) {
console.log(error);
setFatalError(`Agent init failure: ${error instanceof Error ? error.message : '' + error}`);
} finally {
setLoadingChat(false);
}
};
initializeFromState();
}, [
agentState,
setChat,
setFatalError,
setAgentWaitingMessage,
loadCurrentChat,
resumeSessionId,
setSearchParams,
]);
// Followed by sending the initialMessage if we have one. This will happen
// only once, unless we reset the chat in step one.
useEffect(() => {
if (agentState !== AgentState.INITIALIZED || !initialMessage || hasProcessedInitialInput) {
return;
}
setIsTransitioningFromHub(true);
setHasProcessedInitialInput(true);
setMessageToSubmit(initialMessage);
setShouldAutoSubmit(true);
}, [agentState, initialMessage, hasProcessedInitialInput]);
useEffect(() => {
if (agentState === AgentState.NO_PROVIDER) {
setView('welcome');
}
}, [agentState, setView]);
const { initialPrompt: recipeInitialPrompt } = useRecipeManager(chat, chat.recipe || null);
const handleMessageSubmit = (message: string) => {
// Clean up any auto submit state:
setShouldAutoSubmit(false);
setIsTransitioningFromHub(false);
setMessageToSubmit(null);
console.log('Message submitted:', message);
};
const recipePrompt =
agentState === 'initialized' && chat.messages.length === 0 && recipeInitialPrompt;
const initialValue = messageToSubmit || recipePrompt || undefined;
const customChatInputProps = {
// Pass initial message from Hub or recipe prompt
initialValue,
};
return (
<BaseChat
chat={chat}
loadingChat={loadingChat}
autoSubmit={shouldAutoSubmit}
setChat={setChat}
setView={setView}
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
onMessageSubmit={handleMessageSubmit}
customChatInputProps={customChatInputProps}
contentClassName={cn('pr-1 pb-10', (isMobile || sidebarState === 'collapsed') && 'pt-11')} // Use dynamic content class with mobile margin and sidebar state
showPopularTopics={!isTransitioningFromHub} // Don't show popular topics while transitioning from Hub
suppressEmptyState={isTransitioningFromHub} // Suppress all empty state content while transitioning from Hub
/>
);
}