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
338 lines
9.3 KiB
TypeScript
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,
|
|
}
|
|
}
|