mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
fix: address review feedback on session search palette
- useSessionSearch: add `enabled` param so global Ctrl+K is only intercepted when the palette is rendered; case-insensitive key check - ChatLayout: only enable the shortcut in fullscreen mode - AgentUILayout: pass currentSessionId/onSessionChange to ChatArea so session selection is functional - SessionSearch: guard async fetch with an active flag, hoist date objects out of the reduce loop, use a div for the loading state (CommandEmpty only renders post-filter) - tests: patch async _datastore with AsyncMock and build the app via create_app() so the /sessions endpoint tests actually run 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Mervin Praison <[email protected]>
This commit is contained in:
1 parent
92e257306f
commit
ff54833c84
5 files changed
+49
-27
No files matched your search
@@ -30,23 +30,33 @@ export function SessionSearch({ open, onOpenChange, onSessionSelect, currentSess
|
||||
// Fetch sessions when dialog opens
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
|
||||
|
||||
let active = true
|
||||
const fetchSessions = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const res = await fetch('/sessions')
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
setSessions(data.sessions || [])
|
||||
if (active) {
|
||||
setSessions(data.sessions || [])
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch sessions:', err)
|
||||
setSessions([])
|
||||
if (active) {
|
||||
setSessions([])
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
if (active) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchSessions()
|
||||
return () => {
|
||||
active = false
|
||||
}
|
||||
}, [open])
|
||||
|
||||
const handleSelect = useCallback((sessionId: string) => {
|
||||
@@ -73,23 +83,28 @@ export function SessionSearch({ open, onOpenChange, onSessionSelect, currentSess
|
||||
}
|
||||
|
||||
// Group sessions by date
|
||||
const today = new Date()
|
||||
const yesterday = new Date(today)
|
||||
yesterday.setDate(yesterday.getDate() - 1)
|
||||
const lastWeek = new Date(today)
|
||||
lastWeek.setDate(lastWeek.getDate() - 7)
|
||||
|
||||
const todayStr = today.toDateString()
|
||||
const yesterdayStr = yesterday.toDateString()
|
||||
|
||||
const groupedSessions = sessions.reduce((groups, session) => {
|
||||
const date = new Date(session.updated_at)
|
||||
const today = new Date()
|
||||
const yesterday = new Date(today)
|
||||
yesterday.setDate(yesterday.getDate() - 1)
|
||||
const lastWeek = new Date(today)
|
||||
lastWeek.setDate(lastWeek.getDate() - 7)
|
||||
|
||||
const dateStr = date.toDateString()
|
||||
|
||||
let group = 'Older'
|
||||
if (date.toDateString() === today.toDateString()) {
|
||||
if (dateStr === todayStr) {
|
||||
group = 'Today'
|
||||
} else if (date.toDateString() === yesterday.toDateString()) {
|
||||
} else if (dateStr === yesterdayStr) {
|
||||
group = 'Yesterday'
|
||||
} else if (date > lastWeek) {
|
||||
group = 'This Week'
|
||||
}
|
||||
|
||||
|
||||
if (!groups[group]) groups[group] = []
|
||||
groups[group].push(session)
|
||||
return groups
|
||||
@@ -110,7 +125,7 @@ export function SessionSearch({ open, onOpenChange, onSessionSelect, currentSess
|
||||
/>
|
||||
<CommandList>
|
||||
{loading ? (
|
||||
<CommandEmpty>Loading sessions...</CommandEmpty>
|
||||
<div className="py-6 text-center text-sm text-muted-foreground">Loading sessions...</div>
|
||||
) : sessions.length === 0 ? (
|
||||
<CommandEmpty>No sessions found. Start a chat to create one.</CommandEmpty>
|
||||
) : (
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
|
||||
export function useSessionSearch() {
|
||||
export function useSessionSearch(enabled: boolean = true) {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Check for Ctrl+K (Windows/Linux) or Cmd+K (Mac)
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault()
|
||||
setOpen((prev) => !prev)
|
||||
}
|
||||
@@ -14,7 +16,7 @@ export function useSessionSearch() {
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
return () => document.removeEventListener('keydown', handleKeyDown)
|
||||
}, [])
|
||||
}, [enabled])
|
||||
|
||||
const onOpenChange = useCallback((open: boolean) => {
|
||||
setOpen(open)
|
||||
|
||||
@@ -155,7 +155,12 @@ export function AgentUILayout({ config, title }: AgentUILayoutProps) {
|
||||
</header>
|
||||
)}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<ChatArea config={config} className="h-full" />
|
||||
<ChatArea
|
||||
config={config}
|
||||
className="h-full"
|
||||
sessionId={currentSessionId}
|
||||
onSessionChange={handleSessionSelect}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ export function ChatLayout({ config, layout, title }: ChatLayoutProps) {
|
||||
const [sessionListKey, setSessionListKey] = useState(0)
|
||||
|
||||
// Session search palette (Ctrl+K)
|
||||
const { open: sessionSearchOpen, onOpenChange: onSessionSearchChange } = useSessionSearch()
|
||||
const { open: sessionSearchOpen, onOpenChange: onSessionSearchChange } = useSessionSearch(mode === 'fullscreen')
|
||||
|
||||
const handleSessionSelect = useCallback((sessionId: string) => {
|
||||
setCurrentSessionId(sessionId)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Unit tests for session search functionality."""
|
||||
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from starlette.testclient import TestClient
|
||||
@@ -9,8 +9,8 @@ from starlette.testclient import TestClient
|
||||
def test_sessions_endpoint_returns_list(test_client):
|
||||
"""Test that /sessions endpoint returns a list of sessions."""
|
||||
# Mock the session manager to return test sessions
|
||||
with patch("praisonaiui.server._session_manager") as mock_manager:
|
||||
mock_manager.list_sessions.return_value = [
|
||||
with patch("praisonaiui.server._datastore") as mock_datastore:
|
||||
mock_datastore.list_sessions = AsyncMock(return_value=[
|
||||
{
|
||||
"id": "session-1",
|
||||
"title": "Test Session 1",
|
||||
@@ -25,7 +25,7 @@ def test_sessions_endpoint_returns_list(test_client):
|
||||
"updated_at": "2024-01-02T01:00:00Z",
|
||||
"message_count": 10,
|
||||
},
|
||||
]
|
||||
])
|
||||
|
||||
response = test_client.get("/sessions")
|
||||
assert response.status_code == 200
|
||||
@@ -39,8 +39,8 @@ def test_sessions_endpoint_returns_list(test_client):
|
||||
|
||||
def test_sessions_endpoint_empty_list(test_client):
|
||||
"""Test that /sessions endpoint returns empty list when no sessions."""
|
||||
with patch("praisonaiui.server._session_manager") as mock_manager:
|
||||
mock_manager.list_sessions.return_value = []
|
||||
with patch("praisonaiui.server._datastore") as mock_datastore:
|
||||
mock_datastore.list_sessions = AsyncMock(return_value=[])
|
||||
|
||||
response = test_client.get("/sessions")
|
||||
assert response.status_code == 200
|
||||
@@ -93,6 +93,6 @@ def test_session_search_filters_by_id():
|
||||
@pytest.fixture
|
||||
def test_client():
|
||||
"""Create a test client for the app."""
|
||||
from praisonaiui.server import app
|
||||
from praisonaiui.server import create_app
|
||||
|
||||
return TestClient(app)
|
||||
return TestClient(create_app())
|
||||
Reference in new issue
Block a user