mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 02:43:49 +08:00
fix(knowledge-fs): refresh quality evaluation progress
This commit is contained in:
parent
2f8dd177d3
commit
bc6ff536df
@ -110,6 +110,19 @@ const completedRun = {
|
||||
updated_at: '2026-08-19T12:00:02.000Z',
|
||||
} as const
|
||||
|
||||
const queuedRun = {
|
||||
...completedRun,
|
||||
items: completedRun.items.map((item) => ({
|
||||
...item,
|
||||
result: null,
|
||||
state: 'queued' as const,
|
||||
})),
|
||||
revision: 1,
|
||||
state: 'queued' as const,
|
||||
summary: { completed: 0, failed: 0, hit_rate: 0, passed: 0, total: 1 },
|
||||
updated_at: completedRun.created_at,
|
||||
}
|
||||
|
||||
const completedRunWithEvidence = {
|
||||
...completedRun,
|
||||
items: [
|
||||
@ -145,13 +158,14 @@ const completedRunWithEvidence = {
|
||||
],
|
||||
} as const
|
||||
|
||||
function renderPanel() {
|
||||
const queryClient = new QueryClient({
|
||||
function renderPanel(
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
mutations: { retry: false },
|
||||
queries: { retry: false },
|
||||
},
|
||||
})
|
||||
}),
|
||||
) {
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
)
|
||||
@ -355,6 +369,138 @@ describe('QualityEvaluationPanel', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
it('shows the queued run without waiting for the evaluation list to refresh', async () => {
|
||||
const user = userEvent.setup()
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
mutations: { retry: false },
|
||||
queries: { retry: false, staleTime: Number.POSITIVE_INFINITY },
|
||||
},
|
||||
})
|
||||
let listRequestCount = 0
|
||||
serviceMock.createReplay.mockResolvedValue(queuedRun)
|
||||
serviceMock.getReplay.mockImplementation(() => new Promise(() => {}))
|
||||
serviceMock.listReplays.mockImplementation(() => {
|
||||
listRequestCount += 1
|
||||
return listRequestCount === 1
|
||||
? Promise.resolve({ data: [], next_cursor: null })
|
||||
: new Promise(() => {})
|
||||
})
|
||||
|
||||
renderPanel(queryClient)
|
||||
|
||||
await screen.findByText('dataset.newKnowledge.qualityPage.evaluation.emptyTitle')
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'dataset.newKnowledge.qualityPage.evaluation.run',
|
||||
}),
|
||||
)
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'dataset.newKnowledge.qualityPage.evaluation.start',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(
|
||||
await screen.findByRole('heading', {
|
||||
name: 'dataset.newKnowledge.qualityPage.evaluation.reportTitle',
|
||||
}),
|
||||
).toBeVisible()
|
||||
expect(
|
||||
screen.getAllByText('dataset.newKnowledge.qualityPage.evaluation.state.queued'),
|
||||
).not.toHaveLength(0)
|
||||
})
|
||||
|
||||
it('opens a completed evaluation with the latest progress from the list', async () => {
|
||||
const user = userEvent.setup()
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
mutations: { retry: false },
|
||||
queries: { retry: false, staleTime: Number.POSITIVE_INFINITY },
|
||||
},
|
||||
})
|
||||
let detailRequestCount = 0
|
||||
serviceMock.listReplays
|
||||
.mockResolvedValueOnce({ data: [queuedRun], next_cursor: null })
|
||||
.mockResolvedValueOnce({ data: [completedRun], next_cursor: null })
|
||||
serviceMock.getReplay.mockImplementation(() => {
|
||||
detailRequestCount += 1
|
||||
return detailRequestCount === 1 ? Promise.resolve(queuedRun) : new Promise(() => {})
|
||||
})
|
||||
|
||||
renderPanel(queryClient)
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'dataset.newKnowledge.qualityPage.evaluation.viewReport',
|
||||
}),
|
||||
)
|
||||
await screen.findAllByText('dataset.newKnowledge.qualityPage.evaluation.state.queued')
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'dataset.newKnowledge.qualityPage.evaluationTab',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(
|
||||
await screen.findAllByText('dataset.newKnowledge.qualityPage.evaluation.state.passed'),
|
||||
).not.toHaveLength(0)
|
||||
expect(serviceMock.listReplays).toHaveBeenCalledTimes(2)
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'dataset.newKnowledge.qualityPage.evaluation.viewReport',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(
|
||||
await screen.findAllByText('dataset.newKnowledge.qualityPage.evaluation.state.passed'),
|
||||
).not.toHaveLength(0)
|
||||
expect(screen.getByText('1/1')).toBeVisible()
|
||||
})
|
||||
|
||||
it('keeps newer report progress when the evaluation list is stale', async () => {
|
||||
const user = userEvent.setup()
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
mutations: { retry: false },
|
||||
queries: { retry: false, staleTime: Number.POSITIVE_INFINITY },
|
||||
},
|
||||
})
|
||||
let detailRequestCount = 0
|
||||
serviceMock.listReplays.mockResolvedValue({ data: [queuedRun], next_cursor: null })
|
||||
serviceMock.getReplay.mockImplementation(() => {
|
||||
detailRequestCount += 1
|
||||
return detailRequestCount === 1 ? Promise.resolve(completedRun) : new Promise(() => {})
|
||||
})
|
||||
|
||||
renderPanel(queryClient)
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'dataset.newKnowledge.qualityPage.evaluation.viewReport',
|
||||
}),
|
||||
)
|
||||
expect(
|
||||
await screen.findAllByText('dataset.newKnowledge.qualityPage.evaluation.state.passed'),
|
||||
).not.toHaveLength(0)
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'dataset.newKnowledge.qualityPage.evaluationTab',
|
||||
}),
|
||||
)
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'dataset.newKnowledge.qualityPage.evaluation.viewReport',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(
|
||||
screen.getAllByText('dataset.newKnowledge.qualityPage.evaluation.state.passed'),
|
||||
).not.toHaveLength(0)
|
||||
expect(screen.getByText('1/1')).toBeVisible()
|
||||
})
|
||||
|
||||
it('opens evidence hit details and identifies matched and missing passages', async () => {
|
||||
const user = userEvent.setup()
|
||||
serviceMock.listReplays.mockResolvedValue({ data: [completedRun], next_cursor: null })
|
||||
|
||||
@ -31,6 +31,7 @@ type ReplayItem = KnowledgeFsQualityReplayResponse['items'][number]
|
||||
|
||||
const pageSize = 20
|
||||
const activeQuestionCountLimit = 100
|
||||
const replayRefreshInterval = 1000
|
||||
const activeReplayStates = new Set<ReplayState>(['queued', 'running'])
|
||||
|
||||
function evaluationStateClassName(state: ReplayState) {
|
||||
@ -50,6 +51,13 @@ function formatDuration(milliseconds?: number | null) {
|
||||
return `${(milliseconds / 1000).toFixed(milliseconds < 10_000 ? 1 : 0)} s`
|
||||
}
|
||||
|
||||
function keepLatestReplay(
|
||||
current: KnowledgeFsQualityReplayResponse | undefined,
|
||||
candidate: KnowledgeFsQualityReplayResponse,
|
||||
) {
|
||||
return current && current.revision >= candidate.revision ? current : candidate
|
||||
}
|
||||
|
||||
function EvaluationState({ state }: { state: ReplayState }) {
|
||||
const { t } = useTranslation('dataset')
|
||||
return (
|
||||
@ -85,8 +93,11 @@ export function EvaluationReport({
|
||||
})
|
||||
const detailQuery = useQuery({
|
||||
...detailOptions,
|
||||
refetchOnMount: 'always',
|
||||
refetchInterval: (query) =>
|
||||
query.state.data && activeReplayStates.has(query.state.data.state) ? 1500 : false,
|
||||
query.state.data && activeReplayStates.has(query.state.data.state)
|
||||
? replayRefreshInterval
|
||||
: false,
|
||||
})
|
||||
|
||||
if (detailQuery.isLoading)
|
||||
@ -475,11 +486,19 @@ function RunEvaluationDialogContent({
|
||||
headers: { 'Idempotency-Key': crypto.randomUUID() },
|
||||
params: { control_space_id: knowledgeSpaceId },
|
||||
})
|
||||
await queryClient.invalidateQueries({
|
||||
const detailOptions =
|
||||
consoleQuery.knowledgeFs.spaces.byControlSpaceId.quality.replayRuns.byRunId.get.queryOptions(
|
||||
{
|
||||
input: { params: { control_space_id: knowledgeSpaceId, run_id: run.id } },
|
||||
},
|
||||
)
|
||||
queryClient.setQueryData(detailOptions.queryKey, (current) => keepLatestReplay(current, run))
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.knowledgeFs.spaces.byControlSpaceId.quality.replayRuns.get.key({
|
||||
input: { params: { control_space_id: knowledgeSpaceId } },
|
||||
type: 'infinite',
|
||||
}),
|
||||
refetchType: 'none',
|
||||
})
|
||||
onClose()
|
||||
onRunStarted(run.id)
|
||||
@ -560,6 +579,7 @@ export function QualityEvaluationPanel({
|
||||
const { space } = useKnowledgeSpace()
|
||||
const canEdit = useKnowledgeSpacePermission('knowledge_space_edit')
|
||||
const knowledgeSpaceId = space.control_space_id
|
||||
const queryClient = useQueryClient()
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
const listOptions =
|
||||
consoleQuery.knowledgeFs.spaces.byControlSpaceId.quality.replayRuns.get.infiniteOptions({
|
||||
@ -575,15 +595,25 @@ export function QualityEvaluationPanel({
|
||||
})
|
||||
const listQuery = useInfiniteQuery({
|
||||
...listOptions,
|
||||
refetchOnMount: 'always',
|
||||
refetchInterval: (query) =>
|
||||
query.state.data?.pages.some((page) =>
|
||||
page.data.some((run) => activeReplayStates.has(run.state)),
|
||||
)
|
||||
? 1500
|
||||
? replayRefreshInterval
|
||||
: false,
|
||||
})
|
||||
const runs = listQuery.data?.pages.flatMap((page) => page.data) ?? []
|
||||
|
||||
const openReport = (run: KnowledgeFsQualityReplayResponse) => {
|
||||
const detailOptions =
|
||||
consoleQuery.knowledgeFs.spaces.byControlSpaceId.quality.replayRuns.byRunId.get.queryOptions({
|
||||
input: { params: { control_space_id: knowledgeSpaceId, run_id: run.id } },
|
||||
})
|
||||
queryClient.setQueryData(detailOptions.queryKey, (current) => keepLatestReplay(current, run))
|
||||
onOpenReport(run.id)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="min-w-0">
|
||||
@ -657,7 +687,7 @@ export function QualityEvaluationPanel({
|
||||
variant="secondary"
|
||||
size="small"
|
||||
className="ml-auto"
|
||||
onClick={() => onOpenReport(run.id)}
|
||||
onClick={() => openReport(run)}
|
||||
>
|
||||
{t(($) => $['newKnowledge.qualityPage.evaluation.viewReport'])}
|
||||
</Button>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user