diff --git a/src/components/UnifiedDataBar/SourcesTab.tsx b/src/components/UnifiedDataBar/SourcesTab.tsx index 4db4e7e..2370209 100644 --- a/src/components/UnifiedDataBar/SourcesTab.tsx +++ b/src/components/UnifiedDataBar/SourcesTab.tsx @@ -89,6 +89,7 @@ interface FeatureTableNode { interface SourcesTabProps { context: UdbContext; + onSourcesChanged?: () => void; } /* ─── Icons ──────────────────────────────────────────────────────────── */ @@ -115,27 +116,46 @@ const _SERVICE_ICONS: Record = { const _SOURCE_COLORS: Record = { sharepointFolder: '#0078d4', + sharepoint: '#0078d4', onedriveFolder: '#0078d4', + onedrive: '#0078d4', outlookFolder: '#0078d4', + outlook: '#0078d4', googleDriveFolder: '#34a853', + drive: '#34a853', gmailFolder: '#ea4335', + gmail: '#ea4335', ftpFolder: '#795548', + files: '#795548', + 'local:ftp': '#795548', + 'local:jira': '#1976d2', + clickup: '#7b68ee', }; function _getSourceColor(sourceType: string): string { return _SOURCE_COLORS[sourceType] || '#1976d2'; } +const _SOURCE_ICONS: Record = { + sharepointFolder: '\uD83D\uDCC1', + sharepoint: '\uD83D\uDCC1', + onedriveFolder: '\u2601\uFE0F', + onedrive: '\u2601\uFE0F', + outlookFolder: '\uD83D\uDCE7', + outlook: '\uD83D\uDCE7', + googleDriveFolder: '\uD83D\uDCC2', + drive: '\uD83D\uDCC2', + gmailFolder: '\uD83D\uDCE8', + gmail: '\uD83D\uDCE8', + ftpFolder: '\uD83D\uDD17', + files: '\uD83D\uDD17', + 'local:ftp': '\uD83D\uDD17', + 'local:jira': '\uD83D\uDD27', + clickup: '\uD83D\uDCCB', +}; + function _getSourceIcon(sourceType: string): string { - const map: Record = { - sharepointFolder: '\uD83D\uDCC1', - onedriveFolder: '\u2601\uFE0F', - outlookFolder: '\uD83D\uDCE7', - googleDriveFolder: '\uD83D\uDCC2', - gmailFolder: '\uD83D\uDCE8', - ftpFolder: '\uD83D\uDD17', - }; - return map[sourceType] || '\uD83D\uDCC1'; + return _SOURCE_ICONS[sourceType] || '\uD83D\uDCC1'; } /* ─── Scope / Neutralize constants ───────────────────────────────────── */ @@ -162,6 +182,15 @@ function _nextScope(current: string): string { return _SCOPE_ORDER[(idx + 1) % _SCOPE_ORDER.length]; } +const _SERVICE_TO_SOURCE_TYPE: Record = { + sharepoint: 'sharepointFolder', + onedrive: 'onedriveFolder', + outlook: 'outlookFolder', + drive: 'googleDriveFolder', + gmail: 'gmailFolder', + files: 'ftpFolder', +}; + /* ─── Tree helpers ───────────────────────────────────────────────────── */ function _mapTree(nodes: TreeNode[], key: string, updater: (n: TreeNode) => TreeNode): TreeNode[] { @@ -301,7 +330,7 @@ function _Spinner(): React.ReactElement { /* ─── Component ──────────────────────────────────────────────────────── */ -const SourcesTab: React.FC = ({ context }) => { +const SourcesTab: React.FC = ({ context, onSourcesChanged }) => { const instanceId = context.instanceId; /* ── Active sources (fetched internally) ── */ @@ -444,22 +473,15 @@ const SourcesTab: React.FC = ({ context }) => { if (!node.service || !node.connectionId) return; setAddingPath(node.key); try { - const sourceTypeMap: Record = { - sharepoint: 'sharepointFolder', - onedrive: 'onedriveFolder', - outlook: 'outlookFolder', - drive: 'googleDriveFolder', - gmail: 'gmailFolder', - files: 'ftpFolder', - }; await api.post(`/api/workspace/${instanceId}/datasources`, { connectionId: node.connectionId, - sourceType: sourceTypeMap[node.service] || node.service, + sourceType: _SERVICE_TO_SOURCE_TYPE[node.service] || node.service, path: node.path || '/', label: node.label, displayPath: node.displayPath || node.label, }); _fetchDataSources(); + onSourcesChanged?.(); } catch (err) { console.error('Failed to add data source:', err); } finally { @@ -472,15 +494,19 @@ const SourcesTab: React.FC = ({ context }) => { try { await api.delete(`/api/workspace/${instanceId}/datasources/${dsId}`); _fetchDataSources(); + onSourcesChanged?.(); } catch (err) { console.error('Failed to remove data source:', err); } }, [instanceId, _fetchDataSources]); /* ── 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; return dataSources.some(ds => - ds.connectionId === connectionId && ds.path === (path || '/'), + ds.connectionId === connectionId && + ds.path === (path || '/') && + (!expectedSourceType || ds.sourceType === expectedSourceType), ); }, [dataSources]); @@ -617,6 +643,7 @@ const SourcesTab: React.FC = ({ context }) => { label: table.label?.en || table.label?.de || table.tableName, }); _fetchFeatureDataSources(); + onSourcesChanged?.(); } catch (err) { console.error('Failed to add feature data source:', err); } finally { @@ -629,6 +656,7 @@ const SourcesTab: React.FC = ({ context }) => { try { await api.delete(`/api/workspace/${instanceId}/feature-datasources/${fdsId}`); _fetchFeatureDataSources(); + onSourcesChanged?.(); } catch (err) { console.error('Failed to remove feature data source:', err); } @@ -651,7 +679,11 @@ const SourcesTab: React.FC = ({ context }) => {
Active Personal Sources
- {dataSources.map(ds => { + {[...dataSources].sort((a, b) => { + const aKey = `${a.sourceType}|${a.label || a.path || ''}`; + const bKey = `${b.sourceType}|${b.label || b.path || ''}`; + return aKey.localeCompare(bKey); + }).map(ds => { const connColor = _getSourceColor(ds.sourceType); const connNode = tree.find(n => n.connectionId === ds.connectionId); const connLabel = connNode?.label || ds.connectionId; @@ -751,7 +783,7 @@ const SourcesTab: React.FC = ({ context }) => {
Active Feature Sources
- {featureDataSources.map(fds => { + {[...featureDataSources].sort((a, b) => (a.label || a.tableName || '').localeCompare(b.label || b.tableName || '')).map(fds => { const meta = _findFeatureInstanceMeta(featureTree, fds.featureInstanceId); const fdsConnLabel = meta?.instanceLabel || fds.tableName; return ( diff --git a/src/components/UnifiedDataBar/UnifiedDataBar.tsx b/src/components/UnifiedDataBar/UnifiedDataBar.tsx index 8a6ddc9..75bf641 100644 --- a/src/components/UnifiedDataBar/UnifiedDataBar.tsx +++ b/src/components/UnifiedDataBar/UnifiedDataBar.tsx @@ -25,6 +25,7 @@ interface UnifiedDataBarProps { onDeleteChat?: (chatId: string) => void; onChatDragStart?: (chatId: string, event: React.DragEvent) => void; onFileSelect?: (fileId: string) => void; + onSourcesChanged?: () => void; className?: string; } @@ -46,6 +47,7 @@ const UnifiedDataBar: React.FC = ({ onDeleteChat, onChatDragStart, onFileSelect, + onSourcesChanged, className, }) => { const visibleTabs = (['chats', 'files', 'sources'] as UdbTab[]).filter( @@ -91,7 +93,7 @@ const UnifiedDataBar: React.FC = ({ /> )} {currentTab === 'sources' && !hideTabs?.includes('sources') && ( - + )} diff --git a/src/pages/views/workspace/WorkspaceInput.tsx b/src/pages/views/workspace/WorkspaceInput.tsx index 7138661..affed12 100644 --- a/src/pages/views/workspace/WorkspaceInput.tsx +++ b/src/pages/views/workspace/WorkspaceInput.tsx @@ -543,7 +543,7 @@ export const WorkspaceInput: React.FC = ({ {uploading ? '...' : '+'} - {dataSources.length > 0 && ( + {(dataSources.length > 0 || featureDataSources.length > 0) && (