udb fix
This commit is contained in:
parent
8e5a01df6d
commit
1c4233c7ea
2 changed files with 41 additions and 99 deletions
|
|
@ -606,17 +606,6 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
}
|
}
|
||||||
}, [instanceId, _fetchDataSources, onSourcesChanged]);
|
}, [instanceId, _fetchDataSources, onSourcesChanged]);
|
||||||
|
|
||||||
/* ── Remove DataSource ── */
|
|
||||||
const _removeDatasource = useCallback(async (dsId: string) => {
|
|
||||||
try {
|
|
||||||
await api.delete(`/api/workspace/${instanceId}/datasources/${dsId}`);
|
|
||||||
_fetchDataSources();
|
|
||||||
onSourcesChanged?.();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to remove data source:', err);
|
|
||||||
}
|
|
||||||
}, [instanceId, _fetchDataSources, onSourcesChanged]);
|
|
||||||
|
|
||||||
/* ── Check if a path is already added ── */
|
/* ── Check if a path is already added ── */
|
||||||
const _isAdded = useCallback((connectionId: string, service: string | undefined, path: string | undefined): boolean => {
|
const _isAdded = useCallback((connectionId: string, service: string | undefined, path: string | undefined): boolean => {
|
||||||
const expectedSourceType = service ? (_SERVICE_TO_SOURCE_TYPE[service] || service) : undefined;
|
const expectedSourceType = service ? (_SERVICE_TO_SOURCE_TYPE[service] || service) : undefined;
|
||||||
|
|
@ -841,17 +830,6 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
}
|
}
|
||||||
}, [instanceId, _fetchFeatureDataSources, onSourcesChanged]);
|
}, [instanceId, _fetchFeatureDataSources, onSourcesChanged]);
|
||||||
|
|
||||||
/* ── Feature: Remove FeatureDataSource ── */
|
|
||||||
const _removeFeatureDataSource = useCallback(async (fdsId: string) => {
|
|
||||||
try {
|
|
||||||
await api.delete(`/api/workspace/${instanceId}/feature-datasources/${fdsId}`);
|
|
||||||
_fetchFeatureDataSources();
|
|
||||||
onSourcesChanged?.();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to remove feature data source:', err);
|
|
||||||
}
|
|
||||||
}, [instanceId, _fetchFeatureDataSources, onSourcesChanged]);
|
|
||||||
|
|
||||||
/* ── Feature: check if table already added (no record filter) ── */
|
/* ── Feature: check if table already added (no record filter) ── */
|
||||||
const _isFeatureTableAdded = useCallback((featureInstanceId: string, tableName: string): boolean => {
|
const _isFeatureTableAdded = useCallback((featureInstanceId: string, tableName: string): boolean => {
|
||||||
return featureDataSources.some(fds =>
|
return featureDataSources.some(fds =>
|
||||||
|
|
@ -1021,7 +999,6 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
dataSources={dataSources}
|
dataSources={dataSources}
|
||||||
onCycleScope={_cyclePersonalScope}
|
onCycleScope={_cyclePersonalScope}
|
||||||
onToggleNeutralize={_togglePersonalNeutralize}
|
onToggleNeutralize={_togglePersonalNeutralize}
|
||||||
onRemoveDs={_removeDatasource}
|
|
||||||
onSendToChat={_sendNodeToChat}
|
onSendToChat={_sendNodeToChat}
|
||||||
scopeCycleTitle={_scopeCycleTitle}
|
scopeCycleTitle={_scopeCycleTitle}
|
||||||
selectedKeys={selectedKeys}
|
selectedKeys={selectedKeys}
|
||||||
|
|
@ -1081,7 +1058,6 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
onCycleScope={_cycleFeatureScope}
|
onCycleScope={_cycleFeatureScope}
|
||||||
onToggleNeutralize={_toggleFeatureNeutralize}
|
onToggleNeutralize={_toggleFeatureNeutralize}
|
||||||
onToggleNeutralizeField={_toggleNeutralizeField}
|
onToggleNeutralizeField={_toggleNeutralizeField}
|
||||||
onRemoveFds={_removeFeatureDataSource}
|
|
||||||
featureTree={featureTree}
|
featureTree={featureTree}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
@ -1110,7 +1086,6 @@ interface _TreeNodeViewProps {
|
||||||
dataSources: UdbDataSource[];
|
dataSources: UdbDataSource[];
|
||||||
onCycleScope: (ds: UdbDataSource) => void;
|
onCycleScope: (ds: UdbDataSource) => void;
|
||||||
onToggleNeutralize: (ds: UdbDataSource) => void;
|
onToggleNeutralize: (ds: UdbDataSource) => void;
|
||||||
onRemoveDs: (dsId: string) => void;
|
|
||||||
onSendToChat?: (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => void;
|
onSendToChat?: (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => void;
|
||||||
scopeCycleTitle: (scope: string) => string;
|
scopeCycleTitle: (scope: string) => string;
|
||||||
selectedKeys: Set<string>;
|
selectedKeys: Set<string>;
|
||||||
|
|
@ -1121,7 +1096,7 @@ interface _TreeNodeViewProps {
|
||||||
|
|
||||||
const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
|
const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
|
||||||
node, depth, onToggle, onEnsureDs, isAdded, addingPath,
|
node, depth, onToggle, onEnsureDs, isAdded, addingPath,
|
||||||
dataSources, onCycleScope, onToggleNeutralize, onRemoveDs, onSendToChat, scopeCycleTitle,
|
dataSources, onCycleScope, onToggleNeutralize, onSendToChat, scopeCycleTitle,
|
||||||
selectedKeys, onSelect, inheritedScope, inheritedNeutralize,
|
selectedKeys, onSelect, inheritedScope, inheritedNeutralize,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
|
@ -1216,19 +1191,11 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
|
||||||
{node.label}
|
{node.label}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Dynamic action: Remove (only when DS exists) — placed LEFT of the
|
{/* ── Stable trio: chat | scope | neutralize (always in this order).
|
||||||
* stable trio so the trio always anchors at the right edge. */}
|
* No "remove from workspace" button here by design: the UDB row only
|
||||||
{ds && (
|
* exposes the catalog state. Detach from the *current chat* happens
|
||||||
<button
|
* via the chip "x" in WorkspaceInput; that chip is the single source
|
||||||
onClick={e => { e.stopPropagation(); onRemoveDs(ds.id); }}
|
* of truth for chat-scoped attachment lifecycle. */}
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 10, color: '#999', padding: '0 2px', flexShrink: 0 }}
|
|
||||||
title={t('Entfernen')}
|
|
||||||
>
|
|
||||||
{'\u2715'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ── Stable trio: chat | scope | neutralize (always in this order) ── */}
|
|
||||||
<button
|
<button
|
||||||
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
|
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -1290,7 +1257,6 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
|
||||||
dataSources={dataSources}
|
dataSources={dataSources}
|
||||||
onCycleScope={onCycleScope}
|
onCycleScope={onCycleScope}
|
||||||
onToggleNeutralize={onToggleNeutralize}
|
onToggleNeutralize={onToggleNeutralize}
|
||||||
onRemoveDs={onRemoveDs}
|
|
||||||
onSendToChat={onSendToChat}
|
onSendToChat={onSendToChat}
|
||||||
scopeCycleTitle={scopeCycleTitle}
|
scopeCycleTitle={scopeCycleTitle}
|
||||||
selectedKeys={selectedKeys}
|
selectedKeys={selectedKeys}
|
||||||
|
|
@ -1343,7 +1309,6 @@ interface _FeatureActionContext {
|
||||||
onCycleScope: (fds: UdbFeatureDataSource) => void;
|
onCycleScope: (fds: UdbFeatureDataSource) => void;
|
||||||
onToggleNeutralize: (fds: UdbFeatureDataSource) => void;
|
onToggleNeutralize: (fds: UdbFeatureDataSource) => void;
|
||||||
onToggleNeutralizeField: (fds: UdbFeatureDataSource, fieldName: string) => void;
|
onToggleNeutralizeField: (fds: UdbFeatureDataSource, fieldName: string) => void;
|
||||||
onRemoveFds: (fdsId: string) => void;
|
|
||||||
featureTree: MandateGroupNode[];
|
featureTree: MandateGroupNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1463,16 +1428,6 @@ const _FeatureNodeView: React.FC<_FeatureNodeViewProps> = (props) => {
|
||||||
{node.tableCount} {t('Tabellen')}
|
{node.tableCount} {t('Tabellen')}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{wildcardFds && (
|
|
||||||
<button
|
|
||||||
onClick={e => { e.stopPropagation(); ctx.onRemoveFds(wildcardFds.id); }}
|
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 10, color: '#999', padding: '0 2px', flexShrink: 0 }}
|
|
||||||
title={t('Entfernen')}
|
|
||||||
>
|
|
||||||
{'\u2715'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -1608,7 +1563,6 @@ const _FeatureItemView: React.FC<_FeatureItemViewProps> = (props) => {
|
||||||
onCycleScope={ctx.onCycleScope}
|
onCycleScope={ctx.onCycleScope}
|
||||||
onToggleNeutralize={ctx.onToggleNeutralize}
|
onToggleNeutralize={ctx.onToggleNeutralize}
|
||||||
onToggleNeutralizeField={ctx.onToggleNeutralizeField}
|
onToggleNeutralizeField={ctx.onToggleNeutralizeField}
|
||||||
onRemoveFds={ctx.onRemoveFds}
|
|
||||||
featureTree={ctx.featureTree}
|
featureTree={ctx.featureTree}
|
||||||
inheritedScope={inheritedScope}
|
inheritedScope={inheritedScope}
|
||||||
inheritedNeutralize={inheritedNeutralize}
|
inheritedNeutralize={inheritedNeutralize}
|
||||||
|
|
@ -1884,16 +1838,6 @@ const _RecordRowView: React.FC<_RecordRowViewProps> = (props) => {
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{fds && (
|
|
||||||
<button
|
|
||||||
onClick={(e) => { e.stopPropagation(); ctx.onRemoveFds(fds.id); }}
|
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 10, color: '#999', padding: '0 2px', flexShrink: 0 }}
|
|
||||||
title={t('Entfernen')}
|
|
||||||
>
|
|
||||||
{'\u2715'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={(e) => { e.stopPropagation(); ctx.onSendToChat?.(_chatPayload); }}
|
onClick={(e) => { e.stopPropagation(); ctx.onSendToChat?.(_chatPayload); }}
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -1975,7 +1919,6 @@ interface _FeatureTableRowProps {
|
||||||
onCycleScope: (fds: UdbFeatureDataSource) => void;
|
onCycleScope: (fds: UdbFeatureDataSource) => void;
|
||||||
onToggleNeutralize: (fds: UdbFeatureDataSource) => void;
|
onToggleNeutralize: (fds: UdbFeatureDataSource) => void;
|
||||||
onToggleNeutralizeField: (fds: UdbFeatureDataSource, fieldName: string) => void;
|
onToggleNeutralizeField: (fds: UdbFeatureDataSource, fieldName: string) => void;
|
||||||
onRemoveFds: (fdsId: string) => void;
|
|
||||||
featureTree: MandateGroupNode[];
|
featureTree: MandateGroupNode[];
|
||||||
inheritedScope?: string;
|
inheritedScope?: string;
|
||||||
inheritedNeutralize?: boolean;
|
inheritedNeutralize?: boolean;
|
||||||
|
|
@ -1984,7 +1927,7 @@ interface _FeatureTableRowProps {
|
||||||
const _FeatureTableRow: React.FC<_FeatureTableRowProps> = ({
|
const _FeatureTableRow: React.FC<_FeatureTableRowProps> = ({
|
||||||
featureNode, table, depth, onAddFeatureTable, onSendToChat,
|
featureNode, table, depth, onAddFeatureTable, onSendToChat,
|
||||||
featureDataSources, onCycleScope, onToggleNeutralize, onToggleNeutralizeField,
|
featureDataSources, onCycleScope, onToggleNeutralize, onToggleNeutralizeField,
|
||||||
onRemoveFds, featureTree, inheritedScope, inheritedNeutralize,
|
featureTree, inheritedScope, inheritedNeutralize,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
|
|
@ -2052,16 +1995,6 @@ const _FeatureTableRow: React.FC<_FeatureTableRowProps> = ({
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{fds && (
|
|
||||||
<button
|
|
||||||
onClick={e => { e.stopPropagation(); onRemoveFds(fds.id); }}
|
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 10, color: '#999', padding: '0 2px', flexShrink: 0 }}
|
|
||||||
title={t('Entfernen')}
|
|
||||||
>
|
|
||||||
{'\u2715'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
|
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -118,23 +118,43 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId,
|
||||||
}
|
}
|
||||||
}, [draftAppend, onDraftAppendConsumed]);
|
}, [draftAppend, onDraftAppendConsumed]);
|
||||||
|
|
||||||
|
// Persist a changed attachment list to the backend so the next chat
|
||||||
|
// reload reflects the current state. Defined early so the
|
||||||
|
// pendingAttachDsId / pendingAttachFdsId effects below can also persist
|
||||||
|
// immediately after a 💬-click or drag-drop attach.
|
||||||
|
const _persistAttachments = useCallback((dsIds: string[], fdsIds: string[]) => {
|
||||||
|
if (!instanceId || !workflowId) return;
|
||||||
|
api.patch(`/api/workspace/${instanceId}/workflows/${workflowId}/attachments`, {
|
||||||
|
dataSourceIds: dsIds,
|
||||||
|
featureDataSourceIds: fdsIds,
|
||||||
|
}).catch(err => console.warn('Failed to persist chat attachments:', err));
|
||||||
|
}, [instanceId, workflowId]);
|
||||||
|
|
||||||
|
// 💬-click or drag-drop attach: parent sets pendingAttachDsId after
|
||||||
|
// creating/finding the DataSource. Add to the chip bar AND persist
|
||||||
|
// immediately so a chat reload before the user sends a message still
|
||||||
|
// shows the chip.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pendingAttachDsId) {
|
if (!pendingAttachDsId) return;
|
||||||
setAttachedDataSourceIds(prev =>
|
setAttachedDataSourceIds(prev => {
|
||||||
prev.includes(pendingAttachDsId) ? prev : [...prev, pendingAttachDsId],
|
if (prev.includes(pendingAttachDsId)) return prev;
|
||||||
);
|
const next = [...prev, pendingAttachDsId];
|
||||||
onPendingAttachDsConsumed?.();
|
_persistAttachments(next, attachedFeatureDataSourceIds);
|
||||||
}
|
return next;
|
||||||
}, [pendingAttachDsId, onPendingAttachDsConsumed]);
|
});
|
||||||
|
onPendingAttachDsConsumed?.();
|
||||||
|
}, [pendingAttachDsId, onPendingAttachDsConsumed, _persistAttachments, attachedFeatureDataSourceIds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pendingAttachFdsId) {
|
if (!pendingAttachFdsId) return;
|
||||||
setAttachedFeatureDataSourceIds(prev =>
|
setAttachedFeatureDataSourceIds(prev => {
|
||||||
prev.includes(pendingAttachFdsId) ? prev : [...prev, pendingAttachFdsId],
|
if (prev.includes(pendingAttachFdsId)) return prev;
|
||||||
);
|
const next = [...prev, pendingAttachFdsId];
|
||||||
onPendingAttachFdsConsumed?.();
|
_persistAttachments(attachedDataSourceIds, next);
|
||||||
}
|
return next;
|
||||||
}, [pendingAttachFdsId, onPendingAttachFdsConsumed]);
|
});
|
||||||
|
onPendingAttachFdsConsumed?.();
|
||||||
|
}, [pendingAttachFdsId, onPendingAttachFdsConsumed, _persistAttachments, attachedDataSourceIds]);
|
||||||
|
|
||||||
// Rehydrate the chip-bar whenever the parent re-loads a chat (loadedNonce
|
// Rehydrate the chip-bar whenever the parent re-loads a chat (loadedNonce
|
||||||
// bumps on every loadWorkflow call). We trust the loaded IDs initially;
|
// bumps on every loadWorkflow call). We trust the loaded IDs initially;
|
||||||
|
|
@ -184,17 +204,6 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId,
|
||||||
});
|
});
|
||||||
}, [loadedNonce, featureDataSources]);
|
}, [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
|
|
||||||
// the next animation frame to coalesce rapid clicks.
|
|
||||||
const _persistAttachments = useCallback((dsIds: string[], fdsIds: string[]) => {
|
|
||||||
if (!instanceId || !workflowId) return;
|
|
||||||
api.patch(`/api/workspace/${instanceId}/workflows/${workflowId}/attachments`, {
|
|
||||||
dataSourceIds: dsIds,
|
|
||||||
featureDataSourceIds: fdsIds,
|
|
||||||
}).catch(err => console.warn('Failed to persist chat attachments:', err));
|
|
||||||
}, [instanceId, workflowId]);
|
|
||||||
|
|
||||||
const promptBeforeVoiceRef = useRef('');
|
const promptBeforeVoiceRef = useRef('');
|
||||||
const finalizedTextRef = useRef('');
|
const finalizedTextRef = useRef('');
|
||||||
const currentInterimRef = useRef('');
|
const currentInterimRef = useRef('');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue