From 14d1e01149177ac4f15dc0c9c936f7ee8790ace3 Mon Sep 17 00:00:00 2001 From: JeffJiang Date: Wed, 4 Mar 2026 09:50:45 +0800 Subject: [PATCH] Refactor hooks and improve error handling in chat functionality (#962) * refactor: update useThreadChat and useThreadStream hooks for improved state management * fix: improve error handling in agent configuration loading and enhance chat page functionality * fix: enhance error handling in agent configuration loading * Update frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/src/agents/lead_agent/agent.py | 2 +- .../[agent_name]/chats/[thread_id]/page.tsx | 25 ++++++++++---- .../src/app/workspace/agents/new/page.tsx | 1 + .../workspace/artifacts/artifact-trigger.tsx | 2 +- .../workspace/chats/use-thread-chat.ts | 20 +++++++---- frontend/src/core/threads/hooks.ts | 33 ++++++------------- 6 files changed, 46 insertions(+), 37 deletions(-) diff --git a/backend/src/agents/lead_agent/agent.py b/backend/src/agents/lead_agent/agent.py index 5195b0a..fa21138 100644 --- a/backend/src/agents/lead_agent/agent.py +++ b/backend/src/agents/lead_agent/agent.py @@ -265,7 +265,7 @@ def make_lead_agent(config: RunnableConfig): is_bootstrap = config.get("configurable", {}).get("is_bootstrap", False) agent_name = config.get("configurable", {}).get("agent_name") - agent_config = load_agent_config(agent_name) + agent_config = load_agent_config(agent_name) if not is_bootstrap else None # Custom agent model or fallback to global/default model resolution agent_model_name = agent_config.model if agent_config and agent_config.model else _resolve_model_name() diff --git a/frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx index 506e053..6bbb597 100644 --- a/frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/agents/[agent_name]/chats/[thread_id]/page.tsx @@ -1,10 +1,11 @@ "use client"; -import { BotIcon } from "lucide-react"; -import { useParams } from "next/navigation"; +import { BotIcon, PlusSquare } from "lucide-react"; +import { useParams, useRouter } from "next/navigation"; import { useCallback } from "react"; import type { PromptInputMessage } from "@/components/ai-elements/prompt-input"; +import { Button } from "@/components/ui/button"; import { AgentWelcome } from "@/components/workspace/agent-welcome"; import { ArtifactTrigger } from "@/components/workspace/artifacts"; import { ChatBox, useThreadChat } from "@/components/workspace/chats"; @@ -13,6 +14,7 @@ import { MessageList } from "@/components/workspace/messages"; import { ThreadContext } from "@/components/workspace/messages/context"; import { ThreadTitle } from "@/components/workspace/thread-title"; import { TodoList } from "@/components/workspace/todo-list"; +import { Tooltip } from "@/components/workspace/tooltip"; import { useAgent } from "@/core/agents"; import { useI18n } from "@/core/i18n/hooks"; import { useNotification } from "@/core/notification/hooks"; @@ -25,10 +27,10 @@ import { cn } from "@/lib/utils"; export default function AgentChatPage() { const { t } = useI18n(); const [settings, setSettings] = useLocalSettings(); + const router = useRouter(); - const { agent_name, thread_id: threadIdFromPath } = useParams<{ + const { agent_name } = useParams<{ agent_name: string; - thread_id: string; }>(); const { agent } = useAgent(agent_name); @@ -37,7 +39,7 @@ export default function AgentChatPage() { const { showNotification } = useNotification(); const [thread, sendMessage] = useThreadStream({ - threadId: threadIdFromPath !== "new" ? threadIdFromPath : undefined, + threadId: isNewThread ? undefined : threadId, context: { ...settings.context, agent_name: agent_name }, onStart: () => { setIsNewThread(false); @@ -99,7 +101,18 @@ export default function AgentChatPage() {
-
+
+ + +
diff --git a/frontend/src/app/workspace/agents/new/page.tsx b/frontend/src/app/workspace/agents/new/page.tsx index 0273e98..9424a5f 100644 --- a/frontend/src/app/workspace/agents/new/page.tsx +++ b/frontend/src/app/workspace/agents/new/page.tsx @@ -43,6 +43,7 @@ export default function NewAgentPage() { const threadId = useMemo(() => uuid(), []); const [thread, sendMessage] = useThreadStream({ + threadId: step === "chat" ? threadId : undefined, context: { mode: "flash", is_bootstrap: true, diff --git a/frontend/src/components/workspace/artifacts/artifact-trigger.tsx b/frontend/src/components/workspace/artifacts/artifact-trigger.tsx index a6aa64a..df1fe68 100644 --- a/frontend/src/components/workspace/artifacts/artifact-trigger.tsx +++ b/frontend/src/components/workspace/artifacts/artifact-trigger.tsx @@ -10,7 +10,7 @@ export const ArtifactTrigger = () => { const { t } = useI18n(); const { artifacts, setOpen: setArtifactsOpen } = useArtifacts(); - if (artifacts?.length === 0) { + if (!artifacts || artifacts.length === 0) { return null; } return ( diff --git a/frontend/src/components/workspace/chats/use-thread-chat.ts b/frontend/src/components/workspace/chats/use-thread-chat.ts index 1a4692d..b316448 100644 --- a/frontend/src/components/workspace/chats/use-thread-chat.ts +++ b/frontend/src/components/workspace/chats/use-thread-chat.ts @@ -1,21 +1,29 @@ "use client"; -import { useParams, useSearchParams } from "next/navigation"; -import { useMemo, useState } from "react"; +import { useParams, usePathname, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; import { uuid } from "@/core/utils/uuid"; export function useThreadChat() { const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); + const pathname = usePathname(); + const searchParams = useSearchParams(); - const threadId = useMemo( - () => (threadIdFromPath === "new" ? uuid() : threadIdFromPath), - [threadIdFromPath], - ); + const [threadId, setThreadId] = useState(() => { + return threadIdFromPath === "new" ? uuid() : threadIdFromPath; + }); const [isNewThread, setIsNewThread] = useState( () => threadIdFromPath === "new", ); + + useEffect(() => { + if (pathname.endsWith("/new")) { + setIsNewThread(true); + setThreadId(uuid()); + } + }, [pathname]); const isMock = searchParams.get("mock") === "true"; return { threadId, isNewThread, setIsNewThread, isMock }; } diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts index ec70940..b327aa0 100644 --- a/frontend/src/core/threads/hooks.ts +++ b/frontend/src/core/threads/hooks.ts @@ -1,8 +1,8 @@ import type { AIMessage } from "@langchain/langgraph-sdk"; import type { ThreadsClient } from "@langchain/langgraph-sdk/client"; -import { useStream, type UseStream } from "@langchain/langgraph-sdk/react"; +import { useStream } from "@langchain/langgraph-sdk/react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import type { PromptInputMessage } from "@/components/ai-elements/prompt-input"; @@ -37,6 +37,13 @@ export function useThreadStream({ onToolEnd, }: ThreadStreamOptions) { const [_threadId, setThreadId] = useState(threadId ?? null); + + useEffect(() => { + if (_threadId && _threadId !== threadId) { + setThreadId(threadId ?? null); + } + }, [threadId, _threadId]); + const queryClient = useQueryClient(); const updateSubtask = useUpdateSubtask(); const thread = useStream({ @@ -74,27 +81,7 @@ export function useThreadStream({ }, onFinish(state) { onFinish?.(state.values); - // void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); - queryClient.setQueriesData( - { - queryKey: ["threads", "search"], - exact: false, - }, - (oldData: Array) => { - return oldData.map((t) => { - if (t.thread_id === threadId) { - return { - ...t, - values: { - ...t.values, - title: state.values.title, - }, - }; - } - return t; - }); - }, - ); + void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); }, });