- SearchModal queries GET /messages/search and jumps to the matching session; opened from the sidebar button or Ctrl+K - session items in the sidebar gain a hover export button that downloads the markdown export via an authenticated fetch + blob (plain links cannot send the JWT)
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { useParams } from 'react-router-dom'
|
|
import { useChatStore } from '../../store/chatStore'
|
|
import ChatContainer from '../../components/chat/ChatContainer'
|
|
import SearchModal from '../../components/chat/SearchModal'
|
|
import Sidebar from '../../components/layout/Sidebar'
|
|
import { api } from '../../api/client'
|
|
import type { Message } from '../../types/chat'
|
|
|
|
export default function ChatPage() {
|
|
const { sessionId } = useParams()
|
|
const { setSessionId, setMessages } = useChatStore()
|
|
const [showSearch, setShowSearch] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (!sessionId) return
|
|
setSessionId(sessionId)
|
|
|
|
// Load persisted history for the session
|
|
let cancelled = false
|
|
api.sessions.messages(sessionId).then((data) => {
|
|
if (cancelled) return
|
|
const msgs: Message[] = (data.messages || []).map((m) => ({
|
|
id: `db-${m.id}`,
|
|
session_id: m.session_id,
|
|
role: m.role === 'user' ? 'user' : 'assistant',
|
|
content: m.content,
|
|
created_at: m.created_at,
|
|
}))
|
|
setMessages(msgs)
|
|
}).catch(() => { /* history unavailable */ })
|
|
return () => { cancelled = true }
|
|
}, [sessionId, setSessionId, setMessages])
|
|
|
|
// Ctrl+K opens message search
|
|
useEffect(() => {
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
e.preventDefault()
|
|
setShowSearch(true)
|
|
}
|
|
}
|
|
window.addEventListener('keydown', onKey)
|
|
return () => window.removeEventListener('keydown', onKey)
|
|
}, [])
|
|
|
|
return (
|
|
<div className="flex h-full">
|
|
<Sidebar onSearch={() => setShowSearch(true)} />
|
|
<div className="flex-1 min-w-0">
|
|
<ChatContainer />
|
|
</div>
|
|
{showSearch && <SearchModal onClose={() => setShowSearch(false)} />}
|
|
</div>
|
|
)
|
|
}
|
|
|