- {/* ---- Step 0: Connector ---- */}
- {state.step === 0 && (
+ {/* ---- Step: Connector ---- */}
+ {state.currentStep === 'connector' && (
Anbieter wählen
Welchen Dienst möchtest du verbinden?
- {(['google', 'msft', 'clickup'] as ConnectorType[]).map(type => (
+ {(['google', 'msft', 'clickup', 'infomaniak'] as ConnectorType[]).map(type => (
setConnector(type)}
+ onClick={() => selectConnector(type)}
>
{CONNECTOR_ICONS[type]}
{CONNECTOR_LABELS[type]}
@@ -260,151 +164,103 @@ export const AddConnectionWizard: React.FC = ({
)}
- {/* ---- Step 1: Consent ---- */}
- {state.step === 1 && (
+ {/* ---- Step: Consent ---- */}
+ {state.currentStep === 'consent' && (
-
Wissensdatenbank
Möchtest du Inhalte aus dieser Verbindung in deine persönliche
Wissensdatenbank aufnehmen, damit die KI beim Antworten auf Informationen
- aus{' '}
- {state.connector ? CONNECTOR_LABELS[state.connector] : 'diesem Dienst'}{' '}
- zurückgreifen kann?
+ aus {state.connector ? CONNECTOR_LABELS[state.connector] : 'diesem Dienst'} zurückgreifen kann?
- Du kannst diese Einstellung später in den Verbindungsdetails ändern.
+ Du kannst dies später jederzeit in der UDB pro Datenquelle steuern.
+
+
+ setConsent(true)}>
+ Ja, aktivieren
+
+ setConsent(false)}>
+ Nein, überspringen
+
+
+
+ Zurück
+
+
+ )}
+
+ {/* ---- Step: MSFT Admin Consent ---- */}
+ {state.currentStep === 'msftAdminConsent' && (
+
+
+
+
+
Organisations-Zustimmung (optional)
+
+ Falls du Mandant-Administrator bist, kannst du jetzt für deine ganze Organisation zustimmen.
+ So müssen andere Benutzer nicht einzeln bestätigen.
+
+
+ Wenn du kein Admin bist oder dies später tun möchtest, überspringe diesen Schritt.
setKnowledgeEnabled(true)}
+ onClick={() => { onMsftAdminConsent?.(); setState(s => ({ ...s, adminConsentDone: true })); goNext(); }}
>
- Ja, aufnehmen
+ Admin-Zustimmung erteilen
- setKnowledgeEnabled(false)}
- >
- Nein, überspringen
+
+ Überspringen
- setStep(0)}>
- Zurück
-
+ Zurück
)}
- {/* ---- Step 2: Preferences ---- */}
- {state.step === 2 && (
+ {/* ---- Step: Infomaniak PAT ---- */}
+ {state.currentStep === 'infomaniakPat' && (
-
Einstellungen
-
- Steuere, welche Inhalte und in welcher Form sie indexiert werden.
+
Infomaniak Personal Access Token
+
+ Erstelle einen Personal Access Token in deinem Infomaniak-Konto und füge ihn hier ein.
-
-
-
-
- Anonymisierung vor dem Indexieren
- updatePref('neutralizeBeforeEmbed', e.target.checked)}
- className={styles.prefCheck}
- />
-
-
- Persönliche Daten (Namen, E-Mail-Adressen) werden vor dem Speichern ersetzt.
-
-
-
- {(state.connector === 'google' || state.connector === 'msft') && (
- <>
-
-
- E-Mail-Inhalt
- updatePref('mailContentDepth', e.target.value as any)}
- className={styles.prefSelect}
- >
- Nur Metadaten (Betreff, Absender, Datum)
- Vorschautext (ca. 250 Zeichen)
- Vollständiger Text
-
-
-
-
-
- E-Mail-Anhänge indexieren
- updatePref('mailIndexAttachments', e.target.checked)}
- className={styles.prefCheck}
- />
-
-
- >
- )}
-
- {state.connector === 'clickup' && (
-
-
- Aufgaben-Inhalt
- updatePref('clickupScope', e.target.value as any)}
- className={styles.prefSelect}
- >
- Nur Aufgabentitel
- Titel + Beschreibung
- Titel + Beschreibung + Kommentare
-
-
-
- )}
-
-
-
- Zeitfenster (Tage)
- updatePref('maxAgeDays', parseInt(e.target.value, 10) || 0)}
- className={styles.prefNumber}
- />
-
-
0 = kein Limit
-
-
+
setState(s => ({ ...s, infomaniakToken: e.target.value }))}
+ className={styles.patInput}
+ autoFocus
+ />
- setStep(1)}>
- Zurück
-
- setStep(3)}>
- Weiter
+ Zurück
+
+ {isConnecting ? 'Verbinden…' : 'Verbinden'}
+ {!isConnecting && }
)}
- {/* ---- Step 3: Summary ---- */}
- {state.step === 3 && (
+ {/* ---- Step: Connect ---- */}
+ {state.currentStep === 'connect' && (
-
Zusammenfassung
+
Verbindung herstellen
Anbieter
- {CONNECTOR_ICONS[state.connector!]}
+ {state.connector && CONNECTOR_ICONS[state.connector]}
{state.connector ? CONNECTOR_LABELS[state.connector] : '—'}
@@ -414,96 +270,13 @@ export const AddConnectionWizard: React.FC
= ({
{state.knowledgeEnabled ? '✓ Aktiv' : '✗ Nicht aktiv'}
- {state.knowledgeEnabled && (
- <>
-
- Anonymisierung
-
- {state.prefs.neutralizeBeforeEmbed ? 'Ja' : 'Nein'}
-
-
- {(state.connector === 'google' || state.connector === 'msft') && (
-
- E-Mail-Tiefe
-
- {{ metadata: 'Nur Metadaten', snippet: 'Vorschautext', full: 'Volltext' }[
- state.prefs.mailContentDepth ?? 'full'
- ] ?? state.prefs.mailContentDepth}
-
-
- )}
- {state.connector === 'clickup' && (
-
- Aufgaben-Inhalt
-
- {{
- titles: 'Nur Titel',
- title_description: 'Titel + Beschreibung',
- with_comments: 'Titel + Beschreibung + Kommentare',
- }[state.prefs.clickupScope ?? 'title_description'] ?? state.prefs.clickupScope}
-
-
- )}
-
- Zeitfenster
-
- {state.prefs.maxAgeDays ? `${state.prefs.maxAgeDays} Tage` : 'Unbegrenzt'}
-
-
- >
- )}
-
- {/* Cost estimate — only shown when knowledge ingestion is enabled */}
- {state.knowledgeEnabled && (() => {
- const est = computeCostEstimate(state.connector, state.prefs);
- if (!est) return null;
- return (
-
-
-
-
Geschätzte Kosten (erster Sync)
-
-
-
- Embedding
-
- {est.embeddingLow} – {est.embeddingHigh}
-
-
- {est.neutralizationLow && (
-
- Anonymisierung (Private LLM)
-
- {est.neutralizationLow} – {est.neutralizationHigh}
-
-
- )}
-
-
- {est.neutralizationLow && (
-
- ⚠ Anonymisierung ist der Hauptkostentreiber (CHF 0.01 pro LLM-Aufruf, on-premise).
-
- )}
-
{est.note}
-
-
- );
- })()}
-
-
setStep(state.knowledgeEnabled ? 2 : 1)}
- >
- Zurück
-
+
Zurück
{isConnecting ? 'Verbinden…' : `Mit ${state.connector ? CONNECTOR_LABELS[state.connector] : '…'} verbinden`}
diff --git a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.module.css b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.module.css
index 3c1ab0f..db2673e 100644
--- a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.module.css
+++ b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.module.css
@@ -101,9 +101,8 @@
flex-direction: column;
gap: 0.5rem;
min-width: 0;
- /* Share remaining viewport among expanded groups; scroll when many groups */
- flex: 1 1 280px;
- min-height: 0;
+ flex: 1 1 400px;
+ min-height: 350px;
}
.groupSectionCollapsed {
diff --git a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx
index f8b39ec..352f8e8 100644
--- a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx
+++ b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx
@@ -681,7 +681,7 @@ export function FormGeneratorTable>({
resizable = true,
pagination = true,
pageSize = 10,
- pageSizeOptions = [10, 25, 50, 100, 500],
+ pageSizeOptions = [10, 25, 50, 100, 500, 1000, 2000, 10000],
showPageSizeSelector = true,
onRowClick,
onRowSelect,
@@ -740,13 +740,16 @@ export function FormGeneratorTable>({
const [activeViewKey, setActiveViewKey] = useState(null);
const [activeViewId, setActiveViewId] = useState(null);
const [groupByLevels, setGroupByLevels] = useState([]);
- const useSectionsGroupLayout =
- tableGroupLayoutMode === 'sections' &&
+ const [groupLayoutMode, setGroupLayoutMode] = useState<'inline' | 'sections'>(tableGroupLayoutMode ?? 'inline');
+
+ const canUseSections =
!!tableContextKey &&
- groupByLevels.length === 1 &&
+ groupByLevels.length > 0 &&
typeof hookDataProp?.fetchGroupSectionSummaries === 'function' &&
typeof hookDataProp?.refetchForSection === 'function';
+ const useSectionsGroupLayout = canUseSections && groupLayoutMode === 'sections';
+
const [sectionSummaries, setSectionSummaries] = useState<
Array<{ value: string | null; label: string; totalCount: number }>
>([]);
@@ -1360,6 +1363,7 @@ export function FormGeneratorTable>({
viewKey: activeViewKey,
groupField: spec.field,
groupDirection: spec.direction || 'asc',
+ groupByLevels: groupLevelsToApiPayload(groupByLevels),
});
if (!cancelled) setSectionSummaries(Array.isArray(list) ? list : []);
} catch (e) {
@@ -2750,6 +2754,27 @@ export function FormGeneratorTable>({
onUpdateViewGrouping={(id, levels) => void handleUpdateViewGrouping(id, levels)}
onDeleteView={(id) => void handleDeleteView(id)}
onReloadViews={() => void reloadViews()}
+ canUseSections={canUseSections}
+ groupLayoutMode={groupLayoutMode}
+ onGroupLayoutModeChange={(mode) => {
+ setGroupLayoutMode(mode);
+ setCollapsedGroups(new Set());
+ setCollapsedSectionKeys(new Set());
+ setCurrentPage(1);
+ }}
+ hasGroupBands={!!effectiveGroupLayout && effectiveGroupLayout.bands.length > 0}
+ onCollapseAll={() => {
+ if (effectiveGroupLayout) {
+ setCollapsedGroups(new Set(effectiveGroupLayout.bands.map((b) => b.path.join('///'))));
+ }
+ if (useSectionsGroupLayout) {
+ setCollapsedSectionKeys(new Set(sectionSummaries.map((g) => g.value === null || g.value === undefined ? '__empty__' : String(g.value))));
+ }
+ }}
+ onExpandAll={() => {
+ setCollapsedGroups(new Set());
+ setCollapsedSectionKeys(new Set());
+ }}
/>
)}
@@ -3341,13 +3366,23 @@ export function FormGeneratorTable>({
)}
{sectionSummaries.map((g) => {
- const field = groupByLevels[0].field;
- const sectionFilter: Record
= {
- [field]: g.value === null || g.value === undefined ? null : g.value,
- };
+ const isMultiLevel = groupByLevels.length > 1 && (g as any).filters;
+ const sectionFilter: Record = isMultiLevel
+ ? (g as any).filters
+ : { [groupByLevels[0].field]: g.value === null || g.value === undefined ? null : g.value };
+ const groupFields = isMultiLevel
+ ? groupByLevels.map((l) => l.field)
+ : [groupByLevels[0].field];
const sk =
g.value === null || g.value === undefined ? '__empty__' : String(g.value);
const sectionCollapsed = collapsedSectionKeys.has(sk);
+ const groupFieldSet = new Set(groupFields);
+ const sectionColumns = providedColumns.map((col: any) =>
+ groupFieldSet.has(col.key) ? { ...col, filterable: false } : col,
+ );
+ const sectionInitialFilters = Object.fromEntries(
+ Object.entries(filters).filter(([k]) => !groupFieldSet.has(k)),
+ );
return (
>({
{!sectionCollapsed && (
- key={`${sk}-r${refreshNonce}-${JSON.stringify(filters)}-${JSON.stringify(sortConfigs)}-${activeViewKey ?? ''}`}
+ key={`${sk}-r${refreshNonce}-${JSON.stringify(sectionInitialFilters)}-${JSON.stringify(sortConfigs)}-${activeViewKey ?? ''}`}
className={styles.groupSectionTableWrap}
- columns={providedColumns}
+ columns={sectionColumns}
data={[]}
searchable={false}
filterable={filterable}
@@ -3415,7 +3450,7 @@ export function FormGeneratorTable>({
localDataMode
viewKeyForQueries={activeViewKey}
initialSearchTerm={debouncedSearchTerm}
- initialFilters={filters}
+ initialFilters={sectionInitialFilters}
initialSort={sortConfigs}
apiEndpoint={apiEndpoint}
csvExportQueryParams={hookDataProp?.csvExportQueryParams}
@@ -3427,13 +3462,13 @@ export function FormGeneratorTable>({
if (!hookDataProp?.refetchForSection) {
return { items: [], pagination: null };
}
- return hookDataProp.refetchForSection(p, sectionFilter, filters);
+ return hookDataProp.refetchForSection(p, sectionFilter, sectionInitialFilters);
},
...(hookDataProp?.fetchFilterValues && typeof hookDataProp.fetchFilterValues === 'function'
? {
fetchFilterValues: async (columnKey: string, crossFilters?: Record) => {
const merged: Record = {
- ...filters,
+ ...sectionInitialFilters,
...(crossFilters || {}),
...sectionFilter,
};
diff --git a/src/components/FormGenerator/TableViewsBar/TableViewsBar.module.css b/src/components/FormGenerator/TableViewsBar/TableViewsBar.module.css
index 090715d..82696c2 100644
--- a/src/components/FormGenerator/TableViewsBar/TableViewsBar.module.css
+++ b/src/components/FormGenerator/TableViewsBar/TableViewsBar.module.css
@@ -153,6 +153,51 @@
white-space: nowrap;
}
+.layoutToggle {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ border-radius: 8px;
+ border: 1px solid var(--color-border, #cbd5e1);
+ background: var(--color-bg, #fff);
+ color: var(--color-text, #0f172a);
+ cursor: pointer;
+ font-size: 18px;
+ transition: background 0.15s, border-color 0.15s;
+}
+
+.layoutToggle:hover {
+ background: var(--bg-hover, rgba(15, 23, 42, 0.04));
+ border-color: var(--color-primary, #64748b);
+}
+
+.collapseExpandGroup {
+ display: inline-flex;
+ gap: 2px;
+}
+
+.collapseBtn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 30px;
+ height: 30px;
+ border-radius: 6px;
+ border: 1px solid var(--color-border, #cbd5e1);
+ background: var(--color-bg, #fff);
+ color: var(--text-secondary, #94a3b8);
+ cursor: pointer;
+ font-size: 16px;
+ transition: background 0.15s, color 0.15s;
+}
+
+.collapseBtn:hover {
+ background: var(--bg-hover, rgba(15, 23, 42, 0.04));
+ color: var(--color-text, #0f172a);
+}
+
.viewBlock {
display: flex;
flex-wrap: wrap;
diff --git a/src/components/FormGenerator/TableViewsBar/TableViewsBar.tsx b/src/components/FormGenerator/TableViewsBar/TableViewsBar.tsx
index e59743c..80ac03a 100644
--- a/src/components/FormGenerator/TableViewsBar/TableViewsBar.tsx
+++ b/src/components/FormGenerator/TableViewsBar/TableViewsBar.tsx
@@ -1,5 +1,7 @@
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
import { FaLayerGroup, FaTrash } from 'react-icons/fa';
+import { TbLayoutList, TbLayoutRows } from 'react-icons/tb';
+import { FiChevronsDown, FiChevronsUp } from 'react-icons/fi';
import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from './TableViewsBar.module.css';
@@ -30,6 +32,12 @@ export interface TableViewsBarProps {
onUpdateViewGrouping: (viewId: string, levels: GroupByLevelSpec[]) => void | Promise;
onDeleteView?: (viewId: string) => void | Promise;
onReloadViews: () => void;
+ canUseSections?: boolean;
+ groupLayoutMode?: 'inline' | 'sections';
+ onGroupLayoutModeChange?: (mode: 'inline' | 'sections') => void;
+ hasGroupBands?: boolean;
+ onCollapseAll?: () => void;
+ onExpandAll?: () => void;
}
function slugify(name: string): string {
@@ -74,6 +82,12 @@ export function TableViewsBar({
onUpdateViewGrouping,
onDeleteView,
onReloadViews,
+ canUseSections,
+ groupLayoutMode,
+ onGroupLayoutModeChange,
+ hasGroupBands,
+ onCollapseAll,
+ onExpandAll,
}: TableViewsBarProps) {
const { t } = useLanguage();
const [groupMenuOpen, setGroupMenuOpen] = useState(false);
@@ -249,6 +263,41 @@ export function TableViewsBar({
: `${t('Aktiv')}: ${summary}`}
+ {canUseSections && groupByLevels.length > 0 && onGroupLayoutModeChange && (
+ onGroupLayoutModeChange(groupLayoutMode === 'inline' ? 'sections' : 'inline')}
+ >
+ {groupLayoutMode === 'inline' ? : }
+
+ )}
+
+ {hasGroupBands && onCollapseAll && onExpandAll && (
+
+
+
+
+
+
+
+
+ )}
+
{t('Ansicht')}
{
+ const { t } = useLanguage();
+ const { request } = useApiRequest();
+ const [jobs, setJobs] = useState<_RagJob[]>([]);
+ const [expanded, setExpanded] = useState(false);
+ const timerRef = useRef | null>(null);
+
+ const _fetchJobs = useCallback(async () => {
+ try {
+ const result = await request({ url: '/api/rag/inventory/jobs', method: 'get' });
+ setJobs(Array.isArray(result) ? result : []);
+ } catch {
+ setJobs([]);
+ }
+ }, [request]);
+
+ useEffect(() => {
+ _fetchJobs();
+ timerRef.current = setInterval(_fetchJobs, _POLL_INTERVAL_MS);
+ return () => { if (timerRef.current) clearInterval(timerRef.current); };
+ }, [_fetchJobs]);
+
+ if (jobs.length === 0) return null;
+
+ return (
+
+
setExpanded(prev => !prev)}
+ title={t('RAG-Indexierung aktiv')}
+ >
+
+
+ {jobs.length} {jobs.length === 1 ? t('Job') : t('Jobs')}
+
+
+
+ {expanded && (
+
+
+ {t('Aktive RAG-Jobs')}
+
+ {jobs.map(job => (
+
+ {job.connectionLabel || job.jobType}
+
+ {job.progress != null ? `${Math.round(job.progress * 100)}%` : '...'}
+
+
+ ))}
+
+ )}
+
+ );
+};
diff --git a/src/components/UnifiedDataBar/SourcesTab.tsx b/src/components/UnifiedDataBar/SourcesTab.tsx
index 33605ad..e287097 100644
--- a/src/components/UnifiedDataBar/SourcesTab.tsx
+++ b/src/components/UnifiedDataBar/SourcesTab.tsx
@@ -42,6 +42,7 @@ interface UdbDataSource {
displayPath?: string;
scope: string;
neutralize: boolean;
+ ragIndexEnabled?: boolean;
}
interface UdbFeatureDataSource {
@@ -689,6 +690,17 @@ const SourcesTab: React.FC = ({ context, onSourcesChanged, onSe
}
}, []);
+ /* ── RAG-Index toggle (personal data source, optimistic) ── */
+ const _togglePersonalRagIndex = useCallback(async (ds: UdbDataSource) => {
+ const newValue = !ds.ragIndexEnabled;
+ setDataSources(prev => prev.map(d => d.id === ds.id ? { ...d, ragIndexEnabled: newValue } : d));
+ try {
+ await api.patch(`/api/datasources/${ds.id}/rag-index`, { ragIndexEnabled: newValue });
+ } catch {
+ setDataSources(prev => prev.map(d => d.id === ds.id ? { ...d, ragIndexEnabled: ds.ragIndexEnabled } : d));
+ }
+ }, []);
+
/* ── Scope change (feature data source, optimistic) ── */
const _cycleFeatureScope = useCallback(async (fds: UdbFeatureDataSource) => {
const newScope = _nextScope(fds.scope);
@@ -1018,6 +1030,7 @@ const SourcesTab: React.FC = ({ context, onSourcesChanged, onSe
dataSources={dataSources}
onCycleScope={_cyclePersonalScope}
onToggleNeutralize={_togglePersonalNeutralize}
+ onToggleRagIndex={_togglePersonalRagIndex}
onSendToChat={_sendNodeToChat}
scopeCycleTitle={_scopeCycleTitle}
selectedKeys={selectedKeys}
@@ -1105,18 +1118,20 @@ interface _TreeNodeViewProps {
dataSources: UdbDataSource[];
onCycleScope: (ds: UdbDataSource) => void;
onToggleNeutralize: (ds: UdbDataSource) => void;
+ onToggleRagIndex: (ds: UdbDataSource) => void;
onSendToChat?: (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => void;
scopeCycleTitle: (scope: string) => string;
selectedKeys: Set;
onSelect: (node: TreeNode, e: React.MouseEvent) => void;
inheritedScope?: string;
inheritedNeutralize?: boolean;
+ inheritedRagIndex?: boolean;
}
const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
node, depth, onToggle, onEnsureDs, isAdded, addingPath,
- dataSources, onCycleScope, onToggleNeutralize, onSendToChat, scopeCycleTitle,
- selectedKeys, onSelect, inheritedScope, inheritedNeutralize,
+ dataSources, onCycleScope, onToggleNeutralize, onToggleRagIndex, onSendToChat, scopeCycleTitle,
+ selectedKeys, onSelect, inheritedScope, inheritedNeutralize, inheritedRagIndex,
}) => {
const { t } = useLanguage();
const [hovered, setHovered] = useState(false);
@@ -1128,8 +1143,10 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
const effectiveScope = ds?.scope ?? inheritedScope;
const effectiveNeutralize = ds?.neutralize ?? inheritedNeutralize ?? false;
+ const effectiveRagIndex = ds?.ragIndexEnabled ?? inheritedRagIndex ?? false;
const childInheritedScope = ds?.scope ?? inheritedScope;
const childInheritedNeutralize = ds?.neutralize ?? inheritedNeutralize;
+ const childInheritedRagIndex = ds?.ragIndexEnabled ?? inheritedRagIndex;
const _dragPayload = {
connectionId: node.connectionId,
@@ -1261,6 +1278,24 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
>
{'\uD83D\uDD12'}
+ {
+ e.stopPropagation();
+ if (ds) { onToggleRagIndex(ds); return; }
+ const newId = await onEnsureDs(node);
+ if (newId) {
+ try { await api.patch(`/api/datasources/${newId}/rag-index`, { ragIndexEnabled: !effectiveRagIndex }); } catch {}
+ }
+ }}
+ style={{
+ background: 'none', border: 'none', cursor: 'pointer',
+ fontSize: 12, padding: '0 2px', lineHeight: 1, flexShrink: 0, width: 22, textAlign: 'center',
+ opacity: (ds?.ragIndexEnabled ?? effectiveRagIndex) ? 1 : 0.35,
+ }}
+ title={(ds?.ragIndexEnabled ?? effectiveRagIndex) ? t('RAG-Indexierung an') : t('RAG-Indexierung aus')}
+ >
+ {'\uD83E\uDDE0'}
+
@@ -1278,12 +1313,14 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
dataSources={dataSources}
onCycleScope={onCycleScope}
onToggleNeutralize={onToggleNeutralize}
+ onToggleRagIndex={onToggleRagIndex}
onSendToChat={onSendToChat}
scopeCycleTitle={scopeCycleTitle}
selectedKeys={selectedKeys}
onSelect={onSelect}
inheritedScope={childInheritedScope}
inheritedNeutralize={childInheritedNeutralize}
+ inheritedRagIndex={childInheritedRagIndex}
/>
))}
diff --git a/src/config/pageRegistry.tsx b/src/config/pageRegistry.tsx
index ea22f44..51ccfbd 100644
--- a/src/config/pageRegistry.tsx
+++ b/src/config/pageRegistry.tsx
@@ -53,6 +53,7 @@ export const PAGE_ICONS: Record = {
'page.system.billingAdmin': ,
'page.system.statistics': ,
'page.system.automations': ,
+ 'page.system.ragInventory': ,
// Billing pages (legacy compat)
'page.billing.dashboard': ,
diff --git a/src/hooks/useConnections.ts b/src/hooks/useConnections.ts
index 299480c..c6681d3 100644
--- a/src/hooks/useConnections.ts
+++ b/src/hooks/useConnections.ts
@@ -101,17 +101,15 @@ export function useConnections() {
viewKey?: string | null;
groupField: string;
groupDirection?: 'asc' | 'desc';
+ groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: string }>;
}) => {
+ const levels = base.groupByLevels?.length
+ ? base.groupByLevels
+ : [{ field: base.groupField, nullLabel: '—', direction: base.groupDirection || 'asc' }];
const pObj: Record = {
page: 1,
pageSize: 25,
- groupByLevels: [
- {
- field: base.groupField,
- nullLabel: '—',
- direction: base.groupDirection || 'asc',
- },
- ],
+ groupByLevels: levels,
};
if (base.search) (pObj as { search?: string }).search = base.search;
if (base.filters && Object.keys(base.filters).length) (pObj as { filters: typeof base.filters }).filters = base.filters;
diff --git a/src/hooks/useFiles.ts b/src/hooks/useFiles.ts
index a770388..704d778 100644
--- a/src/hooks/useFiles.ts
+++ b/src/hooks/useFiles.ts
@@ -149,17 +149,15 @@ export function useUserFiles() {
viewKey?: string | null;
groupField: string;
groupDirection?: 'asc' | 'desc';
+ groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: string }>;
}) => {
+ const levels = base.groupByLevels?.length
+ ? base.groupByLevels
+ : [{ field: base.groupField, nullLabel: '—', direction: base.groupDirection || 'asc' }];
const pObj: Record = {
page: 1,
pageSize: 25,
- groupByLevels: [
- {
- field: base.groupField,
- nullLabel: '—',
- direction: base.groupDirection || 'asc',
- },
- ],
+ groupByLevels: levels,
};
if (base.search) (pObj as { search?: string }).search = base.search;
if (base.filters && Object.keys(base.filters).length) (pObj as { filters: typeof base.filters }).filters = base.filters;
diff --git a/src/hooks/usePrompts.ts b/src/hooks/usePrompts.ts
index 26cef0b..004ed29 100644
--- a/src/hooks/usePrompts.ts
+++ b/src/hooks/usePrompts.ts
@@ -98,17 +98,15 @@ export function usePrompts() {
viewKey?: string | null;
groupField: string;
groupDirection?: 'asc' | 'desc';
+ groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: string }>;
}) => {
+ const levels = base.groupByLevels?.length
+ ? base.groupByLevels
+ : [{ field: base.groupField, nullLabel: '—', direction: base.groupDirection || 'asc' }];
const pObj: Record = {
page: 1,
pageSize: 25,
- groupByLevels: [
- {
- field: base.groupField,
- nullLabel: '—',
- direction: base.groupDirection || 'asc',
- },
- ],
+ groupByLevels: levels,
};
if (base.search) (pObj as { search?: string }).search = base.search;
if (base.filters && Object.keys(base.filters).length) (pObj as { filters: typeof base.filters }).filters = base.filters;
diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx
index 880c702..bc4e2c9 100644
--- a/src/layouts/MainLayout.tsx
+++ b/src/layouts/MainLayout.tsx
@@ -14,6 +14,7 @@ import { WorkspaceKeepAlive } from '../pages/views/workspace/WorkspaceKeepAlive'
import { CommcoachKeepAlive } from '../pages/views/commcoach/CommcoachKeepAlive';
import { GraphicalEditorKeepAlive } from '../pages/views/graphicalEditor/GraphicalEditorKeepAlive';
import { AdminLanguagesKeepAlive } from '../pages/admin/AdminLanguagesKeepAlive';
+import { RagRunningBadge } from '../components/RagRunningBadge/RagRunningBadge';
import styles from './MainLayout.module.css';
import { useLanguage } from '../providers/language/LanguageContext';
@@ -132,6 +133,8 @@ const MainLayoutInner: React.FC = () => {
+
+
);
};
diff --git a/src/pages/FeatureView.tsx b/src/pages/FeatureView.tsx
index 826b195..cc0b8cb 100644
--- a/src/pages/FeatureView.tsx
+++ b/src/pages/FeatureView.tsx
@@ -35,7 +35,6 @@ import { GraphicalEditorTemplatesPage } from './views/graphicalEditor/GraphicalE
import { WorkspacePage } from './views/workspace/WorkspacePage';
import { WorkspaceEditorPage } from './views/workspace/WorkspaceEditorPage';
import { WorkspaceSettingsPage } from './views/workspace/WorkspaceSettingsPage';
-import { WorkspaceRagInsightsPage } from './views/workspace/WorkspaceRagInsightsPage';
// Teamsbot Views
import { TeamsbotDashboardView } from './views/teamsbot/TeamsbotDashboardView';
@@ -155,7 +154,6 @@ const VIEW_COMPONENTS: Record