diff --git a/apps/web/src/app/agents/[id]/chat/page.tsx b/apps/web/src/app/agents/[id]/chat/page.tsx index bff9a72..11a2512 100644 --- a/apps/web/src/app/agents/[id]/chat/page.tsx +++ b/apps/web/src/app/agents/[id]/chat/page.tsx @@ -213,8 +213,12 @@ export default function PublicAgentChatPage(): React.ReactElement { return searchParams.get('c') || generateConversationId(); }, [searchParams]); - // Track if we've updated the URL with conversation ID - const hasUpdatedUrl = useRef(false); + // Immediately update URL with conversation ID if not present + useEffect(() => { + if (!searchParams.get('c')) { + router.replace(`/agents/${agentId}/chat?c=${conversationId}`, { scroll: false }); + } + }, [searchParams, agentId, conversationId, router]); const [agent, setAgent] = useState(null); const [messages, setMessages] = useState([]); @@ -372,12 +376,6 @@ export default function PublicAgentChatPage(): React.ReactElement { throw new Error(errorData.error || 'Failed to send message'); } - // Update URL with conversation ID so refresh preserves chat history - if (!hasUpdatedUrl.current && !searchParams.get('c')) { - hasUpdatedUrl.current = true; - router.replace(`/agents/${agentId}/chat?c=${conversationId}`, { scroll: false }); - } - // Handle SSE stream const reader = response.body?.getReader(); if (!reader) throw new Error('No response body'); diff --git a/apps/web/src/app/dashboard/agents/[id]/chat/page.tsx b/apps/web/src/app/dashboard/agents/[id]/chat/page.tsx index 94075ef..3fc1bd0 100644 --- a/apps/web/src/app/dashboard/agents/[id]/chat/page.tsx +++ b/apps/web/src/app/dashboard/agents/[id]/chat/page.tsx @@ -4,7 +4,7 @@ import type { AIProvider } from '@tpmjs/types/agent'; import { Button } from '@tpmjs/ui/Button/Button'; import { Icon } from '@tpmjs/ui/Icon/Icon'; import Link from 'next/link'; -import { useParams, useRouter } from 'next/navigation'; +import { useParams, useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useRef, useState } from 'react'; import { DashboardLayout } from '~/components/dashboard/DashboardLayout'; @@ -161,14 +161,24 @@ const PROVIDER_DISPLAY_NAMES: Record = { MISTRAL: 'Mistral', }; +function generateConversationId(): string { + return `conv-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; +} + export default function AgentChatPage(): React.ReactElement { const params = useParams(); const router = useRouter(); + const searchParams = useSearchParams(); const agentId = params.id as string; + // Get conversation ID from URL or generate new one + const urlConversationId = searchParams.get('c'); + const [agent, setAgent] = useState(null); const [conversations, setConversations] = useState([]); - const [activeConversationId, setActiveConversationId] = useState(null); + const [activeConversationId, setActiveConversationId] = useState( + urlConversationId + ); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(true); @@ -278,9 +288,22 @@ export default function AgentChatPage(): React.ReactElement { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }); - const generateConversationId = () => { - return `conv-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; - }; + // Sync URL with active conversation ID + useEffect(() => { + if (activeConversationId && activeConversationId !== urlConversationId) { + router.replace(`/dashboard/agents/${agentId}/chat?c=${activeConversationId}`, { + scroll: false, + }); + } + }, [activeConversationId, urlConversationId, agentId, router]); + + // If no conversation ID in URL, generate one immediately + useEffect(() => { + if (!urlConversationId && !activeConversationId) { + const newId = generateConversationId(); + setActiveConversationId(newId); + } + }, [urlConversationId, activeConversationId]); const handleSend = async () => { if (!input.trim() || !agent || isSending) return; @@ -292,7 +315,7 @@ export default function AgentChatPage(): React.ReactElement { setError(null); setToolCalls([]); - // Create new conversation if needed + // Use active conversation ID (always set by this point) const conversationId = activeConversationId || generateConversationId(); if (!activeConversationId) { setActiveConversationId(conversationId); @@ -406,7 +429,8 @@ export default function AgentChatPage(): React.ReactElement { }; const startNewConversation = () => { - setActiveConversationId(null); + const newId = generateConversationId(); + setActiveConversationId(newId); setMessages([]); inputRef.current?.focus(); };