fix(agents): list source-less agents and select only the uploaded source

Review follow-ups:

- The agents list and the pinned list dropped agents that had neither a
  source nor a retriever. Publishing such an agent is now allowed, so it
  vanished from the list right after publish. Both filters are gone; a
  source-less agent skips retrieval and is still runnable. Tests cover
  both listings.
- The agent form selected an uploaded source by diffing the source
  catalog before and after the upload, which would also pick up any
  source created or shared in the meantime. Upload now passes the id of
  the source it created to onSuccessfulUpload, and the form selects only
  that id.
This commit is contained in:
Alex committed 2026-09-09 18:39:09 +01:00
1 parent 79d418f32a
commit 0fe059db12
4 files changed
+69 -38

No files matched your search

+9 -18
View File
@@ -153,9 +153,6 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
const [isModelsPopupOpen, setIsModelsPopupOpen] = useState(false);
const [uploadModalState, setUploadModalState] =
useState<ActiveState>('INACTIVE');
// Set once an upload started from this form finishes, so the source it
// created can be selected as soon as the refreshed list arrives.
const [pendingUploadSelect, setPendingUploadSelect] = useState(false);
const [selectedSourceIds, setSelectedSourceIds] = useState<Set<string>>(
new Set(),
);
@@ -181,7 +178,6 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
const sourceAnchorButtonRef = useRef<HTMLButtonElement>(null);
const toolAnchorButtonRef = useRef<HTMLButtonElement>(null);
const modelAnchorButtonRef = useRef<HTMLButtonElement>(null);
const sourceIdsBeforeUploadRef = useRef<Set<string>>(new Set());
const modeConfig = {
new: {
@@ -275,13 +271,18 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
});
const handleUploadClick = () => {
sourceIdsBeforeUploadRef.current = new Set(
(sourceDocs ?? []).map(sourceItemId),
);
setIsSourcePopupOpen(false);
setUploadModalState('ACTIVE');
};
// Select the source an upload started from this form created. The id comes
// from the upload itself, so a source that appeared meanwhile for another
// reason is never picked up by accident.
const handleUploadedSource = (sourceId?: string) => {
if (!sourceId) return;
setSelectedSourceIds((prev) => new Set([...prev, sourceId]));
};
// Sources go out as the legacy single ``source`` for one selection and as
// the ``sources`` list for several; an empty selection sends both empty.
const appendSourceFields = (formData: FormData) => {
@@ -656,16 +657,6 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
validateAndSetFolder();
}, [folderIdFromUrl, agentFolders, token, dispatch]);
// After an upload started from this form, select the source it created.
useEffect(() => {
if (!pendingUploadSelect || !sourceDocs) return;
const before = sourceIdsBeforeUploadRef.current;
const added = sourceDocs.map(sourceItemId).filter((id) => !before.has(id));
if (added.length === 0) return;
setPendingUploadSelect(false);
setSelectedSourceIds((prev) => new Set([...prev, ...added]));
}, [pendingUploadSelect, sourceDocs]);
useEffect(() => {
if ((mode === 'edit' || mode === 'draft') && agentId) {
const getAgent = async () => {
@@ -1476,7 +1467,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
isOnboarding={false}
renderTab={null}
close={() => setUploadModalState('INACTIVE')}
onSuccessfulUpload={() => setPendingUploadSelect(true)}
onSuccessfulUpload={handleUploadedSource}
/>
)}
<AddPromptModal
+12 -8
View File
@@ -67,7 +67,8 @@ function Upload({
isOnboarding: boolean;
renderTab: string | null;
close: () => void;
onSuccessfulUpload?: () => void;
/** Fires once the upload is ingested, with the id of the source it created. */
onSuccessfulUpload?: (sourceId?: string) => void;
}) {
const token = useSelector(selectToken);
const selectedDocs = useSelector(selectSelectedDocs);
@@ -442,7 +443,10 @@ function Upload({
(clientTaskId: string) => {
let handled = false;
const handleTerminal = (status: 'completed' | 'failed') => {
const handleTerminal = (
status: 'completed' | 'failed',
sourceId?: string,
) => {
if (handled) return;
handled = true;
if (status !== 'completed') return;
@@ -468,7 +472,7 @@ function Upload({
}
}
}
onSuccessfulUpload?.();
onSuccessfulUpload?.(sourceId);
})
.catch((err) => {
console.error(
@@ -483,7 +487,7 @@ function Upload({
const task = state.upload.tasks.find((t) => t.id === clientTaskId);
if (!task) return false;
if (task.status === 'completed' || task.status === 'failed') {
handleTerminal(task.status);
handleTerminal(task.status, task.sourceId);
return true;
}
// Recover from the race where the terminal SSE landed before
@@ -494,7 +498,7 @@ function Upload({
for (const event of state.notifications.recentEvents) {
if (event.scope?.id !== task.sourceId) continue;
if (event.type === 'source.ingest.completed') {
handleTerminal('completed');
handleTerminal('completed', task.sourceId);
return true;
}
if (event.type === 'source.ingest.failed') {
@@ -626,7 +630,7 @@ function Upload({
updates: { status: 'completed', progress: 100 },
}),
);
onSuccessfulUpload?.();
onSuccessfulUpload?.(parsed.source_id);
}
} catch (error) {
handleTaskFailure(clientTaskId);
@@ -762,7 +766,7 @@ function Upload({
updates: { status: 'completed', progress: 100 },
}),
);
onSuccessfulUpload?.();
onSuccessfulUpload?.(response.source_id);
}
} catch (error) {
handleTaskFailure(clientTaskId);
@@ -813,7 +817,7 @@ function Upload({
},
}),
);
onSuccessfulUpload?.();
onSuccessfulUpload?.(data.source_id);
})
.catch(() => handleTaskFailure(clientTaskId));
};