diff --git a/frontend/src/app/workspace/chats/page.tsx b/frontend/src/app/workspace/chats/page.tsx index 53a6613d2..43d661225 100644 --- a/frontend/src/app/workspace/chats/page.tsx +++ b/frontend/src/app/workspace/chats/page.tsx @@ -48,10 +48,7 @@ export default function ChatsPage() {
{filteredThreads?.map((thread) => ( - +
{titleOfThread(thread)}
diff --git a/frontend/src/components/workspace/recent-chat-list.tsx b/frontend/src/components/workspace/recent-chat-list.tsx index a0c00d298..8858f3371 100644 --- a/frontend/src/components/workspace/recent-chat-list.tsx +++ b/frontend/src/components/workspace/recent-chat-list.tsx @@ -62,7 +62,11 @@ export function RecentChatList() { const { t } = useI18n(); const router = useRouter(); const pathname = usePathname(); - const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); + const { thread_id: threadIdFromPath, agent_name: agentNameFromPath } = + useParams<{ + thread_id: string; + agent_name?: string; + }>(); const { data: threads = [] } = useThreads(); const { mutate: deleteThread } = useDeleteThread(); const { mutate: renameThread } = useRenameThread(); @@ -77,18 +81,20 @@ export function RecentChatList() { deleteThread({ threadId }); if (threadId === threadIdFromPath) { const threadIndex = threads.findIndex((t) => t.thread_id === threadId); - let nextThreadId = "new"; + let nextThreadPath = pathOfThread("new", { + agent_name: agentNameFromPath, + }); if (threadIndex > -1) { if (threads[threadIndex + 1]) { - nextThreadId = threads[threadIndex + 1]!.thread_id; + nextThreadPath = pathOfThread(threads[threadIndex + 1]!); } else if (threads[threadIndex - 1]) { - nextThreadId = threads[threadIndex - 1]!.thread_id; + nextThreadPath = pathOfThread(threads[threadIndex - 1]!); } } - void router.push(`/workspace/chats/${nextThreadId}`); + void router.push(nextThreadPath); } }, - [deleteThread, router, threadIdFromPath, threads], + [agentNameFromPath, deleteThread, router, threadIdFromPath, threads], ); const handleRenameClick = useCallback( @@ -110,7 +116,7 @@ export function RecentChatList() { }, [renameThread, renameThreadId, renameValue]); const handleShare = useCallback( - async (threadId: string) => { + async (thread: AgentThread) => { // Always use Vercel URL for sharing so others can access const VERCEL_URL = "https://deer-flow-v2.vercel.app"; const isLocalhost = @@ -118,7 +124,7 @@ export function RecentChatList() { window.location.hostname === "127.0.0.1"; // On localhost: use Vercel URL; On production: use current origin const baseUrl = isLocalhost ? VERCEL_URL : window.location.origin; - const shareUrl = `${baseUrl}/workspace/chats/${threadId}`; + const shareUrl = `${baseUrl}${pathOfThread(thread)}`; try { await navigator.clipboard.writeText(shareUrl); toast.success(t.clipboard.linkCopied); @@ -169,7 +175,7 @@ export function RecentChatList() {
{threads.map((thread) => { - const isActive = pathOfThread(thread.thread_id) === pathname; + const isActive = pathOfThread(thread) === pathname; return ( {titleOfThread(thread)} @@ -211,7 +217,7 @@ export function RecentChatList() { {t.common.rename} handleShare(thread.thread_id)} + onSelect={() => handleShare(thread)} > {t.common.share} diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts index 51ef05ca5..af9b80f54 100644 --- a/frontend/src/core/threads/hooks.ts +++ b/frontend/src/core/threads/hooks.ts @@ -528,7 +528,7 @@ export function useThreads( limit: 50, sortBy: "updated_at", sortOrder: "desc", - select: ["thread_id", "updated_at", "values"], + select: ["thread_id", "updated_at", "values", "context"], }, ) { const apiClient = getAPIClient(); diff --git a/frontend/src/core/threads/types.ts b/frontend/src/core/threads/types.ts index b9c416165..50f1dab66 100644 --- a/frontend/src/core/threads/types.ts +++ b/frontend/src/core/threads/types.ts @@ -9,8 +9,6 @@ export interface AgentThreadState extends Record { todos?: Todo[]; } -export interface AgentThread extends Thread {} - export interface AgentThreadContext extends Record { thread_id: string; model_name: string | undefined; @@ -20,3 +18,7 @@ export interface AgentThreadContext extends Record { reasoning_effort?: "minimal" | "low" | "medium" | "high"; agent_name?: string; } + +export interface AgentThread extends Thread { + context?: AgentThreadContext; +} diff --git a/frontend/src/core/threads/utils.test.ts b/frontend/src/core/threads/utils.test.ts new file mode 100644 index 000000000..c107c4e2b --- /dev/null +++ b/frontend/src/core/threads/utils.test.ts @@ -0,0 +1,33 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +const { pathOfThread } = await import( + new URL("./utils.ts", import.meta.url).href +); + +void test("uses standard chat route when thread has no agent context", () => { + assert.equal(pathOfThread("thread-123"), "/workspace/chats/thread-123"); + assert.equal( + pathOfThread({ + thread_id: "thread-123", + }), + "/workspace/chats/thread-123", + ); +}); + +void test("uses agent chat route when thread context has agent_name", () => { + assert.equal( + pathOfThread({ + thread_id: "thread-123", + context: { agent_name: "researcher" }, + }), + "/workspace/agents/researcher/chats/thread-123", + ); +}); + +void test("uses provided context when pathOfThread is called with a thread id", () => { + assert.equal( + pathOfThread("thread-123", { agent_name: "ops agent" }), + "/workspace/agents/ops%20agent/chats/thread-123", + ); +}); diff --git a/frontend/src/core/threads/utils.ts b/frontend/src/core/threads/utils.ts index 22510fa80..246a17782 100644 --- a/frontend/src/core/threads/utils.ts +++ b/frontend/src/core/threads/utils.ts @@ -1,9 +1,28 @@ import type { Message } from "@langchain/langgraph-sdk"; -import type { AgentThread } from "./types"; +import type { AgentThread, AgentThreadContext } from "./types"; -export function pathOfThread(threadId: string) { - return `/workspace/chats/${threadId}`; +type ThreadRouteTarget = + | string + | Pick + | { + thread_id: string; + context?: Pick | null; + }; + +export function pathOfThread( + thread: ThreadRouteTarget, + context?: Pick | null, +) { + const threadId = typeof thread === "string" ? thread : thread.thread_id; + const agentName = + typeof thread === "string" + ? context?.agent_name + : thread.context?.agent_name; + + return agentName + ? `/workspace/agents/${encodeURIComponent(agentName)}/chats/${threadId}` + : `/workspace/chats/${threadId}`; } export function textOfMessage(message: Message) {