teamsbot auth fixes
This commit is contained in:
parent
2ee08c314b
commit
0d8e6501d3
4 changed files with 95 additions and 57 deletions
|
|
@ -58,6 +58,13 @@ function _buildChildMap<T>(nodes: TreeNode<T>[]): Map<string | '__root__', TreeN
|
||||||
if (list) list.push(n);
|
if (list) list.push(n);
|
||||||
else map.set(key, [n]);
|
else map.set(key, [n]);
|
||||||
}
|
}
|
||||||
|
for (const [, children] of map) {
|
||||||
|
children.sort((a, b) => {
|
||||||
|
if (a.type === 'folder' && b.type !== 'folder') return -1;
|
||||||
|
if (a.type !== 'folder' && b.type === 'folder') return 1;
|
||||||
|
return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
|
||||||
|
});
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -651,10 +658,14 @@ export function FormGeneratorTree<T = any>({
|
||||||
const _handleCycleScope = useCallback(
|
const _handleCycleScope = useCallback(
|
||||||
async (node: TreeNode<T>) => {
|
async (node: TreeNode<T>) => {
|
||||||
const newScope = _nextScope(node.scope);
|
const newScope = _nextScope(node.scope);
|
||||||
await provider.patchScope?.([node.id], newScope);
|
const isFolder = node.type === 'folder';
|
||||||
setNodes((prev) =>
|
await provider.patchScope?.([node.id], newScope, isFolder);
|
||||||
prev.map((n) => (n.id === node.id ? { ...n, scope: newScope } : n)),
|
setNodes((prev) => {
|
||||||
);
|
if (!isFolder) return prev.map((n) => (n.id === node.id ? { ...n, scope: newScope } : n));
|
||||||
|
const descendantIds = new Set(_collectDescendantIds(node.id, prev));
|
||||||
|
descendantIds.add(node.id);
|
||||||
|
return prev.map((n) => descendantIds.has(n.id) ? { ...n, scope: newScope } : n);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[provider],
|
[provider],
|
||||||
);
|
);
|
||||||
|
|
@ -663,9 +674,12 @@ export function FormGeneratorTree<T = any>({
|
||||||
async (node: TreeNode<T>) => {
|
async (node: TreeNode<T>) => {
|
||||||
const newValue = !node.neutralize;
|
const newValue = !node.neutralize;
|
||||||
await provider.patchNeutralize?.([node.id], newValue);
|
await provider.patchNeutralize?.([node.id], newValue);
|
||||||
setNodes((prev) =>
|
setNodes((prev) => {
|
||||||
prev.map((n) => (n.id === node.id ? { ...n, neutralize: newValue } : n)),
|
if (node.type !== 'folder') return prev.map((n) => (n.id === node.id ? { ...n, neutralize: newValue } : n));
|
||||||
);
|
const descendantIds = new Set(_collectDescendantIds(node.id, prev));
|
||||||
|
descendantIds.add(node.id);
|
||||||
|
return prev.map((n) => descendantIds.has(n.id) ? { ...n, neutralize: newValue } : n);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[provider],
|
[provider],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,7 @@ async function _loadServices(instanceId: string, connectionId: string): Promise<
|
||||||
service: s.service,
|
service: s.service,
|
||||||
path: '/',
|
path: '/',
|
||||||
displayPath: s.label || s.service,
|
displayPath: s.label || s.service,
|
||||||
}));
|
})).sort((a: TreeNode, b: TreeNode) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _browseService(
|
async function _browseService(
|
||||||
|
|
@ -375,6 +375,10 @@ async function _browseService(
|
||||||
path: entry.path,
|
path: entry.path,
|
||||||
displayPath,
|
displayPath,
|
||||||
};
|
};
|
||||||
|
}).sort((a: TreeNode, b: TreeNode) => {
|
||||||
|
if (a.type === 'folder' && b.type !== 'folder') return -1;
|
||||||
|
if (a.type !== 'folder' && b.type === 'folder') return 1;
|
||||||
|
return a.label.localeCompare(b.label, undefined, { sensitivity: 'base' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -496,6 +500,7 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
scope: d.scope || 'personal',
|
scope: d.scope || 'personal',
|
||||||
neutralize: d.neutralize ?? false,
|
neutralize: d.neutralize ?? false,
|
||||||
}));
|
}));
|
||||||
|
list.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }));
|
||||||
setDataSources(list);
|
setDataSources(list);
|
||||||
})
|
})
|
||||||
.catch(() => { if (mountedRef.current) setDataSources([]); });
|
.catch(() => { if (mountedRef.current) setDataSources([]); });
|
||||||
|
|
@ -519,6 +524,7 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
neutralizeFields: d.neutralizeFields || undefined,
|
neutralizeFields: d.neutralizeFields || undefined,
|
||||||
recordFilter: d.recordFilter || undefined,
|
recordFilter: d.recordFilter || undefined,
|
||||||
}));
|
}));
|
||||||
|
list.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }));
|
||||||
setFeatureDataSources(list);
|
setFeatureDataSources(list);
|
||||||
})
|
})
|
||||||
.catch(() => { if (mountedRef.current) setFeatureDataSources([]); });
|
.catch(() => { if (mountedRef.current) setFeatureDataSources([]); });
|
||||||
|
|
@ -547,7 +553,8 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
children: null,
|
children: null,
|
||||||
connectionId: c.id,
|
connectionId: c.id,
|
||||||
authority: c.authority,
|
authority: c.authority,
|
||||||
}));
|
}))
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }));
|
||||||
setTree(nodes);
|
setTree(nodes);
|
||||||
})
|
})
|
||||||
.catch(() => { if (mountedRef.current) setTree([]); })
|
.catch(() => { if (mountedRef.current) setTree([]); })
|
||||||
|
|
@ -760,7 +767,7 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
expanded: false,
|
expanded: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
tables: null,
|
tables: null,
|
||||||
})),
|
})).sort((a: FeatureConnectionNode, b: FeatureConnectionNode) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })),
|
||||||
})));
|
})));
|
||||||
})
|
})
|
||||||
.catch(() => { if (mountedRef.current) setFeatureTree([]); })
|
.catch(() => { if (mountedRef.current) setFeatureTree([]); })
|
||||||
|
|
@ -798,14 +805,14 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
objectKey: t.objectKey ?? '',
|
objectKey: t.objectKey ?? '',
|
||||||
tableName: t.tableName ?? '',
|
tableName: t.tableName ?? '',
|
||||||
label: t.label ?? '',
|
label: t.label ?? '',
|
||||||
fields: t.fields ?? [],
|
fields: (t.fields ?? []).slice().sort((a: string, b: string) => a.localeCompare(b, undefined, { sensitivity: 'base' })),
|
||||||
isParent: Boolean(t.isParent),
|
isParent: Boolean(t.isParent),
|
||||||
parentTable: t.parentTable ?? null,
|
parentTable: t.parentTable ?? null,
|
||||||
parentKey: t.parentKey ?? null,
|
parentKey: t.parentKey ?? null,
|
||||||
displayFields: t.displayFields ?? [],
|
displayFields: t.displayFields ?? [],
|
||||||
isGroup: Boolean(t.isGroup),
|
isGroup: Boolean(t.isGroup),
|
||||||
group: t.group ?? null,
|
group: t.group ?? null,
|
||||||
}));
|
})).sort((a: FeatureTableNode, b: FeatureTableNode) => (a.label || a.tableName).localeCompare(b.label || b.tableName, undefined, { sensitivity: 'base' }));
|
||||||
|
|
||||||
// Default-expand all categorical groups so users immediately see their content.
|
// Default-expand all categorical groups so users immediately see their content.
|
||||||
const defaultExpansions: string[] = tables
|
const defaultExpansions: string[] = tables
|
||||||
|
|
@ -912,7 +919,7 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged, onSe
|
||||||
displayLabel: r.displayLabel || r.id,
|
displayLabel: r.displayLabel || r.id,
|
||||||
fields: r.fields || {},
|
fields: r.fields || {},
|
||||||
tableName: table.tableName,
|
tableName: table.tableName,
|
||||||
}));
|
})).sort((a: ParentRecordNode, b: ParentRecordNode) => a.displayLabel.localeCompare(b.displayLabel, undefined, { sensitivity: 'base' }));
|
||||||
if (mountedRef.current) {
|
if (mountedRef.current) {
|
||||||
setFeatureRecordsByPath(prev => ({ ...prev, [pathKey]: records }));
|
setFeatureRecordsByPath(prev => ({ ...prev, [pathKey]: records }));
|
||||||
}
|
}
|
||||||
|
|
@ -1229,11 +1236,24 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
|
||||||
{node.label}
|
{node.label}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* ── Stable trio: chat | scope | neutralize (always in this order).
|
<button
|
||||||
* No "remove from workspace" button here by design: the UDB row only
|
onClick={async (e) => {
|
||||||
* exposes the catalog state. Detach from the *current chat* happens
|
e.stopPropagation();
|
||||||
* via the chip "x" in WorkspaceInput; that chip is the single source
|
if (ds) { onToggleRagIndex(ds); return; }
|
||||||
* of truth for chat-scoped attachment lifecycle. */}
|
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'}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
|
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -1278,24 +1298,6 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
|
||||||
>
|
>
|
||||||
{'\uD83D\uDD12'}
|
{'\uD83D\uDD12'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={async (e) => {
|
|
||||||
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'}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -415,6 +415,17 @@
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sessionSwitcherSelect {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border: 1px solid var(--border-color, #ccc);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-primary, #fff);
|
||||||
|
color: var(--text-primary, #333);
|
||||||
|
}
|
||||||
|
|
||||||
/* ----- Session Layout (UDB Sidebar + Main) ------------------------------- */
|
/* ----- Session Layout (UDB Sidebar + Main) ------------------------------- */
|
||||||
|
|
||||||
.sessionLayout {
|
.sessionLayout {
|
||||||
|
|
|
||||||
|
|
@ -771,28 +771,39 @@ export const TeamsbotSessionView: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Session Switcher (if multiple sessions exist) */}
|
{/* Session Switcher */}
|
||||||
{allSessions.length > 1 && (
|
{allSessions.length > 1 && (
|
||||||
<div style={{ display: 'flex', gap: '8px', marginBottom: '12px', flexWrap: 'wrap' }}>
|
<div style={{ marginBottom: '12px' }}>
|
||||||
{allSessions.map((s) => (
|
<select
|
||||||
<button
|
value={sessionId}
|
||||||
key={s.id}
|
onChange={(e) => _switchSession(e.target.value)}
|
||||||
onClick={() => _switchSession(s.id)}
|
className={styles.sessionSwitcherSelect}
|
||||||
style={{
|
>
|
||||||
padding: '6px 12px',
|
{[...allSessions]
|
||||||
borderRadius: '6px',
|
.sort((a, b) => {
|
||||||
border: s.id === sessionId ? '2px solid #4A90D9' : '1px solid #ddd',
|
const ta = a.startedAt ? new Date(a.startedAt).getTime() : 0;
|
||||||
background: s.id === sessionId ? '#EBF3FC' : '#fff',
|
const tb = b.startedAt ? new Date(b.startedAt).getTime() : 0;
|
||||||
cursor: 'pointer',
|
return tb - ta;
|
||||||
fontSize: '13px',
|
})
|
||||||
fontWeight: s.id === sessionId ? 600 : 400,
|
.map((s) => {
|
||||||
}}
|
const dt = s.startedAt ? new Date(s.startedAt) : null;
|
||||||
>
|
const ts = dt && !Number.isNaN(dt.getTime())
|
||||||
{s.botName}
|
? `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')} ${String(dt.getHours()).padStart(2, '0')}:${String(dt.getMinutes()).padStart(2, '0')}`
|
||||||
{['active', 'joining', 'pending'].includes(s.status) && ' (aktiv)'}
|
: '';
|
||||||
{s.status === 'ended' && ' (beendet)'}
|
const statusTag = ['active', 'joining', 'pending'].includes(s.status)
|
||||||
</button>
|
? ` (${t('aktiv')})`
|
||||||
))}
|
: s.status === 'ended'
|
||||||
|
? ` (${t('beendet')})`
|
||||||
|
: s.status === 'error'
|
||||||
|
? ` (${t('Fehler')})`
|
||||||
|
: '';
|
||||||
|
return (
|
||||||
|
<option key={s.id} value={s.id}>
|
||||||
|
{ts ? `${ts} — ` : ''}{s.botName}{statusTag}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue