feat: include conversation ID in URL immediately on chat page load

- Public chat page: redirect to include ?c= parameter on mount
- Dashboard chat page: sync URL with active conversation ID
- New conversations get ID in URL immediately, not after first message
This commit is contained in:
Ajax Davis 2026-01-08 00:31:39 +10:00
parent cefd237005
commit dc5a2292fc
2 changed files with 37 additions and 15 deletions

View file

@ -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<Agent | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
@ -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');

View file

@ -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<AIProvider, string> = {
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<Agent | null>(null);
const [conversations, setConversations] = useState<Conversation[]>([]);
const [activeConversationId, setActiveConversationId] = useState<string | null>(null);
const [activeConversationId, setActiveConversationId] = useState<string | null>(
urlConversationId
);
const [messages, setMessages] = useState<Message[]>([]);
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();
};