Spaces:
Build error
Build error
| import { useSelector } from "react-redux"; | |
| import React from "react"; | |
| import posthog from "posthog-js"; | |
| import { useParams } from "react-router"; | |
| import { useTranslation } from "react-i18next"; | |
| import { I18nKey } from "#/i18n/declaration"; | |
| import { convertImageToBase64 } from "#/utils/convert-image-to-base-64"; | |
| import { TrajectoryActions } from "../trajectory/trajectory-actions"; | |
| import { createChatMessage } from "#/services/chat-service"; | |
| import { InteractiveChatBox } from "./interactive-chat-box"; | |
| import { RootState } from "#/store"; | |
| import { AgentState } from "#/types/agent-state"; | |
| import { generateAgentStateChangeEvent } from "#/services/agent-state-service"; | |
| import { FeedbackModal } from "../feedback/feedback-modal"; | |
| import { useScrollToBottom } from "#/hooks/use-scroll-to-bottom"; | |
| import { TypingIndicator } from "./typing-indicator"; | |
| import { useWsClient } from "#/context/ws-client-provider"; | |
| import { Messages } from "./messages"; | |
| import { ChatSuggestions } from "./chat-suggestions"; | |
| import { ActionSuggestions } from "./action-suggestions"; | |
| import { ScrollToBottomButton } from "#/components/shared/buttons/scroll-to-bottom-button"; | |
| import { LoadingSpinner } from "#/components/shared/loading-spinner"; | |
| import { useGetTrajectory } from "#/hooks/mutation/use-get-trajectory"; | |
| import { downloadTrajectory } from "#/utils/download-trajectory"; | |
| import { displayErrorToast } from "#/utils/custom-toast-handlers"; | |
| import { useOptimisticUserMessage } from "#/hooks/use-optimistic-user-message"; | |
| import { useWSErrorMessage } from "#/hooks/use-ws-error-message"; | |
| import { ErrorMessageBanner } from "./error-message-banner"; | |
| import { shouldRenderEvent } from "./event-content-helpers/should-render-event"; | |
| function getEntryPoint( | |
| hasRepository: boolean | null, | |
| hasReplayJson: boolean | null, | |
| ): string { | |
| if (hasRepository) return "github"; | |
| if (hasReplayJson) return "replay"; | |
| return "direct"; | |
| } | |
| export function ChatInterface() { | |
| const { getErrorMessage } = useWSErrorMessage(); | |
| const { send, isLoadingMessages, parsedEvents } = useWsClient(); | |
| const { setOptimisticUserMessage, getOptimisticUserMessage } = | |
| useOptimisticUserMessage(); | |
| const { t } = useTranslation(); | |
| const scrollRef = React.useRef<HTMLDivElement>(null); | |
| const { scrollDomToBottom, onChatBodyScroll, hitBottom } = | |
| useScrollToBottom(scrollRef); | |
| const { curAgentState } = useSelector((state: RootState) => state.agent); | |
| const [feedbackPolarity, setFeedbackPolarity] = React.useState< | |
| "positive" | "negative" | |
| >("positive"); | |
| const [feedbackModalIsOpen, setFeedbackModalIsOpen] = React.useState(false); | |
| const [messageToSend, setMessageToSend] = React.useState<string | null>(null); | |
| const { selectedRepository, replayJson } = useSelector( | |
| (state: RootState) => state.initialQuery, | |
| ); | |
| const params = useParams(); | |
| const { mutate: getTrajectory } = useGetTrajectory(); | |
| const optimisticUserMessage = getOptimisticUserMessage(); | |
| const errorMessage = getErrorMessage(); | |
| const events = parsedEvents.filter(shouldRenderEvent); | |
| const handleSendMessage = async (content: string, files: File[]) => { | |
| if (events.length === 0) { | |
| posthog.capture("initial_query_submitted", { | |
| entry_point: getEntryPoint( | |
| selectedRepository !== null, | |
| replayJson !== null, | |
| ), | |
| query_character_length: content.length, | |
| replay_json_size: replayJson?.length, | |
| }); | |
| } else { | |
| posthog.capture("user_message_sent", { | |
| session_message_count: events.length, | |
| current_message_length: content.length, | |
| }); | |
| } | |
| const promises = files.map((file) => convertImageToBase64(file)); | |
| const imageUrls = await Promise.all(promises); | |
| const timestamp = new Date().toISOString(); | |
| send(createChatMessage(content, imageUrls, timestamp)); | |
| setOptimisticUserMessage(content); | |
| setMessageToSend(null); | |
| }; | |
| const handleStop = () => { | |
| posthog.capture("stop_button_clicked"); | |
| send(generateAgentStateChangeEvent(AgentState.STOPPED)); | |
| }; | |
| const onClickShareFeedbackActionButton = async ( | |
| polarity: "positive" | "negative", | |
| ) => { | |
| setFeedbackModalIsOpen(true); | |
| setFeedbackPolarity(polarity); | |
| }; | |
| const onClickExportTrajectoryButton = () => { | |
| if (!params.conversationId) { | |
| displayErrorToast(t(I18nKey.CONVERSATION$DOWNLOAD_ERROR)); | |
| return; | |
| } | |
| getTrajectory(params.conversationId, { | |
| onSuccess: async (data) => { | |
| await downloadTrajectory( | |
| params.conversationId ?? t(I18nKey.CONVERSATION$UNKNOWN), | |
| data.trajectory, | |
| ); | |
| }, | |
| onError: () => { | |
| displayErrorToast(t(I18nKey.CONVERSATION$DOWNLOAD_ERROR)); | |
| }, | |
| }); | |
| }; | |
| const isWaitingForUserInput = | |
| curAgentState === AgentState.AWAITING_USER_INPUT || | |
| curAgentState === AgentState.FINISHED; | |
| return ( | |
| <div className="h-full flex flex-col justify-between"> | |
| {events.length === 0 && !optimisticUserMessage && ( | |
| <ChatSuggestions onSuggestionsClick={setMessageToSend} /> | |
| )} | |
| <div | |
| ref={scrollRef} | |
| onScroll={(e) => onChatBodyScroll(e.currentTarget)} | |
| className="scrollbar scrollbar-thin scrollbar-thumb-gray-400 scrollbar-thumb-rounded-full scrollbar-track-gray-800 hover:scrollbar-thumb-gray-300 flex flex-col grow overflow-y-auto overflow-x-hidden px-4 pt-4 gap-2 fast-smooth-scroll" | |
| > | |
| {isLoadingMessages && ( | |
| <div className="flex justify-center"> | |
| <LoadingSpinner size="small" /> | |
| </div> | |
| )} | |
| {!isLoadingMessages && ( | |
| <Messages | |
| messages={events} | |
| isAwaitingUserConfirmation={ | |
| curAgentState === AgentState.AWAITING_USER_CONFIRMATION | |
| } | |
| /> | |
| )} | |
| {isWaitingForUserInput && | |
| events.length > 0 && | |
| !optimisticUserMessage && ( | |
| <ActionSuggestions | |
| onSuggestionsClick={(value) => handleSendMessage(value, [])} | |
| /> | |
| )} | |
| </div> | |
| <div className="flex flex-col gap-[6px] px-4 pb-4"> | |
| <div className="flex justify-between relative"> | |
| <TrajectoryActions | |
| onPositiveFeedback={() => | |
| onClickShareFeedbackActionButton("positive") | |
| } | |
| onNegativeFeedback={() => | |
| onClickShareFeedbackActionButton("negative") | |
| } | |
| onExportTrajectory={() => onClickExportTrajectoryButton()} | |
| /> | |
| <div className="absolute left-1/2 transform -translate-x-1/2 bottom-0"> | |
| {curAgentState === AgentState.RUNNING && <TypingIndicator />} | |
| </div> | |
| {!hitBottom && <ScrollToBottomButton onClick={scrollDomToBottom} />} | |
| </div> | |
| {errorMessage && <ErrorMessageBanner message={errorMessage} />} | |
| <InteractiveChatBox | |
| onSubmit={handleSendMessage} | |
| onStop={handleStop} | |
| isDisabled={ | |
| curAgentState === AgentState.LOADING || | |
| curAgentState === AgentState.AWAITING_USER_CONFIRMATION | |
| } | |
| mode={curAgentState === AgentState.RUNNING ? "stop" : "submit"} | |
| value={messageToSend ?? undefined} | |
| onChange={setMessageToSend} | |
| /> | |
| </div> | |
| <FeedbackModal | |
| isOpen={feedbackModalIsOpen} | |
| onClose={() => setFeedbackModalIsOpen(false)} | |
| polarity={feedbackPolarity} | |
| /> | |
| </div> | |
| ); | |
| } | |