"use client"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Loader2, RefreshCw, Upload } from "lucide-react"; import type { KnowledgeUploadPolicy } from "@/lib/knowledge-api"; import { kbIsUploadable, kbNeedsReindex, resolveKbStatus, resolveProgressPercent, validateFiles, type KnowledgeBase, } from "@/lib/knowledge-helpers"; import type { TaskState } from "@/hooks/useKnowledgeProgress"; import type { HistoryEntry } from "@/hooks/useKnowledgeHistory"; import ProcessLogs from "@/components/common/ProcessLogs"; import FileDropZone from "./FileDropZone"; import KbUpdateHistory from "./KbUpdateHistory"; interface KbDocumentsSectionProps { kb: KnowledgeBase; uploadPolicy: KnowledgeUploadPolicy; task?: TaskState; history: HistoryEntry[]; onClearHistory: () => void; onRetry?: () => Promise; onUpload: (files: File[]) => Promise; } /** * The "Add documents" tab. Focused on the incremental-upload flow: drop * zone, upload button, live process logs while a task runs, and a list of * past update events. The file list and preview live under the separate * "Files" tab to keep each surface single-purpose. */ export default function KbDocumentsSection({ kb, uploadPolicy, task, history, onClearHistory, onRetry, onUpload, }: KbDocumentsSectionProps) { const { t } = useTranslation(); const [files, setFiles] = useState([]); const [submitting, setSubmitting] = useState(false); const [retrySubmitting, setRetrySubmitting] = useState(false); const uploadable = kbIsUploadable(kb); const needsReindex = kbNeedsReindex(kb); const status = resolveKbStatus(kb); const isError = status === "error"; const isUploadingHere = task?.kind === "upload" && task.executing; const isIndexingHere = (task?.kind === "reindex" || task?.kind === "retry") && task.executing; const isRetryingHere = task?.kind === "retry" && task.executing; // An error-state KB is not locked: the user can drop the file(s) that failed // (Files tab) and upload replacements here, instead of being forced to // delete and rebuild the whole base. Uploads stay open unless a rebuild is // actively running; legacy/transition states remain genuinely blocked. const canUpload = uploadable || (isError && !isIndexingHere); const blockedReason = canUpload ? null : needsReindex ? t( "This knowledge base is in legacy index format and needs reindex before upload.", ) : status !== "ready" ? t( "This knowledge base is currently {{status}} and cannot accept uploads yet.", { status: status.replaceAll("_", " ") }, ) : null; const selection = validateFiles(files, uploadPolicy, t); const canRetry = Boolean(onRetry) && isError && !isIndexingHere; // Unsupported files are skipped (shown in the drop zone), not blocking, so a // picked folder with mixed content still uploads its supported members. const canSubmit = canUpload && selection.validFiles.length > 0 && !submitting && !isUploadingHere; const handleSubmit = async () => { if (!canSubmit) return; setSubmitting(true); try { await onUpload(selection.validFiles); setFiles([]); } finally { setSubmitting(false); } }; const handleRetry = async () => { if (!onRetry || !canRetry || retrySubmitting) return; setRetrySubmitting(true); try { await onRetry(); } finally { setRetrySubmitting(false); } }; const percent = resolveProgressPercent(kb.progress); const showTaskLogs = task?.kind === "upload" || task?.kind === "create" || task?.kind === "reindex" || task?.kind === "retry"; const taskLogTitle = task?.kind === "create" ? t("Create Process") : task?.kind === "retry" ? t("Retry Process") : task?.kind === "reindex" ? t("Re-index Process") : t("Upload Process"); return (
{t("Add documents")}

{t( "Drop files here to add them to this knowledge base. New files are indexed against the active embedding model.", )}

{blockedReason && (
{blockedReason}
)} {isError && !blockedReason && (
{t( "The last indexing run failed. Remove the file(s) that failed in the Files tab, upload replacements below, or retry to rebuild from the current documents.", )} {onRetry && ( )}
)}
{showTaskLogs && task && (task.taskId || task.logs.length > 0 || task.executing) && (
{task.label} {task.taskId ? ` · ${task.taskId}` : ""} {task.executing && percent > 0 && ( {percent}% )}
{task.executing && (
)} {task.error && (
                  {task.error}
                
)}
)}
); }