Keep the knowledge graph when one schedule fails to load
One failing per-schedule request no longer blanks the whole graph, and the graph shows the engine's own error instead of always saying the engine cannot be reached. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { correctIndex, questionsOf, toMarkdown, withPlanItem, type Material } from './results'
|
import { correctIndex, keepSettled, questionsOf, toMarkdown, withPlanItem, type Material } from './results'
|
||||||
import { sentencesOf, scriptLang } from '../components/AudioPlayer'
|
import { sentencesOf, scriptLang } from '../components/AudioPlayer'
|
||||||
|
|
||||||
describe('correctIndex', () => {
|
describe('correctIndex', () => {
|
||||||
@@ -65,3 +65,20 @@ describe('withPlanItem', () => {
|
|||||||
expect(withPlanItem('preparing', undefined)).toBe('preparing')
|
expect(withPlanItem('preparing', undefined)).toBe('preparing')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('keepSettled', () => {
|
||||||
|
const ok = (v: number[]): PromiseSettledResult<number[]> => ({ status: 'fulfilled', value: v })
|
||||||
|
const bad = (message: string): PromiseSettledResult<number[]> => ({ status: 'rejected', reason: new Error(message) })
|
||||||
|
|
||||||
|
it('leaves out a schedule that failed', () => {
|
||||||
|
expect(keepSettled([ok([1, 2]), bad('404'), ok([3])])).toEqual([1, 2, 3])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails when every schedule failed', () => {
|
||||||
|
expect(() => keepSettled([bad('500'), bad('502')])).toThrow('500')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is empty without schedules', () => {
|
||||||
|
expect(keepSettled([])).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -172,14 +172,24 @@ async function sourcesOf({ agent, rule }: Schedule): Promise<FoundSource[]> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One schedule the engine cannot answer for (e.g. an archived agent) leaves its results out
|
||||||
|
// instead of failing them all; only when every schedule fails is that an error.
|
||||||
|
export function keepSettled<T>(results: PromiseSettledResult<T[]>[]): T[] {
|
||||||
|
const kept = results.flatMap(r => (r.status === 'fulfilled' ? r.value : []))
|
||||||
|
const failed = results.filter(r => r.status === 'rejected')
|
||||||
|
if (failed.length) console.warn(`${failed.length} of ${results.length} schedules could not be loaded`, failed.map(r => r.reason))
|
||||||
|
if (results.length && failed.length === results.length) throw failed[0].reason
|
||||||
|
return kept
|
||||||
|
}
|
||||||
|
|
||||||
export async function listResults(): Promise<{ materials: Material[]; sources: FoundSource[] }> {
|
export async function listResults(): Promise<{ materials: Material[]; sources: FoundSource[] }> {
|
||||||
const schedules = await expressAgents({ archived: true })
|
const schedules = await expressAgents({ archived: true })
|
||||||
const [materials, sources] = await Promise.all([
|
const [materials, sources] = await Promise.all([
|
||||||
Promise.all(schedules.filter(s => s.rule.type === 'content').map(materialsOf)),
|
Promise.allSettled(schedules.filter(s => s.rule.type === 'content').map(materialsOf)),
|
||||||
Promise.all(schedules.filter(s => s.rule.type === 'research').map(sourcesOf)),
|
Promise.allSettled(schedules.filter(s => s.rule.type === 'research').map(sourcesOf)),
|
||||||
])
|
])
|
||||||
const newest = <T extends { createdAt: string }>(a: T, b: T) => b.createdAt.localeCompare(a.createdAt)
|
const newest = <T extends { createdAt: string }>(a: T, b: T) => b.createdAt.localeCompare(a.createdAt)
|
||||||
return { materials: materials.flat().sort(newest), sources: sources.flat().sort(newest) }
|
return { materials: keepSettled(materials).sort(newest), sources: keepSettled(sources).sort(newest) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const draftContent = (m: Material, id: number) =>
|
const draftContent = (m: Material, id: number) =>
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ export function ProductionPage() {
|
|||||||
</button>
|
</button>
|
||||||
{graph
|
{graph
|
||||||
? <KnowledgeGraph graph={graph} />
|
? <KnowledgeGraph graph={graph} />
|
||||||
: <div className="graph-loading" role="status">{docs.isError || results.isError ? t('brain.engineDown') : t('prod.loadingGraph')}</div>}
|
// The engine's own message: an unreachable engine says so, any other failure says what it was.
|
||||||
|
: <div className="graph-loading" role="status">{(docs.error ?? results.error)?.message ?? t('prod.loadingGraph')}</div>}
|
||||||
|
|
||||||
<aside className="graph-panel">
|
<aside className="graph-panel">
|
||||||
<div className="graph-stats">
|
<div className="graph-stats">
|
||||||
|
|||||||
Reference in New Issue
Block a user