mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-11 08:12:38 +02:00
show errors in activity tab for knowledge graph
This commit is contained in:
parent
c382e3ee8a
commit
c6083de054
6 changed files with 171 additions and 34 deletions
|
|
@ -156,6 +156,28 @@ const SERVICE_LABELS: Record<string, string> = {
|
|||
granola: "Syncing Granola",
|
||||
graph: "Updating knowledge",
|
||||
voice_memo: "Processing voice memo",
|
||||
email_labeling: "Labeling emails",
|
||||
note_tagging: "Tagging notes",
|
||||
agent_notes: "Updating agent notes",
|
||||
}
|
||||
|
||||
function summarizeServiceError(error: string): string {
|
||||
const firstLine = error.split("\n").find((line) => line.trim().length > 0)
|
||||
return firstLine?.trim() || error.trim()
|
||||
}
|
||||
|
||||
function collectServiceErrors(events: ServiceEventType[]): Map<string, string> {
|
||||
const errors = new Map<string, string>()
|
||||
for (const event of events) {
|
||||
if (event.type === "error") {
|
||||
errors.set(event.service, summarizeServiceError(event.error))
|
||||
continue
|
||||
}
|
||||
if (event.type === "run_complete" && event.outcome !== "error") {
|
||||
errors.delete(event.service)
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
type TasksActions = {
|
||||
|
|
@ -227,6 +249,7 @@ function formatRunTime(ts: string): string {
|
|||
function SyncStatusBar() {
|
||||
const { state } = useSidebar()
|
||||
const [activeServices, setActiveServices] = useState<Map<string, string>>(new Map())
|
||||
const [serviceErrors, setServiceErrors] = useState<Map<string, string>>(new Map())
|
||||
const [popoverOpen, setPopoverOpen] = useState(false)
|
||||
const [logEvents, setLogEvents] = useState<ServiceEventType[]>([])
|
||||
const [logLoading, setLogLoading] = useState(false)
|
||||
|
|
@ -260,11 +283,25 @@ function SyncStatusBar() {
|
|||
next.delete(nextEvent.runId)
|
||||
return next
|
||||
})
|
||||
if (nextEvent.outcome !== 'error') {
|
||||
setServiceErrors((prev) => {
|
||||
if (!prev.has(nextEvent.service)) return prev
|
||||
const next = new Map(prev)
|
||||
next.delete(nextEvent.service)
|
||||
return next
|
||||
})
|
||||
}
|
||||
const existingTimeout = runTimeoutsRef.current.get(nextEvent.runId)
|
||||
if (existingTimeout) {
|
||||
clearTimeout(existingTimeout)
|
||||
runTimeoutsRef.current.delete(nextEvent.runId)
|
||||
}
|
||||
} else if (nextEvent.type === 'error') {
|
||||
setServiceErrors((prev) => {
|
||||
const next = new Map(prev)
|
||||
next.set(nextEvent.service, summarizeServiceError(nextEvent.error))
|
||||
return next
|
||||
})
|
||||
}
|
||||
})
|
||||
return cleanup
|
||||
|
|
@ -298,10 +335,14 @@ function SyncStatusBar() {
|
|||
// skip malformed lines
|
||||
}
|
||||
}
|
||||
setServiceErrors(collectServiceErrors(parsed))
|
||||
// Newest first, limit to 1000
|
||||
setLogEvents(parsed.reverse().slice(0, MAX_SYNC_EVENTS))
|
||||
} catch {
|
||||
if (!cancelled) setLogEvents([])
|
||||
if (!cancelled) {
|
||||
setLogEvents([])
|
||||
setServiceErrors(new Map())
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setLogLoading(false)
|
||||
}
|
||||
|
|
@ -312,12 +353,19 @@ function SyncStatusBar() {
|
|||
|
||||
const isSyncing = activeServices.size > 0
|
||||
const isCollapsed = state === "collapsed"
|
||||
const errorEntries = Array.from(serviceErrors.entries())
|
||||
const primaryErrorService = errorEntries[0]?.[0] ?? null
|
||||
const hasServiceErrors = errorEntries.length > 0
|
||||
|
||||
// Build status label from active services
|
||||
const activeServiceNames = [...new Set(activeServices.values())]
|
||||
const statusLabel = isSyncing
|
||||
? activeServiceNames.map((s) => SERVICE_LABELS[s] || s).join(", ")
|
||||
: "All caught up"
|
||||
: hasServiceErrors
|
||||
? errorEntries.length === 1
|
||||
? `${SERVICE_LABELS[primaryErrorService ?? ""] || primaryErrorService} failed`
|
||||
: "Recent sync issues"
|
||||
: "All caught up"
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -335,11 +383,16 @@ function SyncStatusBar() {
|
|||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-sidebar-accent"
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between rounded-md px-2 py-1 text-xs hover:bg-sidebar-accent",
|
||||
hasServiceErrors && !isSyncing ? "text-red-600 dark:text-red-400" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
<span className="flex items-center gap-2 min-w-0">
|
||||
{isSyncing ? (
|
||||
<LoaderIcon className="h-3 w-3 shrink-0 animate-spin" />
|
||||
) : hasServiceErrors ? (
|
||||
<AlertTriangle className="h-3 w-3 shrink-0" />
|
||||
) : (
|
||||
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/60" />
|
||||
)}
|
||||
|
|
@ -357,7 +410,7 @@ function SyncStatusBar() {
|
|||
<div className="p-3 border-b">
|
||||
<h4 className="font-semibold text-sm">Sync Activity</h4>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{isSyncing ? statusLabel : "All services up to date"}
|
||||
{isSyncing || hasServiceErrors ? statusLabel : "All services up to date"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-80 overflow-y-auto p-2">
|
||||
|
|
@ -389,7 +442,17 @@ function SyncStatusBar() {
|
|||
{SERVICE_LABELS[event.service]?.split(" ").slice(-1)[0] || event.service}
|
||||
</span>
|
||||
</span>
|
||||
<span className="leading-4 text-foreground/80">{event.message}</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="leading-4 text-foreground/80">{event.message}</p>
|
||||
{event.type === 'error' && (
|
||||
<p
|
||||
className="truncate text-[11px] leading-4 text-red-600/90 dark:text-red-400/90"
|
||||
title={event.error}
|
||||
>
|
||||
{summarizeServiceError(event.error)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue