feat(frontend): add per-sub-question types and stream state management

Add SubQuestionSources interface for grouped per-sub-question sources. Convert QueryStreamEvent from flat interface to discriminated union with 7 variants including generating_subquestion and completed with sub_question_sources. Add subQuestionSources to QueryStreamState. Update completed event handler to populate subQuestionSources. Make sub_question_sources optional for backward compatibility with old SSE format.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Woody 2026-04-26 23:29:38 +08:00
parent 3f50f81bfe
commit 098368bb42
2 changed files with 24 additions and 8 deletions

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { queryDocument, queryDocumentStream, ingestDocument, listDocuments, listChunks, deleteDocument, deleteChunk, listPromptProfiles, getPromptProfile, activatePromptProfile, updatePrompt, updateAllPrompts, resetPrompts, listQueryHistory, getQueryHistoryDetail, deleteQueryHistory, clearQueryHistory, getHistoryStats } from './api' import { queryDocument, queryDocumentStream, ingestDocument, listDocuments, listChunks, deleteDocument, deleteChunk, listPromptProfiles, getPromptProfile, activatePromptProfile, updatePrompt, updateAllPrompts, resetPrompts, listQueryHistory, getQueryHistoryDetail, deleteQueryHistory, clearQueryHistory, getHistoryStats } from './api'
import type { QueryRequest, QueryResponse, QueryStreamEvent, SourceMetadata, IngestResponse, DocumentListResponse, ChunkInfo, DeleteResponse, PromptProfileListResponse, PromptSetResponse, PromptUpdateRequest, PromptBatchUpdateRequest, PromptActivateResponse, PromptStatusResponse, QueryHistoryList, QueryHistoryDetail, HistoryStats, HistoryDeleteResponse } from '../types' import type { QueryRequest, QueryResponse, QueryStreamEvent, SourceMetadata, SubQuestionSources, IngestResponse, DocumentListResponse, ChunkInfo, DeleteResponse, PromptProfileListResponse, PromptSetResponse, PromptUpdateRequest, PromptBatchUpdateRequest, PromptActivateResponse, PromptStatusResponse, QueryHistoryList, QueryHistoryDetail, HistoryStats, HistoryDeleteResponse } from '../types'
import { useState, useCallback, useRef } from 'react' import { useState, useCallback, useRef } from 'react'
export const queryClient = new QueryClient() export const queryClient = new QueryClient()
@ -16,6 +16,7 @@ export interface QueryStreamState {
extractedQuestions: string[] | null extractedQuestions: string[] | null
answer: string | null answer: string | null
sources: SourceMetadata[] | null sources: SourceMetadata[] | null
subQuestionSources: SubQuestionSources[] | null
phase: 'idle' | 'decomposing' | 'retrieving' | 'filtering' | 'generating' | 'completed' | 'error' phase: 'idle' | 'decomposing' | 'retrieving' | 'filtering' | 'generating' | 'completed' | 'error'
error: Error | null error: Error | null
} }
@ -25,6 +26,7 @@ export const useQueryDocumentStream = () => {
extractedQuestions: null, extractedQuestions: null,
answer: null, answer: null,
sources: null, sources: null,
subQuestionSources: null,
phase: 'idle', phase: 'idle',
error: null, error: null,
}) })
@ -35,6 +37,7 @@ export const useQueryDocumentStream = () => {
extractedQuestions: null, extractedQuestions: null,
answer: null, answer: null,
sources: null, sources: null,
subQuestionSources: null,
phase: 'decomposing', phase: 'decomposing',
error: null, error: null,
}) })
@ -60,11 +63,15 @@ export const useQueryDocumentStream = () => {
case 'generating': case 'generating':
setState(prev => ({ ...prev, phase: 'generating' })) setState(prev => ({ ...prev, phase: 'generating' }))
break break
case 'generating_subquestion':
setState(prev => ({ ...prev, phase: 'generating' }))
break
case 'completed': case 'completed':
setState(prev => ({ setState(prev => ({
...prev, ...prev,
answer: event.answer ?? null, answer: event.answer ?? null,
sources: event.sources ?? null, sources: event.sources ?? null,
subQuestionSources: event.sub_question_sources ?? null,
phase: 'completed', phase: 'completed',
})) }))
break break
@ -96,6 +103,7 @@ export const useQueryDocumentStream = () => {
extractedQuestions: null, extractedQuestions: null,
answer: null, answer: null,
sources: null, sources: null,
subQuestionSources: null,
phase: 'idle', phase: 'idle',
error: null, error: null,
}) })

View File

@ -7,6 +7,12 @@ export interface SourceMetadata {
chunk_file_path: string | null chunk_file_path: string | null
} }
export interface SubQuestionSources {
sub_question_index: number
sub_question_text: string
sources: SourceMetadata[]
}
export interface QueryRequest { export interface QueryRequest {
question: string question: string
} }
@ -14,16 +20,18 @@ export interface QueryRequest {
export interface QueryResponse { export interface QueryResponse {
extracted_questions: string[] extracted_questions: string[]
answer: string answer: string
sub_question_sources?: SubQuestionSources[]
sources: SourceMetadata[] sources: SourceMetadata[]
} }
export interface QueryStreamEvent { export type QueryStreamEvent =
phase: 'decomposed' | 'retrieving' | 'filtering' | 'generating' | 'completed' | 'error' | { phase: 'decomposed'; extracted_questions: string[] }
extracted_questions?: string[] | { phase: 'retrieving' }
answer?: string | { phase: 'filtering' }
sources?: SourceMetadata[] | { phase: 'generating' }
message?: string | { phase: 'generating_subquestion'; sub_question_index: number; sub_question_text: string }
} | { phase: 'completed'; answer: string; sub_question_sources?: SubQuestionSources[]; sources?: SourceMetadata[] }
| { phase: 'error'; message: string }
export interface IngestResponse { export interface IngestResponse {
document_id: string document_id: string