This commit is contained in:
ValueOn AG 2026-04-21 07:46:18 +02:00
parent b4574b6a2e
commit 8e5a01df6d

View file

@ -138,8 +138,8 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId,
// Rehydrate the chip-bar whenever the parent re-loads a chat (loadedNonce
// bumps on every loadWorkflow call). We trust the loaded IDs initially;
// a separate effect below drops IDs that don't resolve once the source
// lists have arrived from the backend.
// a separate one-shot reconciliation below drops IDs that don't resolve
// once the source lists have arrived from the backend.
useEffect(() => {
if (loadedNonce === undefined) return;
setAttachedFileIds([]);
@ -149,25 +149,40 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId,
// Drop persisted attachment IDs that no longer resolve to an existing
// source (e.g. the DataSource was deleted while the chat was closed).
// We only run this once the lists are populated to avoid wiping chips
// before the lists have loaded.
//
// CRITICAL: this MUST run only once per chat-load (per `loadedNonce`),
// and only after the source lists have actually arrived. A continuous
// filter would race with `_handleDataSourceDrop` /
// `_handleSendToChat_FeatureSource` in the parent: the drop sets the
// chip via `pendingAttachDsId` *before* `refreshDataSources()` has
// returned, so a continuous filter would briefly evict the freshly
// dropped ID and the chip would visibly flash in and out.
const _reconciledDsForNonce = useRef<number | undefined>(undefined);
const _reconciledFdsForNonce = useRef<number | undefined>(undefined);
useEffect(() => {
if (dataSources.length === 0 && attachedDataSourceIds.length === 0) return;
if (loadedNonce === undefined) return;
if (_reconciledDsForNonce.current === loadedNonce) return;
if (dataSources.length === 0) return; // wait for the list to arrive
_reconciledDsForNonce.current = loadedNonce;
const validIds = new Set(dataSources.map(d => d.id));
setAttachedDataSourceIds(prev => {
const filtered = prev.filter(id => validIds.has(id));
return filtered.length === prev.length ? prev : filtered;
});
}, [dataSources, attachedDataSourceIds.length]);
}, [loadedNonce, dataSources]);
useEffect(() => {
if (featureDataSources.length === 0 && attachedFeatureDataSourceIds.length === 0) return;
if (loadedNonce === undefined) return;
if (_reconciledFdsForNonce.current === loadedNonce) return;
if (featureDataSources.length === 0) return;
_reconciledFdsForNonce.current = loadedNonce;
const validIds = new Set(featureDataSources.map(d => d.id));
setAttachedFeatureDataSourceIds(prev => {
const filtered = prev.filter(id => validIds.has(id));
return filtered.length === prev.length ? prev : filtered;
});
}, [featureDataSources, attachedFeatureDataSourceIds.length]);
}, [loadedNonce, featureDataSources]);
// Persist a changed attachment list to the backend so the next chat
// reload reflects the current state. We debounce slightly by sending on