Files
Cyrene_v2/frontend/web/src/pages/chat/ChatPage.tsx
T
Aska 26d65dd6bb
Cyrene CI / backend (push) Has been cancelled
Cyrene CI / frontend (push) Has been cancelled
feat(frontend): message search and session export entry points
- 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)
2026-08-21 12:59:30 +08:00

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>
)
}