diff --git a/frontend-web/src/components/assistant-ui/document-upload-popup.tsx b/frontend-web/src/components/assistant-ui/document-upload-popup.tsx index a0afd6d5..78e314fe 100644 --- a/frontend-web/src/components/assistant-ui/document-upload-popup.tsx +++ b/frontend-web/src/components/assistant-ui/document-upload-popup.tsx @@ -405,8 +405,15 @@ const DocumentUploadPopupContent: FC<{ if (response.ok) { const data = await response.json() as { imported_count?: number }; toast.success(`成功导入 ${data.imported_count || dataList.length} 个来源`); - setListData([]); - handleBatchDelete(); + onUploadComplete?.( + selectedSearchSources.map((s) => ({ + id: undefined, + title: s.title, + task_id: undefined, + status: "success" as const, + })) + ); + onOpenChange(false); } else { const errorData = await response.json() as { detail?: string }; toast.error(errorData.detail || "导入失败,请重试"); diff --git a/frontend-web/src/pages/StudioNotebookDetailPage.tsx b/frontend-web/src/pages/StudioNotebookDetailPage.tsx index bfb7ea8b..03134ff6 100644 --- a/frontend-web/src/pages/StudioNotebookDetailPage.tsx +++ b/frontend-web/src/pages/StudioNotebookDetailPage.tsx @@ -154,6 +154,7 @@ function SourcesDialog({ isLoadingSources, selectedSourceIds, pendingSources, + pendingImportTitles, onToggle, onDelete, }: { @@ -164,6 +165,7 @@ function SourcesDialog({ isLoadingSources: boolean; selectedSourceIds: number[]; pendingSources: Map; + pendingImportTitles: string[]; onToggle: (id: number) => void; onDelete: (id: number) => void; }) { @@ -216,6 +218,14 @@ function SourcesDialog({ .map(([taskId, s]) => ( ))} + {pendingImportTitles + .filter((t) => !sources.some((src) => { + const srcTitle = src.title ?? (src.document as { title?: string } | undefined)?.title ?? ""; + return srcTitle === t; + })) + .map((t) => ( + + ))} {sources.map((src) => ( 来源信息 const [pendingSources, setPendingSources] = useState>(new Map()); const pollingIntervalRef = useRef | null>(null); + // 同步导入(搜索导入)的占位标题列表,sources 刷新后按标题匹配移除 + const [pendingImportTitles, setPendingImportTitles] = useState([]); useEffect(() => { if (!selectedModel && models.length > 0) { @@ -287,20 +299,25 @@ export default function StudioNotebookDetailPage({ } }, [sources, notebook?.id, initializedForNotebook]); - // sources 刷新后,将轮询成功的来源(按标题匹配)自动选中 + // sources 刷新后,将轮询成功/同步导入的来源(按标题匹配)自动选中,并移除占位卡 useEffect(() => { - if (pendingSelectTitlesRef.current.size === 0) return; - const matched = sources.filter((s) => { - const title = s.title ?? (s.document as { title?: string } | undefined)?.title ?? ""; - return pendingSelectTitlesRef.current.has(title); - }); - if (matched.length > 0) { - setSelectedSourceIds((prev) => [...new Set([...prev, ...matched.map((s) => s.id)])]); - matched.forEach((s) => { - const title = s.title ?? (s.document as { title?: string } | undefined)?.title ?? ""; - pendingSelectTitlesRef.current.delete(title); - }); + const getTitle = (s: SourceItem) => + s.title ?? (s.document as { title?: string } | undefined)?.title ?? ""; + + if (pendingSelectTitlesRef.current.size > 0) { + const matched = sources.filter((s) => pendingSelectTitlesRef.current.has(getTitle(s))); + if (matched.length > 0) { + setSelectedSourceIds((prev) => [...new Set([...prev, ...matched.map((s) => s.id)])]); + matched.forEach((s) => pendingSelectTitlesRef.current.delete(getTitle(s))); + } } + + setPendingImportTitles((prev) => { + if (prev.length === 0) return prev; + const sourceTitles = new Set(sources.map(getTitle)); + const remaining = prev.filter((t) => !sourceTitles.has(t)); + return remaining.length === prev.length ? prev : remaining; + }); }, [sources]); const citationRehypePlugins = useMemo(() => { const mapped = sources.map((s) => ({ @@ -392,6 +409,12 @@ export default function StudioNotebookDetailPage({ return newMap; }); } + // 同步导入(status=success,无 task_id):立刻显示占位卡 + 等 sources 刷新后自动选中 + const syncTitles = uploadedSources.filter(s => s.status === 'success' && s.title).map(s => s.title); + if (syncTitles.length > 0) { + syncTitles.forEach(t => pendingSelectTitlesRef.current.add(t)); + setPendingImportTitles(prev => [...new Set([...prev, ...syncTitles])]); + } // 刷新来源列表 void queryClient.invalidateQueries({ queryKey: ["studio", "sources", notebook.id] }); // 刷新 notebooks 数据以更新 source_count @@ -854,6 +877,7 @@ export default function StudioNotebookDetailPage({ isLoadingSources={isLoadingSources} selectedSourceIds={selectedSourceIds} pendingSources={pendingSources} + pendingImportTitles={pendingImportTitles} onToggle={handleToggleSource} onDelete={handleDeleteSource} />