Fix syncing of conversation events in SessionDetail (#226)

* Fix syncing of conversation events in SessionDetail

* ellipsis fixes
This commit is contained in:
Sundeep Malladi
2025-06-18 13:13:46 -05:00
committed by GitHub
parent a14827bf51
commit 4954135fa6
2 changed files with 44 additions and 13 deletions

View File

@@ -6,7 +6,7 @@ import { Fragment, jsx, jsxs } from 'react/jsx-runtime'
import { ConversationEvent, ConversationEventType, SessionInfo } from '@/lib/daemon/types'
import { Card, CardContent } from '../ui/card'
import { useHotkeys } from 'react-hotkeys-hook'
import { useFormattedConversation, useConversation } from '@/hooks/useConversation'
import { useConversation } from '@/hooks/useConversation'
import { Skeleton } from '../ui/skeleton'
import { Suspense, useEffect, useRef, useState } from 'react'
import { Bot, MessageCircleDashed, Wrench } from 'lucide-react'
@@ -231,9 +231,8 @@ function ConversationContent({
setExpandedEventId: (id: number | null) => void
isWideView: boolean
}) {
const { formattedEvents, loading, error } = useFormattedConversation(sessionId)
const { events } = useConversation(sessionId)
console.log('raw events', events)
// const { formattedEvents, loading, error } = useFormattedConversation(sessionId)
const { events, loading, error, isInitialLoad } = useConversation(sessionId, undefined, 1000)
const displayObjects = events.map(eventToDisplayObject)
const nonEmptyDisplayObjects = displayObjects.filter(displayObject => displayObject !== null)
@@ -280,13 +279,13 @@ function ConversationContent({
if (!starryNight) {
createStarryNight([textMd]).then(sn => (starryNight = sn))
}
}, [loading, formattedEvents])
}, [loading, events])
if (error) {
return <div className="text-destructive">Error loading conversation: {error}</div>
}
if (loading) {
if (loading && isInitialLoad) {
return (
<div className="space-y-4">
<Skeleton className="h-4 w-3/4" />
@@ -297,7 +296,7 @@ function ConversationContent({
}
// No events yet.
if (formattedEvents.length === 0) {
if (events.length === 0) {
return (
<div className="flex flex-col items-center justify-center py-8 text-center">
<div className="text-muted-foreground mb-2">
@@ -392,7 +391,11 @@ function SessionDetail({ session, onClose }: SessionDetailProps) {
<section className="flex flex-col gap-4">
<hgroup className="flex flex-col gap-1">
<h2 className="text-lg font-medium text-foreground font-mono">{session.query} </h2>
<small className="text-muted-foreground font-mono text-xs uppercase tracking-wider">
<small
className={`font-mono text-xs uppercase tracking-wider ${
session.status === 'running' ? 'text-green-600 font-bold' : 'text-muted-foreground'
}`}
>
{`${session.status}${session.model ? `/ ${session.model}` : ''}`}
</small>
</hgroup>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react'
import { useState, useEffect, useCallback, useRef } from 'react'
import { daemonClient, ConversationEvent } from '@/lib/daemon'
import { formatError } from '@/utils/errors'
@@ -6,15 +6,26 @@ interface UseConversationReturn {
events: ConversationEvent[]
loading: boolean
error: string | null
isInitialLoad: boolean
refresh: () => Promise<void>
}
export function useConversation(sessionId?: string, claudeSessionId?: string): UseConversationReturn {
export function useConversation(
sessionId?: string,
claudeSessionId?: string,
pollInterval: number = 1000,
): UseConversationReturn {
const [events, setEvents] = useState<ConversationEvent[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [errorCount, setErrorCount] = useState(0)
const [isInitialLoad, setIsInitialLoad] = useState(true)
const fetchConversation = useCallback(async () => {
if (errorCount > 3) {
return
}
if (!sessionId && !claudeSessionId) {
setError('Either sessionId or claudeSessionId must be provided')
setLoading(false)
@@ -27,22 +38,39 @@ export function useConversation(sessionId?: string, claudeSessionId?: string): U
const response = await daemonClient.getConversation(sessionId, claudeSessionId)
setEvents(response.events)
setErrorCount(0)
setIsInitialLoad(false)
} catch (err) {
setError(formatError(err))
setErrorCount(prev => prev + 1)
} finally {
setLoading(false)
}
}, [sessionId, claudeSessionId])
}, [sessionId, claudeSessionId, errorCount])
// Store the latest fetchConversation function in a ref
const fetchConversationRef = useRef(fetchConversation)
fetchConversationRef.current = fetchConversation
useEffect(() => {
fetchConversation()
}, [fetchConversation])
// Initial fetch
fetchConversationRef.current()
const interval = setInterval(() => {
fetchConversationRef.current()
}, pollInterval)
return () => {
clearInterval(interval)
}
}, []) // Empty dependency array - only runs once on mount
return {
events,
loading,
error,
refresh: fetchConversation,
isInitialLoad,
}
}