Files
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

338 lines
9.3 KiB
TypeScript

import { useCallback, useMemo } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types'
import type { ChunkData, DocumentData, KnowledgeBaseData } from '@/lib/knowledge/types'
import {
type DocumentTagFilter,
type KnowledgeChunksResponse,
type KnowledgeDocumentsResponse,
knowledgeKeys,
serializeChunkParams,
serializeDocumentParams,
useDocumentQuery,
useKnowledgeBaseQuery,
useKnowledgeBasesQuery,
useKnowledgeChunksQuery,
useKnowledgeDocumentsQuery,
} from '@/hooks/queries/kb/knowledge'
const DEFAULT_PAGE_SIZE = 50
/**
* Hook to fetch and manage a single knowledge base
* Uses React Query as single source of truth
*/
export function useKnowledgeBase(id: string) {
const queryClient = useQueryClient()
const query = useKnowledgeBaseQuery(id)
const refresh = useCallback(async () => {
await queryClient.invalidateQueries({
queryKey: knowledgeKeys.detail(id),
})
}, [queryClient, id])
return {
knowledgeBase: query.data ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching,
error: query.error instanceof Error ? query.error.message : null,
refresh,
}
}
/**
* Hook to fetch and manage a single document
* Uses React Query as single source of truth
*/
export function useDocument(knowledgeBaseId: string, documentId: string) {
const query = useDocumentQuery(knowledgeBaseId, documentId)
return {
document: query.data ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching,
error: query.error instanceof Error ? query.error.message : null,
}
}
/**
* Hook to fetch and manage documents for a knowledge base
* Uses React Query as single source of truth
*/
export function useKnowledgeBaseDocuments(
knowledgeBaseId: string,
options?: {
search?: string
limit?: number
offset?: number
sortBy?: DocumentSortField
sortOrder?: SortOrder
enabled?: boolean
refetchInterval?:
| number
| false
| ((data: KnowledgeDocumentsResponse | undefined) => number | false)
enabledFilter?: 'all' | 'enabled' | 'disabled'
tagFilters?: DocumentTagFilter[]
}
) {
const queryClient = useQueryClient()
const requestLimit = options?.limit ?? DEFAULT_PAGE_SIZE
const requestOffset = options?.offset ?? 0
const enabledFilter = options?.enabledFilter ?? 'all'
const tagFilters = options?.tagFilters
const paramsKey = serializeDocumentParams({
knowledgeBaseId,
limit: requestLimit,
offset: requestOffset,
search: options?.search,
sortBy: options?.sortBy,
sortOrder: options?.sortOrder,
enabledFilter,
tagFilters,
})
const refetchIntervalFn = useMemo(() => {
if (typeof options?.refetchInterval === 'function') {
const userFn = options.refetchInterval
return (query: { state: { data?: KnowledgeDocumentsResponse } }) => {
return userFn(query.state.data)
}
}
return options?.refetchInterval
}, [options?.refetchInterval])
const query = useKnowledgeDocumentsQuery(
{
knowledgeBaseId,
limit: requestLimit,
offset: requestOffset,
search: options?.search,
sortBy: options?.sortBy,
sortOrder: options?.sortOrder,
enabledFilter,
tagFilters,
},
{
enabled: (options?.enabled ?? true) && Boolean(knowledgeBaseId),
refetchInterval: refetchIntervalFn,
}
)
const documents = query.data?.documents ?? []
const pagination = query.data?.pagination ?? {
total: 0,
limit: requestLimit,
offset: requestOffset,
hasMore: false,
}
const hasProcessingDocs = useMemo(
() =>
documents.some(
(doc) => doc.processingStatus === 'pending' || doc.processingStatus === 'processing'
),
[documents]
)
const refreshDocuments = useCallback(async () => {
await queryClient.invalidateQueries({
queryKey: knowledgeKeys.documents(knowledgeBaseId, paramsKey),
})
}, [queryClient, knowledgeBaseId, paramsKey])
const updateDocument = useCallback(
(documentId: string, updates: Partial<DocumentData>) => {
queryClient.setQueryData<KnowledgeDocumentsResponse>(
knowledgeKeys.documents(knowledgeBaseId, paramsKey),
(previous) => {
if (!previous) return previous
return {
...previous,
documents: previous.documents.map((doc) =>
doc.id === documentId ? { ...doc, ...updates } : doc
),
}
}
)
},
[knowledgeBaseId, paramsKey, queryClient]
)
return {
documents,
pagination,
isLoading: query.isLoading,
isFetching: query.isFetching,
isPlaceholderData: query.isPlaceholderData,
error: query.error instanceof Error ? query.error.message : null,
hasProcessingDocuments: hasProcessingDocs,
refreshDocuments,
updateDocument,
}
}
/**
* Hook to fetch and manage knowledge bases list
* Uses React Query as single source of truth
*/
export function useKnowledgeBasesList(
workspaceId?: string,
options?: {
enabled?: boolean
}
) {
const queryClient = useQueryClient()
const query = useKnowledgeBasesQuery(workspaceId, { enabled: options?.enabled ?? true })
const removeKnowledgeBase = useCallback(
(knowledgeBaseId: string) => {
queryClient.setQueryData<KnowledgeBaseData[]>(
knowledgeKeys.list(workspaceId),
(previous) => previous?.filter((kb) => kb.id !== knowledgeBaseId) ?? []
)
},
[queryClient, workspaceId]
)
const updateKnowledgeBase = useCallback(
(id: string, updates: Partial<KnowledgeBaseData>) => {
queryClient.setQueryData<KnowledgeBaseData[]>(
knowledgeKeys.list(workspaceId),
(previous) => previous?.map((kb) => (kb.id === id ? { ...kb, ...updates } : kb)) ?? []
)
queryClient.setQueryData<KnowledgeBaseData>(knowledgeKeys.detail(id), (previous) =>
previous ? { ...previous, ...updates } : previous
)
},
[queryClient, workspaceId]
)
const refreshList = useCallback(async () => {
await queryClient.invalidateQueries({ queryKey: knowledgeKeys.list(workspaceId) })
}, [queryClient, workspaceId])
return {
knowledgeBases: query.data ?? [],
isLoading: query.isLoading,
isFetching: query.isFetching,
isPlaceholderData: query.isPlaceholderData,
error: query.error instanceof Error ? query.error.message : null,
refreshList,
removeKnowledgeBase,
updateKnowledgeBase,
}
}
/**
* Hook to manage chunks for a specific document
* Uses React Query as single source of truth
*/
export function useDocumentChunks(
knowledgeBaseId: string,
documentId: string,
page = 1,
search = '',
enabledFilter: 'all' | 'enabled' | 'disabled' = 'all',
sortBy?: 'chunkIndex' | 'tokenCount' | 'enabled',
sortOrder?: 'asc' | 'desc'
) {
const queryClient = useQueryClient()
const currentPage = Math.max(1, page)
const offset = (currentPage - 1) * DEFAULT_PAGE_SIZE
const chunkQuery = useKnowledgeChunksQuery(
{
knowledgeBaseId,
documentId,
limit: DEFAULT_PAGE_SIZE,
offset,
search: search || undefined,
enabledFilter,
sortBy,
sortOrder,
},
{
enabled: Boolean(knowledgeBaseId && documentId),
}
)
const chunks = chunkQuery.data?.chunks ?? []
const pagination = chunkQuery.data?.pagination ?? {
total: 0,
limit: DEFAULT_PAGE_SIZE,
offset: 0,
hasMore: false,
}
const totalPages = Math.max(1, Math.ceil(pagination.total / DEFAULT_PAGE_SIZE))
const hasNextPage = currentPage < totalPages
const hasPrevPage = currentPage > 1
const goToPage = useCallback(
(newPage: number): boolean => {
return newPage >= 1 && newPage <= totalPages
},
[totalPages]
)
const refreshChunks = useCallback(async () => {
const paramsKey = serializeChunkParams({
knowledgeBaseId,
documentId,
limit: DEFAULT_PAGE_SIZE,
offset,
search: search || undefined,
enabledFilter,
sortBy,
sortOrder,
})
await queryClient.invalidateQueries({
queryKey: knowledgeKeys.chunks(knowledgeBaseId, documentId, paramsKey),
})
}, [knowledgeBaseId, documentId, offset, search, enabledFilter, sortBy, sortOrder, queryClient])
const updateChunk = useCallback(
(chunkId: string, updates: Partial<ChunkData>) => {
const paramsKey = serializeChunkParams({
knowledgeBaseId,
documentId,
limit: DEFAULT_PAGE_SIZE,
offset,
search: search || undefined,
enabledFilter,
sortBy,
sortOrder,
})
queryClient.setQueryData<KnowledgeChunksResponse>(
knowledgeKeys.chunks(knowledgeBaseId, documentId, paramsKey),
(previous) => {
if (!previous) return previous
return {
...previous,
chunks: previous.chunks.map((chunk) =>
chunk.id === chunkId ? { ...chunk, ...updates } : chunk
),
}
}
)
},
[knowledgeBaseId, documentId, offset, search, enabledFilter, sortBy, sortOrder, queryClient]
)
return {
chunks,
isLoading: chunkQuery.isLoading,
isFetching: chunkQuery.isFetching,
error: chunkQuery.error instanceof Error ? chunkQuery.error.message : null,
currentPage,
totalPages,
hasNextPage,
hasPrevPage,
goToPage,
refreshChunks,
updateChunk,
}
}