mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Fix conversation initial state (#8647)
This commit is contained in:
parent
31ad7fc175
commit
693d912361
@ -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,
|
||||
}}},
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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) {
|
||||
|
||||
14
frontend/src/hooks/query/use-active-conversation.ts
Normal file
14
frontend/src/hooks/query/use-active-conversation.ts
Normal 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;
|
||||
});
|
||||
};
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user