mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 02:43:49 +08:00
refactor(knowledge-fs): localize document task drawer
This commit is contained in:
parent
611193a139
commit
90f9ff3961
@ -9,6 +9,10 @@ type DocumentDetailLocationRuntime = {
|
||||
setDocumentLocation: (location: DocumentDetailLocation) => Promise<URLSearchParams>
|
||||
}
|
||||
|
||||
type DocumentDetailTitleRuntime = {
|
||||
focusTitle: () => void
|
||||
}
|
||||
|
||||
const unavailableLocationRuntime = async () => {
|
||||
throw new Error('Document detail location runtime is unavailable')
|
||||
}
|
||||
@ -17,6 +21,16 @@ export const documentDetailLocationRuntimeAtom = atom<DocumentDetailLocationRunt
|
||||
setDocumentLocation: unavailableLocationRuntime,
|
||||
})
|
||||
|
||||
export const documentDetailTitleRuntimeAtom = atom<DocumentDetailTitleRuntime>({
|
||||
focusTitle: () => {
|
||||
throw new Error('Document detail title runtime is unavailable')
|
||||
},
|
||||
})
|
||||
|
||||
export const selectDocumentChunkAtom = atom(null, (get, _set, chunkId: string) =>
|
||||
get(documentDetailLocationRuntimeAtom).setDocumentLocation({ chunk: chunkId }),
|
||||
)
|
||||
|
||||
export const focusDocumentDetailTitleAtom = atom(null, (get) => {
|
||||
get(documentDetailTitleRuntimeAtom).focusTitle()
|
||||
})
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { RefObject } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { focusDocumentDetailTitleAtom } from './state/runtime'
|
||||
import {
|
||||
continueDocumentTaskLookupAtom,
|
||||
documentLatestTaskAtom,
|
||||
@ -16,13 +16,7 @@ import {
|
||||
retryDocumentWritePermissionAtom,
|
||||
} from './state/workflow'
|
||||
|
||||
export function DocumentTaskNotices({
|
||||
titleRef,
|
||||
onViewTasks,
|
||||
}: {
|
||||
titleRef: RefObject<HTMLHeadingElement | null>
|
||||
onViewTasks: () => void
|
||||
}) {
|
||||
export function DocumentTaskNotices({ onViewTasks }: { onViewTasks: () => void }) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const isLookingUpTask = useAtomValue(documentTaskIsLookingUpAtom)
|
||||
@ -31,6 +25,7 @@ export function DocumentTaskNotices({
|
||||
const reindexInProgress = useAtomValue(documentReindexInProgressAtom)
|
||||
const tasksError = useAtomValue(documentTasksQueryErrorAtom)
|
||||
const continueLookup = useSetAtom(continueDocumentTaskLookupAtom)
|
||||
const focusTitle = useSetAtom(focusDocumentDetailTitleAtom)
|
||||
const retryTasks = useSetAtom(retryDocumentTasksAtom)
|
||||
|
||||
return (
|
||||
@ -96,7 +91,7 @@ export function DocumentTaskNotices({
|
||||
<Button
|
||||
onClick={() => {
|
||||
continueLookup()
|
||||
requestAnimationFrame(() => titleRef.current?.focus())
|
||||
requestAnimationFrame(focusTitle)
|
||||
}}
|
||||
>
|
||||
{t(($) => $['newKnowledge.continueCheckingTaskStatus'])}
|
||||
@ -109,16 +104,13 @@ export function DocumentTaskNotices({
|
||||
)
|
||||
}
|
||||
|
||||
export function DocumentPermissionRecoveryNotice({
|
||||
titleRef,
|
||||
}: {
|
||||
titleRef: RefObject<HTMLHeadingElement | null>
|
||||
}) {
|
||||
export function DocumentPermissionRecoveryNotice() {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const permissionRecoveryBusy = useAtomValue(documentPermissionRecoveryBusyAtom)
|
||||
const permissionRecoveryNeeded = useAtomValue(documentPermissionRecoveryNeededAtom)
|
||||
const retryWritePermission = useSetAtom(retryDocumentWritePermissionAtom)
|
||||
const focusTitle = useSetAtom(focusDocumentDetailTitleAtom)
|
||||
const permissionRetryRef = useRef<HTMLButtonElement>(null)
|
||||
const permissionRecoveryWasNeededRef = useRef(false)
|
||||
|
||||
@ -142,7 +134,7 @@ export function DocumentPermissionRecoveryNotice({
|
||||
loading={permissionRecoveryBusy}
|
||||
onClick={() =>
|
||||
void retryWritePermission().then((recovered) => {
|
||||
if (recovered) titleRef.current?.focus()
|
||||
if (recovered) focusTitle()
|
||||
else permissionRetryRef.current?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,127 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import type { RefObject } from 'react'
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { logicalDocumentListFromApi } from '../models'
|
||||
import { ProcessingTasksDrawer } from '../tasks/drawer'
|
||||
import { createTaskProgressStore } from '../tasks/progress-store'
|
||||
import { documentDetailKnowledgeSpaceIdAtom } from './state/inputs'
|
||||
import { documentDetailDocumentAtom, refreshDocumentDetailAtom } from './state/queries'
|
||||
import {
|
||||
documentBackgroundTasksAtom,
|
||||
documentCanEditAtom,
|
||||
documentPermissionRecoveryBusyAtom,
|
||||
documentTasksQueryErrorAtom,
|
||||
documentTasksQueryHasNextPageAtom,
|
||||
documentTasksQueryIsFetchingAtom,
|
||||
documentTasksQueryIsFetchingNextPageAtom,
|
||||
documentTasksQueryIsPendingAtom,
|
||||
loadNextDocumentTaskPageAtom,
|
||||
refreshDocumentTasksAtom,
|
||||
retryDocumentTasksAtom,
|
||||
retryDocumentWritePermissionAtom,
|
||||
} from './state/workflow'
|
||||
import { useState } from 'react'
|
||||
import { DocumentDetailTasksDrawer } from '../tasks/drawer'
|
||||
import { DocumentPermissionRecoveryNotice, DocumentTaskNotices } from './status'
|
||||
|
||||
export function DocumentTasksSurface({
|
||||
titleRef,
|
||||
}: {
|
||||
titleRef: RefObject<HTMLHeadingElement | null>
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const currentDocument = useAtomValue(documentDetailDocumentAtom)
|
||||
const knowledgeSpaceId = useAtomValue(documentDetailKnowledgeSpaceIdAtom)
|
||||
const refreshDocument = useSetAtom(refreshDocumentDetailAtom)
|
||||
const tasks = useAtomValue(documentBackgroundTasksAtom)
|
||||
const tasksError = useAtomValue(documentTasksQueryErrorAtom)
|
||||
const hasNextPage = useAtomValue(documentTasksQueryHasNextPageAtom)
|
||||
const isFetching = useAtomValue(documentTasksQueryIsFetchingAtom)
|
||||
const isFetchingNextPage = useAtomValue(documentTasksQueryIsFetchingNextPageAtom)
|
||||
const isPending = useAtomValue(documentTasksQueryIsPendingAtom)
|
||||
const canEdit = useAtomValue(documentCanEditAtom)
|
||||
const permissionRecoveryBusy = useAtomValue(documentPermissionRecoveryBusyAtom)
|
||||
const fetchNextPage = useSetAtom(loadNextDocumentTaskPageAtom)
|
||||
const refreshTasks = useSetAtom(refreshDocumentTasksAtom)
|
||||
const retryTasks = useSetAtom(retryDocumentTasksAtom)
|
||||
const retryWritePermission = useSetAtom(retryDocumentWritePermissionAtom)
|
||||
export function DocumentTasksSurface() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const taskProgressStoreRef = useRef<ReturnType<typeof createTaskProgressStore> | null>(null)
|
||||
if (!taskProgressStoreRef.current) taskProgressStoreRef.current = createTaskProgressStore()
|
||||
const taskProgressStore = taskProgressStoreRef.current
|
||||
const documentsQuery = useInfiniteQuery(
|
||||
consoleQuery.knowledgeFs.spaces.byControlSpaceId.logicalDocuments.get.infiniteOptions({
|
||||
enabled: open,
|
||||
input: (pageParam) => ({
|
||||
params: { control_space_id: knowledgeSpaceId },
|
||||
query: {
|
||||
...(typeof pageParam === 'string' ? { cursor: pageParam } : {}),
|
||||
},
|
||||
}),
|
||||
getNextPageParam: (lastPage) => lastPage.next_cursor,
|
||||
initialPageParam: null as string | null,
|
||||
}),
|
||||
)
|
||||
const documents = useMemo(() => {
|
||||
const queryDocuments =
|
||||
documentsQuery.data?.pages.flatMap((page) => logicalDocumentListFromApi(page).items) ?? []
|
||||
if (queryDocuments.some((document) => document.id === currentDocument.id)) return queryDocuments
|
||||
return [currentDocument, ...queryDocuments]
|
||||
}, [currentDocument, documentsQuery.data])
|
||||
const documentIds = useMemo(() => new Set(documents.map((document) => document.id)), [documents])
|
||||
const hasUnresolvedTaskDocuments = tasks.some(
|
||||
(task) => task.documentId && !documentIds.has(task.documentId),
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocumentTaskNotices titleRef={titleRef} onViewTasks={() => setOpen(true)} />
|
||||
<DocumentPermissionRecoveryNotice titleRef={titleRef} />
|
||||
<ProcessingTasksDrawer
|
||||
actionResultsValid
|
||||
canEdit={canEdit}
|
||||
documentQueryError={Boolean(documentsQuery.error)}
|
||||
documentQueryFetching={documentsQuery.isFetching}
|
||||
documents={documents}
|
||||
documentsPending={Boolean(documentsQuery.isPending || documentsQuery.hasNextPage)}
|
||||
hasNextDocumentPage={Boolean(documentsQuery.hasNextPage)}
|
||||
hasNextTaskPage={hasNextPage}
|
||||
hasUnresolvedTaskDocuments={hasUnresolvedTaskDocuments}
|
||||
isFetchingNextDocumentPage={documentsQuery.isFetchingNextPage}
|
||||
isFetchingNextTaskPage={isFetchingNextPage}
|
||||
knowledgeSpaceId={knowledgeSpaceId}
|
||||
onLoadMoreDocuments={() => void documentsQuery.fetchNextPage()}
|
||||
onLoadMoreTasks={() => void fetchNextPage()}
|
||||
onOpenChange={setOpen}
|
||||
onRefreshDocumentsAndTasks={() => {
|
||||
void Promise.all([refreshDocument(), documentsQuery.refetch(), refreshTasks()])
|
||||
}}
|
||||
onRetryDocumentQuery={() => {
|
||||
if (documentsQuery.isFetchNextPageError) void documentsQuery.fetchNextPage()
|
||||
else void documentsQuery.refetch()
|
||||
}}
|
||||
onRetryPermissionQuery={() => void retryWritePermission()}
|
||||
onRetryTaskQuery={() => {
|
||||
void retryTasks()
|
||||
}}
|
||||
onTaskUpdated={() => void refreshTasks()}
|
||||
onWritePermissionDenied={() => void retryWritePermission()}
|
||||
open={open}
|
||||
permissionQueryError={false}
|
||||
permissionQueryFetching={permissionRecoveryBusy}
|
||||
permissionQueryPending={false}
|
||||
readOnlyReason={
|
||||
canEdit ? undefined : t(($) => $['newKnowledge.documentPermissionRestricted'])
|
||||
}
|
||||
taskProgressStore={taskProgressStore}
|
||||
taskQueryError={Boolean(tasksError)}
|
||||
taskQueryFetching={isFetching}
|
||||
taskQueryPending={isPending}
|
||||
tasks={tasks}
|
||||
/>
|
||||
<DocumentTaskNotices onViewTasks={() => setOpen(true)} />
|
||||
<DocumentPermissionRecoveryNotice />
|
||||
<DocumentDetailTasksDrawer open={open} onOpenChange={setOpen} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useRef } from 'react'
|
||||
import { useHydrateAtoms } from 'jotai/utils'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { KnowledgeModelReadinessBanner } from '../../components/knowledge-model-readiness-banner'
|
||||
import { newKnowledgeDocumentsPath } from '../../routes'
|
||||
@ -12,6 +13,7 @@ import { DocumentReindexAction } from './reindex-action'
|
||||
import { DocumentRevisionBrowser } from './revision-browser'
|
||||
import { documentDetailKnowledgeSpaceIdAtom } from './state/inputs'
|
||||
import { documentDetailDocumentAtom } from './state/queries'
|
||||
import { documentDetailTitleRuntimeAtom } from './state/runtime'
|
||||
import { DOCUMENT_REINDEX_RESTRICTION_ID, documentMissingAtom } from './state/workflow'
|
||||
import { DocumentTasksSurface } from './tasks-surface'
|
||||
import { DocumentWorkflowController } from './workflow-controller'
|
||||
@ -24,6 +26,10 @@ export function DocumentDetailWorkspace() {
|
||||
const documentMissing = useAtomValue(documentMissingAtom)
|
||||
const hasEditPermission = space.permission_keys.includes('knowledge_space_document_write')
|
||||
const titleRef = useRef<HTMLHeadingElement>(null)
|
||||
const titleRuntime = useMemo(() => ({ focusTitle: () => titleRef.current?.focus() }), [])
|
||||
useHydrateAtoms([[documentDetailTitleRuntimeAtom, titleRuntime]], {
|
||||
dangerouslyForceHydrate: true,
|
||||
})
|
||||
|
||||
if (documentMissing)
|
||||
return (
|
||||
@ -55,7 +61,7 @@ export function DocumentDetailWorkspace() {
|
||||
{t(($) => $['newKnowledge.documentPermissionRestricted'])}
|
||||
</p>
|
||||
)}
|
||||
<DocumentTasksSurface titleRef={titleRef} />
|
||||
<DocumentTasksSurface />
|
||||
<DocumentRevisionBrowser />
|
||||
</section>
|
||||
)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { BackgroundTask, DocumentProcessingTask, LogicalDocument } from '../models'
|
||||
import type { TaskProgressStore } from './progress-store'
|
||||
import type { BackgroundTask } from '../models'
|
||||
import { Button, buttonVariants } from '@langgenius/dify-ui/button'
|
||||
import {
|
||||
Drawer,
|
||||
@ -14,19 +13,35 @@ import {
|
||||
DrawerTitle,
|
||||
DrawerViewport,
|
||||
} from '@langgenius/dify-ui/drawer'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useInfiniteQuery, useMutation } from '@tanstack/react-query'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import Link from '@/next/link'
|
||||
import { consoleClient } from '@/service/client'
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import {
|
||||
knowledgeFsTaskFailureMessageKey,
|
||||
knowledgeFsTaskRecoveryPath,
|
||||
} from '../../knowledge-fs-task-error'
|
||||
import { documentDetailKnowledgeSpaceIdAtom } from '../detail/state/inputs'
|
||||
import { documentDetailDocumentAtom, refreshDocumentDetailAtom } from '../detail/state/queries'
|
||||
import {
|
||||
documentBackgroundTasksAtom,
|
||||
documentCanEditAtom,
|
||||
documentTasksQueryErrorAtom,
|
||||
documentTasksQueryHasNextPageAtom,
|
||||
documentTasksQueryIsFetchingAtom,
|
||||
documentTasksQueryIsFetchingNextPageAtom,
|
||||
documentTasksQueryIsPendingAtom,
|
||||
loadNextDocumentTaskPageAtom,
|
||||
refreshDocumentTasksAtom,
|
||||
retryDocumentTasksAtom,
|
||||
retryDocumentWritePermissionAtom,
|
||||
} from '../detail/state/workflow'
|
||||
import { taskCanCancel, taskCanRetry, taskIsActive, taskVersionIsAfter } from '../model'
|
||||
import { backgroundTaskFromApi } from '../models'
|
||||
import { backgroundTaskFromApi, logicalDocumentListFromApi } from '../models'
|
||||
import {
|
||||
selectTaskDrawerTasks,
|
||||
TASK_DRAWER_LIMIT,
|
||||
@ -34,6 +49,7 @@ import {
|
||||
taskProgress,
|
||||
taskTime,
|
||||
} from './drawer-model'
|
||||
import { createTaskProgressStore } from './progress-store'
|
||||
|
||||
type TaskAction = 'cancel' | 'retry'
|
||||
|
||||
@ -50,76 +66,70 @@ function responseStatus(error: unknown): number | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
export function ProcessingTasksDrawer({
|
||||
canEdit,
|
||||
actionResultsValid,
|
||||
documentQueryError,
|
||||
documentQueryFetching,
|
||||
documents,
|
||||
documentsPending,
|
||||
hasNextDocumentPage,
|
||||
hasNextTaskPage,
|
||||
hasUnresolvedTaskDocuments,
|
||||
isFetchingNextDocumentPage,
|
||||
isFetchingNextTaskPage,
|
||||
knowledgeSpaceId,
|
||||
onLoadMoreDocuments,
|
||||
onLoadMoreTasks,
|
||||
export function DocumentDetailTasksDrawer({
|
||||
onOpenChange,
|
||||
onRefreshDocumentsAndTasks,
|
||||
onRetryPermissionQuery,
|
||||
onTaskUpdated,
|
||||
onWritePermissionDenied,
|
||||
open,
|
||||
permissionQueryError,
|
||||
permissionQueryFetching,
|
||||
permissionQueryPending,
|
||||
readOnlyReason,
|
||||
taskQueryError,
|
||||
taskQueryFetching,
|
||||
taskQueryPending,
|
||||
tasks,
|
||||
taskProgressStore,
|
||||
sourceNames,
|
||||
onRetryTaskQuery,
|
||||
onRetryDocumentQuery,
|
||||
}: {
|
||||
canEdit: boolean
|
||||
actionResultsValid: boolean
|
||||
documentQueryError: boolean
|
||||
documentQueryFetching: boolean
|
||||
documents: LogicalDocument[]
|
||||
documentsPending: boolean
|
||||
hasNextDocumentPage: boolean
|
||||
hasNextTaskPage: boolean
|
||||
hasUnresolvedTaskDocuments: boolean
|
||||
isFetchingNextDocumentPage: boolean
|
||||
isFetchingNextTaskPage: boolean
|
||||
knowledgeSpaceId: string
|
||||
onLoadMoreDocuments: () => void
|
||||
onLoadMoreTasks: () => void
|
||||
onOpenChange: (open: boolean) => void
|
||||
onRefreshDocumentsAndTasks: () => void
|
||||
onRetryPermissionQuery: () => void
|
||||
onTaskUpdated: (task: DocumentProcessingTask) => void
|
||||
onWritePermissionDenied: () => void
|
||||
open: boolean
|
||||
permissionQueryError: boolean
|
||||
permissionQueryFetching: boolean
|
||||
permissionQueryPending: boolean
|
||||
readOnlyReason?: string
|
||||
taskQueryError: boolean
|
||||
taskQueryFetching: boolean
|
||||
taskQueryPending: boolean
|
||||
tasks: BackgroundTask[]
|
||||
taskProgressStore: TaskProgressStore
|
||||
sourceNames?: Map<string, string>
|
||||
onRetryDocumentQuery: () => void
|
||||
onRetryTaskQuery: () => void
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const currentDocument = useAtomValue(documentDetailDocumentAtom)
|
||||
const knowledgeSpaceId = useAtomValue(documentDetailKnowledgeSpaceIdAtom)
|
||||
const canEdit = useAtomValue(documentCanEditAtom)
|
||||
const tasks = useAtomValue(documentBackgroundTasksAtom)
|
||||
const taskQueryError = Boolean(useAtomValue(documentTasksQueryErrorAtom))
|
||||
const hasNextTaskPage = useAtomValue(documentTasksQueryHasNextPageAtom)
|
||||
const taskQueryFetching = useAtomValue(documentTasksQueryIsFetchingAtom)
|
||||
const isFetchingNextTaskPage = useAtomValue(documentTasksQueryIsFetchingNextPageAtom)
|
||||
const taskQueryPending = useAtomValue(documentTasksQueryIsPendingAtom)
|
||||
const loadMoreTasks = useSetAtom(loadNextDocumentTaskPageAtom)
|
||||
const refreshDocument = useSetAtom(refreshDocumentDetailAtom)
|
||||
const refreshTasks = useSetAtom(refreshDocumentTasksAtom)
|
||||
const retryTasks = useSetAtom(retryDocumentTasksAtom)
|
||||
const retryWritePermission = useSetAtom(retryDocumentWritePermissionAtom)
|
||||
const documentsQuery = useInfiniteQuery(
|
||||
consoleQuery.knowledgeFs.spaces.byControlSpaceId.logicalDocuments.get.infiniteOptions({
|
||||
enabled: open,
|
||||
input: (pageParam) => ({
|
||||
params: { control_space_id: knowledgeSpaceId },
|
||||
query: {
|
||||
...(typeof pageParam === 'string' ? { cursor: pageParam } : {}),
|
||||
},
|
||||
}),
|
||||
getNextPageParam: (lastPage) => lastPage.next_cursor,
|
||||
initialPageParam: null as string | null,
|
||||
}),
|
||||
)
|
||||
const documents = useMemo(() => {
|
||||
const queryDocuments =
|
||||
documentsQuery.data?.pages.flatMap((page) => logicalDocumentListFromApi(page).items) ?? []
|
||||
if (queryDocuments.some((document) => document.id === currentDocument.id)) return queryDocuments
|
||||
return [currentDocument, ...queryDocuments]
|
||||
}, [currentDocument, documentsQuery.data])
|
||||
const documentIds = useMemo(() => new Set(documents.map((document) => document.id)), [documents])
|
||||
const hasUnresolvedTaskDocuments = tasks.some(
|
||||
(task) => task.documentId && !documentIds.has(task.documentId),
|
||||
)
|
||||
const documentQueryError = Boolean(documentsQuery.error)
|
||||
const documentQueryFetching = documentsQuery.isFetching
|
||||
const documentsPending = Boolean(documentsQuery.isPending || documentsQuery.hasNextPage)
|
||||
const hasNextDocumentPage = Boolean(documentsQuery.hasNextPage)
|
||||
const isFetchingNextDocumentPage = documentsQuery.isFetchingNextPage
|
||||
const readOnlyReason = canEdit
|
||||
? undefined
|
||||
: t(($) => $['newKnowledge.documentPermissionRestricted'])
|
||||
const taskProgressStoreRef = useRef<ReturnType<typeof createTaskProgressStore> | null>(null)
|
||||
if (!taskProgressStoreRef.current) taskProgressStoreRef.current = createTaskProgressStore()
|
||||
const taskProgressStore = taskProgressStoreRef.current
|
||||
const refreshDocumentsAndTasks = () =>
|
||||
Promise.all([refreshDocument(), documentsQuery.refetch(), refreshTasks()])
|
||||
const retryDocuments = () => {
|
||||
if (documentsQuery.isFetchNextPageError) return documentsQuery.fetchNextPage()
|
||||
return documentsQuery.refetch()
|
||||
}
|
||||
const cancelTask = useMutation({
|
||||
mutationFn: async (task: BackgroundTask) =>
|
||||
backgroundTaskFromApi(
|
||||
@ -152,15 +162,12 @@ export function ProcessingTasksDrawer({
|
||||
const drawerCloseButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const taskQueryRetryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const documentQueryRetryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const permissionQueryRetryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const focusedTaskActionRef = useRef<HTMLButtonElement | null>(null)
|
||||
const loadMoreRequestedRef = useRef(false)
|
||||
const queryRetryFocusRequestedRef = useRef(false)
|
||||
const permissionRetryFocusRequestedRef = useRef(false)
|
||||
const loadMoreButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const openCycleRef = useRef(0)
|
||||
const openRef = useRef(open)
|
||||
const actionResultsValidRef = useRef(actionResultsValid)
|
||||
const previousOpenRef = useRef(open)
|
||||
const [pendingActions, setPendingActions] = useState<Set<string>>(() => new Set())
|
||||
const [actionErrors, setActionErrors] = useState<Record<string, string>>({})
|
||||
@ -207,7 +214,6 @@ export function ProcessingTasksDrawer({
|
||||
new Map<string, { generation: number; lifecycle: string }>(),
|
||||
)
|
||||
useLayoutEffect(() => {
|
||||
actionResultsValidRef.current = actionResultsValid
|
||||
const currentTaskIds = new Set(tasks.map((task) => task.id))
|
||||
for (const task of tasks) {
|
||||
const lifecycle = taskLifecycle(task)
|
||||
@ -221,7 +227,7 @@ export function ProcessingTasksDrawer({
|
||||
for (const taskId of taskLifecycleGenerationsRef.current.keys()) {
|
||||
if (!currentTaskIds.has(taskId)) taskLifecycleGenerationsRef.current.delete(taskId)
|
||||
}
|
||||
}, [actionResultsValid, tasks])
|
||||
}, [tasks])
|
||||
|
||||
useEffect(() => {
|
||||
openRef.current = open
|
||||
@ -273,16 +279,6 @@ export function ProcessingTasksDrawer({
|
||||
drawerCloseButtonRef.current?.focus()
|
||||
}, [documentQueryError, hasUnresolvedTaskDocuments, open, taskQueryError])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !permissionRetryFocusRequestedRef.current) return
|
||||
if (permissionQueryError) {
|
||||
permissionQueryRetryButtonRef.current?.focus()
|
||||
return
|
||||
}
|
||||
permissionRetryFocusRequestedRef.current = false
|
||||
drawerCloseButtonRef.current?.focus()
|
||||
}, [open, permissionQueryError])
|
||||
|
||||
useEffect(() => {
|
||||
const focusedAction = focusedTaskActionRef.current
|
||||
if (!open || !focusedAction || focusedAction.isConnected) return
|
||||
@ -306,19 +302,16 @@ export function ProcessingTasksDrawer({
|
||||
const updated =
|
||||
action === 'cancel' ? await cancelTask.mutateAsync(task) : await retryTask.mutateAsync(task)
|
||||
if (
|
||||
!actionResultsValidRef.current ||
|
||||
taskLifecycleGenerationsRef.current.get(task.id)?.generation !== actionLifecycleGeneration
|
||||
)
|
||||
return
|
||||
if (updated.documentId && updated.documentRevision)
|
||||
onTaskUpdated(updated as DocumentProcessingTask)
|
||||
if (updated.documentId && updated.documentRevision) void refreshTasks()
|
||||
setActionErrors((current) => {
|
||||
const next = { ...current }
|
||||
delete next[task.id]
|
||||
return next
|
||||
})
|
||||
if (
|
||||
actionResultsValidRef.current &&
|
||||
openRef.current &&
|
||||
openCycleRef.current === actionOpenCycle &&
|
||||
document.activeElement === actionFocusTarget
|
||||
@ -326,10 +319,9 @@ export function ProcessingTasksDrawer({
|
||||
drawerCloseButtonRef.current?.focus()
|
||||
} catch (error) {
|
||||
const permissionDenied = responseStatus(error) === 403
|
||||
if (permissionDenied) onWritePermissionDenied()
|
||||
if (permissionDenied) void retryWritePermission()
|
||||
if (
|
||||
!permissionDenied &&
|
||||
actionResultsValidRef.current &&
|
||||
openRef.current &&
|
||||
openCycleRef.current === actionOpenCycle &&
|
||||
taskLifecycleGenerationsRef.current.get(task.id)?.generation === actionLifecycleGeneration
|
||||
@ -342,7 +334,7 @@ export function ProcessingTasksDrawer({
|
||||
next.delete(task.id)
|
||||
return next
|
||||
})
|
||||
if (actionResultsValidRef.current) onRefreshDocumentsAndTasks()
|
||||
void refreshDocumentsAndTasks()
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,39 +374,8 @@ export function ProcessingTasksDrawer({
|
||||
{readOnlyReason}
|
||||
</p>
|
||||
)}
|
||||
{permissionQueryPending && (
|
||||
<p className="mt-2 system-xs-regular text-text-tertiary" role="status">
|
||||
{t(($) => $['newKnowledge.permission'])}
|
||||
{' · '}
|
||||
{tCommon(($) => $.loading)}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pr-[calc(1.5rem+env(safe-area-inset-right,0px))] pb-[calc(1.5rem+env(safe-area-inset-bottom,0px))] pl-[calc(1.5rem+env(safe-area-inset-left,0px))]">
|
||||
{permissionQueryError && (
|
||||
<div className="mb-3 rounded-xl border border-divider-regular p-4" role="alert">
|
||||
<p className="system-xs-regular text-text-destructive">
|
||||
{t(($) => $['newKnowledge.permissionLoadFailed'])}
|
||||
</p>
|
||||
<Button
|
||||
ref={permissionQueryRetryButtonRef}
|
||||
aria-label={`${tCommon(($) => $['operation.retry'])} · ${t(($) => $['newKnowledge.permissionLoadFailed'])}`}
|
||||
aria-busy={permissionQueryFetching}
|
||||
className="mt-3"
|
||||
loading={permissionQueryFetching}
|
||||
size="small"
|
||||
onBlur={(event) => {
|
||||
if (event.relatedTarget) permissionRetryFocusRequestedRef.current = false
|
||||
}}
|
||||
onClick={() => {
|
||||
permissionRetryFocusRequestedRef.current = true
|
||||
onRetryPermissionQuery()
|
||||
}}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{taskQueryError && (
|
||||
<div className="mb-3 rounded-xl border border-divider-regular p-4" role="alert">
|
||||
<p className="system-xs-regular text-text-destructive">
|
||||
@ -432,7 +393,7 @@ export function ProcessingTasksDrawer({
|
||||
}}
|
||||
onClick={() => {
|
||||
queryRetryFocusRequestedRef.current = true
|
||||
onRetryTaskQuery()
|
||||
void retryTasks()
|
||||
}}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
@ -456,7 +417,7 @@ export function ProcessingTasksDrawer({
|
||||
}}
|
||||
onClick={() => {
|
||||
queryRetryFocusRequestedRef.current = true
|
||||
onRetryDocumentQuery()
|
||||
void retryDocuments()
|
||||
}}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
@ -482,9 +443,6 @@ export function ProcessingTasksDrawer({
|
||||
($) => $[`newKnowledge.overview.operation.${task.operation}`],
|
||||
)
|
||||
const progress = taskProgress(task)
|
||||
const sourceTitle = task.sourceId
|
||||
? sourceNames?.get(task.sourceId)
|
||||
: undefined
|
||||
const title =
|
||||
task.operation === 'document_processing' && documentTitle
|
||||
? `${t(($) => $['newKnowledge.addDocument'])} · ${documentTitle}`
|
||||
@ -494,11 +452,9 @@ export function ProcessingTasksDrawer({
|
||||
? `${t(($) => $['newKnowledge.reindexDocuments'])}${progress ? ` · ${progress.total}` : documentTitle ? ` · ${documentTitle}` : ''}`
|
||||
: task.operation === 'document_delete' && documentTitle
|
||||
? `${operationTitle} · ${documentTitle}`
|
||||
: sourceTitle
|
||||
? `${operationTitle} · ${sourceTitle}`
|
||||
: progress
|
||||
? `${operationTitle} · ${progress.total}`
|
||||
: operationTitle
|
||||
: progress
|
||||
? `${operationTitle} · ${progress.total}`
|
||||
: operationTitle
|
||||
const timestamp = Date.parse(
|
||||
taskIsActive(task) ? task.createdAt : taskTime(task),
|
||||
)
|
||||
@ -656,8 +612,9 @@ export function ProcessingTasksDrawer({
|
||||
loadMoreRequestedRef.current =
|
||||
document.activeElement === loadMoreButtonRef.current
|
||||
if (tasks.length <= orderedBaseTasks.length && hasNextTaskPage)
|
||||
onLoadMoreTasks()
|
||||
if (hasUnresolvedTaskDocuments && hasNextDocumentPage) onLoadMoreDocuments()
|
||||
void loadMoreTasks()
|
||||
if (hasUnresolvedTaskDocuments && hasNextDocumentPage)
|
||||
void documentsQuery.fetchNextPage()
|
||||
setVisibleTaskLimit((current) => current + TASK_DRAWER_LIMIT)
|
||||
}}
|
||||
>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user