Fix conversation initial state (#8647)

This commit is contained in:
tofarr 2025-05-23 11:36:58 -06:00 committed by GitHub
parent 31ad7fc175
commit 693d912361
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 72 additions and 44 deletions

View File

@ -56,15 +56,15 @@ function TestComponent() {
describe("WsClientProvider", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mock("#/hooks/query/use-user-conversation", () => ({
useUserConversation: () => {
vi.mock("#/hooks/query/use-active-conversation", () => ({
useActiveConversation: () => {
return { data: {
conversation_id: "1",
title: "Conversation 1",
selected_repository: null,
last_updated_at: "2021-10-01T12:00:00Z",
created_at: "2021-10-01T12:00:00Z",
status: "STOPPED" as const,
status: "RUNNING" as const,
url: null,
session_api_key: null,
}}},

View File

@ -4,8 +4,7 @@ import { useTranslation } from "react-i18next";
import { SuggestionItem } from "#/components/features/suggestions/suggestion-item";
import { I18nKey } from "#/i18n/declaration";
import { useUserProviders } from "#/hooks/use-user-providers";
import { useConversationId } from "#/hooks/use-conversation-id";
import { useUserConversation } from "#/hooks/query/use-user-conversation";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
interface ActionSuggestionsProps {
onSuggestionsClick: (value: string) => void;
@ -16,9 +15,7 @@ export function ActionSuggestions({
}: ActionSuggestionsProps) {
const { t } = useTranslation();
const { providers } = useUserProviders();
const { conversationId } = useConversationId();
const { data: conversation } = useUserConversation(conversationId);
const { data: conversation } = useActiveConversation();
const [hasPullRequest, setHasPullRequest] = React.useState(false);
const providersAreSet = providers.length > 0;

View File

@ -15,6 +15,7 @@ import {
} from "#/context/ws-client-provider";
import { useNotification } from "#/hooks/useNotification";
import { browserTab } from "#/utils/browser-tab";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
const notificationStates = [
AgentState.AWAITING_USER_INPUT,
@ -28,6 +29,7 @@ export function AgentStatusBar() {
const { curStatusMessage } = useSelector((state: RootState) => state.status);
const { status } = useWsClient();
const { notify } = useNotification();
const { data: conversation } = useActiveConversation();
const [statusMessage, setStatusMessage] = React.useState<string>("");
@ -78,7 +80,10 @@ export function AgentStatusBar() {
);
React.useEffect(() => {
if (status === WsClientProviderStatus.DISCONNECTED) {
if (conversation?.status === "STARTING") {
setStatusMessage(t(I18nKey.STATUS$STARTING_RUNTIME));
setIndicatorColor(IndicatorColor.RED);
} else if (status === WsClientProviderStatus.DISCONNECTED) {
setStatusMessage(t(I18nKey.STATUS$CONNECTED)); // Using STATUS$CONNECTED instead of STATUS$CONNECTING
setIndicatorColor(IndicatorColor.RED);
} else {
@ -97,7 +102,7 @@ export function AgentStatusBar() {
}
}
}
}, [curAgentState, status, notify, t]);
}, [curAgentState, status, notify, t, conversation?.status]);
return (
<div className="flex flex-col items-center">

View File

@ -1,9 +1,8 @@
import { useParams } from "react-router";
import React from "react";
import { AgentControlBar } from "./agent-control-bar";
import { AgentStatusBar } from "./agent-status-bar";
import { SecurityLock } from "./security-lock";
import { useUserConversation } from "#/hooks/query/use-user-conversation";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { ConversationCard } from "../conversation-panel/conversation-card";
interface ControlsProps {
@ -12,10 +11,7 @@ interface ControlsProps {
}
export function Controls({ setSecurityOpen, showSecurityLock }: ControlsProps) {
const params = useParams();
const { data: conversation } = useUserConversation(
params.conversationId ?? null,
);
const { data: conversation } = useActiveConversation();
return (
<div className="flex flex-col gap-2 md:items-center md:justify-between md:flex-row">

View File

@ -16,7 +16,7 @@ import {
} from "#/types/core/actions";
import { Conversation } from "#/api/open-hands.types";
import { useUserProviders } from "#/hooks/use-user-providers";
import { useUserConversation } from "#/hooks/query/use-user-conversation";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { OpenHandsObservation } from "#/types/core/observations";
import {
isErrorObservation,
@ -68,6 +68,7 @@ const isMessageAction = (
export enum WsClientProviderStatus {
CONNECTED,
DISCONNECTED,
CONNECTING,
}
interface UseWsClient {
@ -147,7 +148,7 @@ export function WsClientProvider({
const { providers } = useUserProviders();
const messageRateHandler = useRate({ threshold: 250 });
const { data: conversation } = useUserConversation(conversationId);
const { data: conversation } = useActiveConversation();
function send(event: Record<string, unknown>) {
if (!sioRef.current) {

View File

@ -0,0 +1,14 @@
import { useConversationId } from "#/hooks/use-conversation-id";
import { useUserConversation } from "./use-user-conversation";
const FIVE_MINUTES = 1000 * 60 * 5;
export const useActiveConversation = () => {
const { conversationId } = useConversationId();
return useUserConversation(conversationId, (query) => {
if (query.state.data?.status === "STARTING") {
return 2000; // 2 seconds
}
return FIVE_MINUTES;
});
};

View File

@ -6,12 +6,16 @@ import OpenHands from "#/api/open-hands";
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
import { RootState } from "#/store";
import { useConversationId } from "#/hooks/use-conversation-id";
import { useActiveConversation } from "./use-active-conversation";
export const useActiveHost = () => {
const { curAgentState } = useSelector((state: RootState) => state.agent);
const [activeHost, setActiveHost] = React.useState<string | null>(null);
const { conversationId } = useConversationId();
const { data: conversation } = useActiveConversation();
const enabled =
conversation?.status === "RUNNING" &&
RUNTIME_INACTIVE_STATES.includes(curAgentState);
const { data } = useQuery({
queryKey: [conversationId, "hosts"],
@ -19,7 +23,7 @@ export const useActiveHost = () => {
const hosts = await OpenHands.getWebHosts(conversationId);
return { hosts };
},
enabled: !RUNTIME_INACTIVE_STATES.includes(curAgentState),
enabled,
initialData: { hosts: [] },
meta: {
disableToast: true,
@ -37,7 +41,7 @@ export const useActiveHost = () => {
return "";
}
},
refetchInterval: 3000,
// refetchInterval: 3000,
meta: {
disableToast: true,
},

View File

@ -6,14 +6,18 @@ import { useConversationId } from "#/hooks/use-conversation-id";
import { GitChange } from "#/api/open-hands.types";
import { RootState } from "#/store";
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
import { useActiveConversation } from "./use-active-conversation";
export const useGetGitChanges = () => {
const { conversationId } = useConversationId();
const { data: conversation } = useActiveConversation();
const [orderedChanges, setOrderedChanges] = React.useState<GitChange[]>([]);
const previousDataRef = React.useRef<GitChange[]>(null);
const { curAgentState } = useSelector((state: RootState) => state.agent);
const runtimeIsActive = !RUNTIME_INACTIVE_STATES.includes(curAgentState);
const enabled =
conversation?.status === "RUNNING" &&
RUNTIME_INACTIVE_STATES.includes(curAgentState);
const result = useQuery({
queryKey: ["file_changes", conversationId],
@ -21,7 +25,7 @@ export const useGetGitChanges = () => {
retry: false,
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 15, // 15 minutes
enabled: runtimeIsActive,
enabled,
meta: {
disableToast: true,
},

View File

@ -1,10 +1,24 @@
import { useQuery } from "@tanstack/react-query";
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Query, useQuery } from "@tanstack/react-query";
import { AxiosError } from "axios";
import OpenHands from "#/api/open-hands";
import { Conversation } from "#/api/open-hands.types";
const FIVE_MINUTES = 1000 * 60 * 5;
const FIFTEEN_MINUTES = 1000 * 60 * 15;
type RefetchInterval = (
query: Query<
Conversation | null,
AxiosError<unknown, any>,
Conversation | null,
(string | null)[]
>,
) => number;
export const useUserConversation = (cid: string | null) =>
export const useUserConversation = (
cid: string | null,
refetchInterval?: RefetchInterval,
) =>
useQuery({
queryKey: ["user", "conversation", cid],
queryFn: async () => {
@ -14,12 +28,7 @@ export const useUserConversation = (cid: string | null) =>
},
enabled: !!cid,
retry: false,
refetchInterval: (query) => {
if (query.state.data?.status === "STARTING") {
return 2000; // 2 seconds
}
return FIVE_MINUTES;
},
refetchInterval,
staleTime: FIVE_MINUTES,
gcTime: FIFTEEN_MINUTES,
});

View File

@ -7,6 +7,7 @@ import { I18nKey } from "#/i18n/declaration";
import { RootState } from "#/store";
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
import { useActiveConversation } from "./use-active-conversation";
// Define the return type for the VS Code URL query
interface VSCodeUrlResult {
@ -17,8 +18,11 @@ interface VSCodeUrlResult {
export const useVSCodeUrl = () => {
const { t } = useTranslation();
const { conversationId } = useConversationId();
const { data: conversation } = useActiveConversation();
const { curAgentState } = useSelector((state: RootState) => state.agent);
const isRuntimeInactive = RUNTIME_INACTIVE_STATES.includes(curAgentState);
const enabled =
conversation?.status === "RUNNING" &&
RUNTIME_INACTIVE_STATES.includes(curAgentState);
return useQuery<VSCodeUrlResult>({
queryKey: ["vscode_url", conversationId],
@ -36,7 +40,7 @@ export const useVSCodeUrl = () => {
error: t(I18nKey.VSCODE$URL_NOT_AVAILABLE),
};
},
enabled: !!conversationId && !isRuntimeInactive,
enabled,
refetchOnMount: true,
retry: 3,
});

View File

@ -1,6 +1,5 @@
import { useParams } from "react-router";
import { useEffect, useRef } from "react";
import { useUserConversation } from "./query/use-user-conversation";
import { useActiveConversation } from "./query/use-active-conversation";
/**
* Hook that updates the document title based on the current conversation.
@ -9,10 +8,7 @@ import { useUserConversation } from "./query/use-user-conversation";
* @param suffix Optional suffix to append to the title (default: "OpenHands")
*/
export function useDocumentTitleFromState(suffix = "OpenHands") {
const params = useParams();
const { data: conversation } = useUserConversation(
params.conversationId ?? null,
);
const { data: conversation } = useActiveConversation();
const lastValidTitleRef = useRef<string | null>(null);
useEffect(() => {

View File

@ -27,7 +27,7 @@ import {
ResizablePanel,
} from "#/components/layout/resizable-panel";
import Security from "#/components/shared/modals/security/security";
import { useUserConversation } from "#/hooks/query/use-user-conversation";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { ServedAppLabel } from "#/components/layout/served-app-label";
import { useSettings } from "#/hooks/query/use-settings";
import { RootState } from "#/store";
@ -42,9 +42,7 @@ function AppContent() {
const { t } = useTranslation();
const { data: settings } = useSettings();
const { conversationId } = useConversationId();
const { data: conversation, isFetched } = useUserConversation(
conversationId || null,
);
const { data: conversation, isFetched } = useActiveConversation();
const { curAgentState } = useSelector((state: RootState) => state.agent);
const dispatch = useDispatch();

View File

@ -147,7 +147,7 @@ class DockerRuntime(ActionExecutionClient):
except docker.errors.NotFound as e:
if self.attach_to_existing:
self.log(
'error',
'warning',
f'Container {self.container_name} not found.',
)
raise AgentRuntimeDisconnectedError from e