feat(history): Implement conversation history management with artifact restoration and Markdown rendering.
This commit is contained in:
@@ -6,7 +6,8 @@ import { RegisterForm } from "./components/auth/RegisterForm"
|
||||
import { AuthCallback } from "./components/auth/AuthCallback"
|
||||
import { ChatInterface } from "./components/chat/ChatInterface"
|
||||
import { AuthService, type UserResponse } from "./services/auth"
|
||||
import { ChatService } from "./services/chat"
|
||||
import { ChatService, type MessageResponse } from "./services/chat"
|
||||
import { type Conversation } from "./components/layout/HistorySidebar"
|
||||
import { registerUnauthorizedCallback } from "./services/api"
|
||||
|
||||
function App() {
|
||||
@@ -15,12 +16,17 @@ function App() {
|
||||
const [authMode, setAuthMode] = useState<"login" | "register">("login")
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [selectedThreadId, setSelectedThreadId] = useState<string | null>(null)
|
||||
const [conversations, setConversations] = useState<Conversation[]>([])
|
||||
const [threadMessages, setThreadMessages] = useState<Record<string, MessageResponse[]>>({})
|
||||
|
||||
useEffect(() => {
|
||||
// Register callback to handle session expiration from anywhere in the app
|
||||
registerUnauthorizedCallback(() => {
|
||||
setIsAuthenticated(false)
|
||||
setUser(null)
|
||||
setConversations([])
|
||||
setSelectedThreadId(null)
|
||||
setThreadMessages({})
|
||||
})
|
||||
|
||||
const initAuth = async () => {
|
||||
@@ -28,8 +34,9 @@ function App() {
|
||||
const userData = await AuthService.getMe()
|
||||
setUser(userData)
|
||||
setIsAuthenticated(true)
|
||||
// Load history after successful auth
|
||||
loadHistory()
|
||||
} catch (err: unknown) {
|
||||
// Not logged in or session expired - this is expected if no cookie
|
||||
console.log("No active session found", err)
|
||||
setIsAuthenticated(false)
|
||||
} finally {
|
||||
@@ -40,11 +47,21 @@ function App() {
|
||||
initAuth()
|
||||
}, [])
|
||||
|
||||
const loadHistory = async () => {
|
||||
try {
|
||||
const history = await ChatService.listConversations()
|
||||
setConversations(history)
|
||||
} catch (err) {
|
||||
console.error("Failed to load conversation history:", err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAuthSuccess = async () => {
|
||||
try {
|
||||
const userData = await AuthService.getMe()
|
||||
setUser(userData)
|
||||
setIsAuthenticated(true)
|
||||
loadHistory()
|
||||
} catch (err: unknown) {
|
||||
console.error("Failed to fetch user profile after login:", err)
|
||||
}
|
||||
@@ -59,19 +76,65 @@ function App() {
|
||||
setIsAuthenticated(false)
|
||||
setUser(null)
|
||||
setSelectedThreadId(null)
|
||||
setConversations([])
|
||||
setThreadMessages({})
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateTempChat = async () => {
|
||||
const handleSelectConversation = async (id: string) => {
|
||||
setSelectedThreadId(id)
|
||||
// Always fetch messages to avoid stale cache issues when switching back
|
||||
// or if the session was updated from elsewhere
|
||||
try {
|
||||
const conv = await ChatService.createConversation("Temporary Chat")
|
||||
setSelectedThreadId(conv.id)
|
||||
const msgs = await ChatService.getMessages(id)
|
||||
setThreadMessages(prev => ({ ...prev, [id]: msgs }))
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch messages:", err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateConversation = async () => {
|
||||
try {
|
||||
const newConv = await ChatService.createConversation()
|
||||
setConversations(prev => [newConv, ...prev])
|
||||
setSelectedThreadId(newConv.id)
|
||||
setThreadMessages(prev => ({ ...prev, [newConv.id]: [] }))
|
||||
} catch (err) {
|
||||
console.error("Failed to create conversation:", err)
|
||||
alert("Failed to start chat session. Please try again.")
|
||||
}
|
||||
}
|
||||
|
||||
const handleRenameConversation = async (id: string, name: string) => {
|
||||
try {
|
||||
const updated = await ChatService.renameConversation(id, name)
|
||||
setConversations(prev => prev.map(c => c.id === id ? updated : c))
|
||||
} catch (err) {
|
||||
console.error("Failed to rename conversation:", err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteConversation = async (id: string) => {
|
||||
try {
|
||||
await ChatService.deleteConversation(id)
|
||||
setConversations(prev => prev.filter(c => c.id !== id))
|
||||
// Also clear from cache
|
||||
setThreadMessages(prev => {
|
||||
const next = { ...prev }
|
||||
delete next[id]
|
||||
return next
|
||||
})
|
||||
if (selectedThreadId === id) {
|
||||
setSelectedThreadId(null)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to delete conversation:", err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleMessagesFinal = (id: string, messages: MessageResponse[]) => {
|
||||
setThreadMessages(prev => ({ ...prev, [id]: messages }))
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
@@ -101,7 +164,14 @@ function App() {
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<MainLayout>
|
||||
<MainLayout
|
||||
conversations={conversations}
|
||||
selectedId={selectedThreadId}
|
||||
onSelect={handleSelectConversation}
|
||||
onCreate={handleCreateConversation}
|
||||
onRename={handleRenameConversation}
|
||||
onDelete={handleDeleteConversation}
|
||||
>
|
||||
<div className="flex flex-col h-full gap-4">
|
||||
<div className="flex justify-between items-center shrink-0">
|
||||
<div>
|
||||
@@ -119,7 +189,12 @@ function App() {
|
||||
|
||||
<div className="flex-1 min-h-0">
|
||||
{selectedThreadId ? (
|
||||
<ChatInterface threadId={selectedThreadId} />
|
||||
<ChatInterface
|
||||
key={selectedThreadId} // Force remount on thread change
|
||||
threadId={selectedThreadId}
|
||||
initialMessages={threadMessages[selectedThreadId] || []}
|
||||
onMessagesFinal={(msgs) => handleMessagesFinal(selectedThreadId, msgs)}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center space-y-4 bg-muted/30 rounded-xl border border-dashed p-12">
|
||||
<div className="p-4 bg-background rounded-full shadow-sm">
|
||||
@@ -144,10 +219,10 @@ function App() {
|
||||
Create a new conversation in the sidebar to start asking questions.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleCreateTempChat}
|
||||
onClick={handleCreateConversation}
|
||||
className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-md text-sm font-medium hover:bg-primary/90"
|
||||
>
|
||||
Start Temporary Chat
|
||||
Start New Chat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user