From 321733360fab5cf320fc517ff9fe1d01002acac7 Mon Sep 17 00:00:00 2001 From: Lluis Agusti Date: Thu, 5 Feb 2026 22:43:28 +0800 Subject: [PATCH] chore: refactor hook --- .../src/app/(platform)/copilot-2/page.tsx | 177 ++--------------- .../(platform)/copilot-2/useCopilotPage.ts | 180 ++++++++++++++++++ 2 files changed, 192 insertions(+), 165 deletions(-) create mode 100644 autogpt_platform/frontend/src/app/(platform)/copilot-2/useCopilotPage.ts diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot-2/page.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot-2/page.tsx index 0277c02d66..8209670b1c 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot-2/page.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/copilot-2/page.tsx @@ -1,177 +1,24 @@ "use client"; -import { useChat } from "@ai-sdk/react"; -import { DefaultChatTransport } from "ai"; -import { useEffect, useMemo, useRef, useState } from "react"; -import { parseAsString, useQueryState } from "nuqs"; import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar"; import { ChatContainer } from "./components/ChatContainer/ChatContainer"; import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar"; import { Button } from "@/components/ui/button"; import { CopyIcon, CheckIcon } from "@phosphor-icons/react"; -import { - getGetV2ListSessionsQueryKey, - getV2GetSession, - postV2CreateSession, -} from "@/app/api/__generated__/endpoints/chat/chat"; -import { convertChatSessionMessagesToUiMessages } from "./helpers/convertChatSessionToUiMessages"; -import { useQueryClient } from "@tanstack/react-query"; +import { useCopilotPage } from "./useCopilotPage"; export default function Page() { - const [copied, setCopied] = useState(false); - const [isCreatingSession, setIsCreatingSession] = useState(false); - const [sessionId, setSessionId] = useQueryState("sessionId", parseAsString); - const hydrationSeq = useRef(0); - const lastHydratedSessionIdRef = useRef(null); - const createSessionPromiseRef = useRef | null>(null); - const queuedFirstMessageRef = useRef(null); - const queuedFirstMessageResolverRef = useRef<(() => void) | null>(null); - const queryClient = useQueryClient(); - - function handleCopySessionId() { - if (!sessionId) return; - navigator.clipboard.writeText(sessionId); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } - - const transport = useMemo(() => { - if (!sessionId) return null; - return new DefaultChatTransport({ - api: `/api/chat/sessions/${sessionId}/stream`, - prepareSendMessagesRequest: ({ messages }) => { - const last = messages[messages.length - 1]; - return { - body: { - message: last.parts - ?.map((p) => (p.type === "text" ? p.text : "")) - .join(""), - is_user_message: last.role === "user", - context: null, - }, - }; - }, - }); - }, [sessionId]); - - const { messages, sendMessage, status, error, setMessages } = useChat({ - id: sessionId ?? undefined, - transport: transport ?? undefined, - }); - - const messagesRef = useRef(messages); - - useEffect(() => { - messagesRef.current = messages; - }, [messages]); - - async function createSession(): Promise { - if (sessionId) return sessionId; - if (createSessionPromiseRef.current) return createSessionPromiseRef.current; - - setIsCreatingSession(true); - const promise = (async () => { - const response = await postV2CreateSession({ - body: JSON.stringify({}), - }); - if (response.status !== 200 || !response.data?.id) { - throw new Error("Failed to create chat session"); - } - setSessionId(response.data.id); - queryClient.invalidateQueries({ - queryKey: getGetV2ListSessionsQueryKey(), - }); - return response.data.id; - })(); - - createSessionPromiseRef.current = promise; - - try { - return await promise; - } finally { - createSessionPromiseRef.current = null; - setIsCreatingSession(false); - } - } - - useEffect(() => { - hydrationSeq.current += 1; - const seq = hydrationSeq.current; - const controller = new AbortController(); - - if (!sessionId) { - setMessages([]); - lastHydratedSessionIdRef.current = null; - return; - } - - const currentSessionId = sessionId; - - if (lastHydratedSessionIdRef.current !== currentSessionId) { - setMessages([]); - lastHydratedSessionIdRef.current = currentSessionId; - } - - async function hydrate() { - try { - const response = await getV2GetSession(currentSessionId, { - signal: controller.signal, - }); - if (response.status !== 200) return; - - const uiMessages = convertChatSessionMessagesToUiMessages( - currentSessionId, - response.data.messages ?? [], - ); - if (controller.signal.aborted) return; - if (hydrationSeq.current !== seq) return; - - const localMessagesCount = messagesRef.current.length; - const remoteMessagesCount = uiMessages.length; - - if (remoteMessagesCount === 0) return; - if (localMessagesCount > remoteMessagesCount) return; - - setMessages(uiMessages); - } catch (error) { - if ((error as { name?: string } | null)?.name === "AbortError") return; - console.warn("Failed to hydrate chat session:", error); - } - } - - void hydrate(); - - return () => controller.abort(); - }, [sessionId, setMessages]); - - useEffect(() => { - if (!sessionId) return; - const firstMessage = queuedFirstMessageRef.current; - if (!firstMessage) return; - - queuedFirstMessageRef.current = null; - sendMessage({ text: firstMessage }); - queuedFirstMessageResolverRef.current?.(); - queuedFirstMessageResolverRef.current = null; - }, [sendMessage, sessionId]); - - async function onSend(message: string) { - const trimmed = message.trim(); - if (!trimmed) return; - - if (sessionId) { - sendMessage({ text: trimmed }); - return; - } - - queuedFirstMessageRef.current = trimmed; - const sentPromise = new Promise((resolve) => { - queuedFirstMessageResolverRef.current = resolve; - }); - - await createSession(); - await sentPromise; - } + const { + copied, + sessionId, + messages, + status, + error, + isCreatingSession, + handleCopySessionId, + createSession, + onSend, + } = useCopilotPage(); return ( (null); + const createSessionPromiseRef = useRef | null>(null); + const queuedFirstMessageRef = useRef(null); + const queuedFirstMessageResolverRef = useRef<(() => void) | null>(null); + const queryClient = useQueryClient(); + + function handleCopySessionId() { + if (!sessionId) return; + navigator.clipboard.writeText(sessionId); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + const transport = useMemo(() => { + if (!sessionId) return null; + return new DefaultChatTransport({ + api: `/api/chat/sessions/${sessionId}/stream`, + prepareSendMessagesRequest: ({ messages }) => { + const last = messages[messages.length - 1]; + return { + body: { + message: last.parts + ?.map((p) => (p.type === "text" ? p.text : "")) + .join(""), + is_user_message: last.role === "user", + context: null, + }, + }; + }, + }); + }, [sessionId]); + + const { messages, sendMessage, status, error, setMessages } = useChat({ + id: sessionId ?? undefined, + transport: transport ?? undefined, + }); + + const messagesRef = useRef(messages); + + useEffect(() => { + messagesRef.current = messages; + }, [messages]); + + async function createSession(): Promise { + if (sessionId) return sessionId; + if (createSessionPromiseRef.current) return createSessionPromiseRef.current; + + setIsCreatingSession(true); + const promise = (async () => { + const response = await postV2CreateSession({ + body: JSON.stringify({}), + }); + if (response.status !== 200 || !response.data?.id) { + throw new Error("Failed to create chat session"); + } + setSessionId(response.data.id); + queryClient.invalidateQueries({ + queryKey: getGetV2ListSessionsQueryKey(), + }); + return response.data.id; + })(); + + createSessionPromiseRef.current = promise; + + try { + return await promise; + } finally { + createSessionPromiseRef.current = null; + setIsCreatingSession(false); + } + } + + useEffect(() => { + hydrationSeq.current += 1; + const seq = hydrationSeq.current; + const controller = new AbortController(); + + if (!sessionId) { + setMessages([]); + lastHydratedSessionIdRef.current = null; + return; + } + + const currentSessionId = sessionId; + + if (lastHydratedSessionIdRef.current !== currentSessionId) { + setMessages([]); + lastHydratedSessionIdRef.current = currentSessionId; + } + + async function hydrate() { + try { + const response = await getV2GetSession(currentSessionId, { + signal: controller.signal, + }); + if (response.status !== 200) return; + + const uiMessages = convertChatSessionMessagesToUiMessages( + currentSessionId, + response.data.messages ?? [], + ); + if (controller.signal.aborted) return; + if (hydrationSeq.current !== seq) return; + + const localMessagesCount = messagesRef.current.length; + const remoteMessagesCount = uiMessages.length; + + if (remoteMessagesCount === 0) return; + if (localMessagesCount > remoteMessagesCount) return; + + setMessages(uiMessages); + } catch (error) { + if ((error as { name?: string } | null)?.name === "AbortError") return; + console.warn("Failed to hydrate chat session:", error); + } + } + + void hydrate(); + + return () => controller.abort(); + }, [sessionId, setMessages]); + + useEffect(() => { + if (!sessionId) return; + const firstMessage = queuedFirstMessageRef.current; + if (!firstMessage) return; + + queuedFirstMessageRef.current = null; + sendMessage({ text: firstMessage }); + queuedFirstMessageResolverRef.current?.(); + queuedFirstMessageResolverRef.current = null; + }, [sendMessage, sessionId]); + + async function onSend(message: string) { + const trimmed = message.trim(); + if (!trimmed) return; + + if (sessionId) { + sendMessage({ text: trimmed }); + return; + } + + queuedFirstMessageRef.current = trimmed; + const sentPromise = new Promise((resolve) => { + queuedFirstMessageResolverRef.current = resolve; + }); + + await createSession(); + await sentPromise; + } + + return { + copied, + sessionId, + messages, + status, + error, + isCreatingSession, + handleCopySessionId, + createSession, + onSend, + }; +}