fixes doc generation and renderers
All checks were successful
Deploy Nyla Frontend to Integration / deploy (push) Successful in 1m23s

This commit is contained in:
ValueOn AG 2026-06-03 16:45:22 +02:00
parent 7876a528f5
commit 59b1e1f6a7
4 changed files with 51 additions and 30 deletions

View file

@ -40,6 +40,8 @@ export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
}) => { }) => {
const { t } = useLanguage(); const { t } = useLanguage();
const bottomRef = useRef<HTMLDivElement>(null); const bottomRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const isAtBottomRef = useRef(true);
const audioQueue = useAudioQueue(); const audioQueue = useAudioQueue();
const enqueuedIdsRef = useRef<Set<string>>(new Set()); const enqueuedIdsRef = useRef<Set<string>>(new Set());
const [copiedId, setCopiedId] = useState<string | null>(null); const [copiedId, setCopiedId] = useState<string | null>(null);
@ -51,8 +53,17 @@ export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
}).catch(() => {}); }).catch(() => {});
}, []); }, []);
const _handleScroll = useCallback(() => {
const el = scrollContainerRef.current;
if (!el) return;
const threshold = 80;
isAtBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
}, []);
useEffect(() => { useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); if (isAtBottomRef.current) {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [messages, agentProgress]); }, [messages, agentProgress]);
useEffect(() => { useEffect(() => {
@ -71,15 +82,19 @@ export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
}, [messages, audioQueue]); }, [messages, audioQueue]);
return ( return (
<div style={{ <div
flex: 1, ref={scrollContainerRef}
minHeight: 0, onScroll={_handleScroll}
overflowY: 'auto', style={{
padding: '16px 24px', flex: 1,
display: 'flex', minHeight: 0,
flexDirection: 'column', overflowY: 'auto',
gap: 12, padding: '16px 24px',
}}> display: 'flex',
flexDirection: 'column',
gap: 12,
}}
>
{messages.map((msg) => ( {messages.map((msg) => (
<div <div
key={msg.id} key={msg.id}

View file

@ -59,6 +59,7 @@ interface WorkspaceInputProps {
onFeatureSourceDrop?: (params: { featureInstanceId: string; featureCode: string; tableName?: string; objectKey: string; label: string; fieldName?: string }) => void; onFeatureSourceDrop?: (params: { featureInstanceId: string; featureCode: string; tableName?: string; objectKey: string; label: string; fieldName?: string }) => void;
onDataSourceDrop?: (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => void; onDataSourceDrop?: (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => void;
pendingAttachDsId?: string; pendingAttachDsId?: string;
pendingAttachDsLabel?: string;
onPendingAttachDsConsumed?: () => void; onPendingAttachDsConsumed?: () => void;
pendingAttachFdsId?: string; pendingAttachFdsId?: string;
onPendingAttachFdsConsumed?: () => void; onPendingAttachFdsConsumed?: () => void;
@ -124,6 +125,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
onFeatureSourceDrop, onFeatureSourceDrop,
onDataSourceDrop, onDataSourceDrop,
pendingAttachDsId, pendingAttachDsId,
pendingAttachDsLabel,
onPendingAttachDsConsumed, onPendingAttachDsConsumed,
pendingAttachFdsId, pendingAttachFdsId,
onPendingAttachFdsConsumed, onPendingAttachFdsConsumed,
@ -151,6 +153,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
const [attachedFeatureDataSourceIds, setAttachedFeatureDataSourceIds] = useState<string[]>([]); const [attachedFeatureDataSourceIds, setAttachedFeatureDataSourceIds] = useState<string[]>([]);
const [neutralizeActive, setNeutralizeActive] = useState(false); const [neutralizeActive, setNeutralizeActive] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const dsLabelCache = useRef<Map<string, string>>(new Map());
const _appendAttachment = useCallback((item: AttachmentItem) => { const _appendAttachment = useCallback((item: AttachmentItem) => {
setAttachments(prev => prev.some(a => a.id === item.id) ? prev : [...prev, item]); setAttachments(prev => prev.some(a => a.id === item.id) ? prev : [...prev, item]);
@ -182,6 +185,9 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
useEffect(() => { useEffect(() => {
if (!pendingAttachDsId) return; if (!pendingAttachDsId) return;
if (pendingAttachDsLabel) {
dsLabelCache.current.set(pendingAttachDsId, pendingAttachDsLabel);
}
setAttachedDataSourceIds(prev => { setAttachedDataSourceIds(prev => {
if (prev.includes(pendingAttachDsId)) return prev; if (prev.includes(pendingAttachDsId)) return prev;
const next = [...prev, pendingAttachDsId]; const next = [...prev, pendingAttachDsId];
@ -189,7 +195,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
return next; return next;
}); });
onPendingAttachDsConsumed?.(); onPendingAttachDsConsumed?.();
}, [pendingAttachDsId, onPendingAttachDsConsumed, _persistAttachments, attachedFeatureDataSourceIds]); }, [pendingAttachDsId, pendingAttachDsLabel, onPendingAttachDsConsumed, _persistAttachments, attachedFeatureDataSourceIds]);
useEffect(() => { useEffect(() => {
if (!pendingAttachFdsId) return; if (!pendingAttachFdsId) return;
@ -657,7 +663,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
background: '#e8f5e9', color: '#2e7d32', fontWeight: 500, background: '#e8f5e9', color: '#2e7d32', fontWeight: 500,
}} }}
> >
🔗 {ds?.label || ds?.path || dsId} 🔗 {ds?.label || ds?.path || dsLabelCache.current.get(dsId) || dsId}
<button <button
type="button" type="button"
onClick={() => _removeAttachedDataSource(dsId)} onClick={() => _removeAttachedDataSource(dsId)}

View file

@ -332,16 +332,13 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
objectKey: params.objectKey, objectKey: params.objectKey,
label: params.label, label: params.label,
}); });
// Backend response shape parity with /datasources — accept either a flat
// ``id`` or a wrapped ``featureDataSource.id`` so a future API tweak
// doesn't silently break the chip again.
const newId = const newId =
res.data?.id || res.data?.id ||
res.data?.featureDataSource?.id || res.data?.featureDataSource?.id ||
res.data?.dataSource?.id || res.data?.dataSource?.id ||
''; '';
workspace.refreshFeatureDataSources();
if (newId) { if (newId) {
await workspace.refreshFeatureDataSources();
setPendingAttachFdsId(newId); setPendingAttachFdsId(newId);
} }
} catch (err) { } catch (err) {
@ -350,9 +347,10 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
}, [instanceId, workspace]); }, [instanceId, workspace]);
const [pendingAttachDsId, setPendingAttachDsId] = useState<string>(''); const [pendingAttachDsId, setPendingAttachDsId] = useState<string>('');
const _handleAttachDataSource = useCallback((dsId: string) => { const [pendingAttachDsLabel, setPendingAttachDsLabel] = useState<string>('');
const _handleAttachDataSource = useCallback(async (dsId: string) => {
await workspace.refreshDataSources();
setPendingAttachDsId(dsId); setPendingAttachDsId(dsId);
workspace.refreshDataSources();
}, [workspace]); }, [workspace]);
const _handleDataSourceDrop = useCallback(async (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => { const _handleDataSourceDrop = useCallback(async (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => {
@ -366,8 +364,9 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
}); });
const newId = res.data?.id || res.data?.dataSource?.id; const newId = res.data?.id || res.data?.dataSource?.id;
if (newId) { if (newId) {
setPendingAttachDsLabel(params.label || params.displayPath || '');
await workspace.refreshDataSources();
setPendingAttachDsId(newId); setPendingAttachDsId(newId);
workspace.refreshDataSources();
} }
} catch (err) { } catch (err) {
console.error('Failed to drop data source to chat:', err); console.error('Failed to drop data source to chat:', err);
@ -562,7 +561,8 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
onFeatureSourceDrop={_handleSendToChat_FeatureSource} onFeatureSourceDrop={_handleSendToChat_FeatureSource}
onDataSourceDrop={_handleDataSourceDrop} onDataSourceDrop={_handleDataSourceDrop}
pendingAttachDsId={pendingAttachDsId} pendingAttachDsId={pendingAttachDsId}
onPendingAttachDsConsumed={() => setPendingAttachDsId('')} pendingAttachDsLabel={pendingAttachDsLabel}
onPendingAttachDsConsumed={() => { setPendingAttachDsId(''); setPendingAttachDsLabel(''); }}
pendingAttachFdsId={pendingAttachFdsId} pendingAttachFdsId={pendingAttachFdsId}
onPendingAttachFdsConsumed={() => setPendingAttachFdsId('')} onPendingAttachFdsConsumed={() => setPendingAttachFdsId('')}
onPasteAsFile={_uploadAndAttach} onPasteAsFile={_uploadAndAttach}

View file

@ -97,7 +97,7 @@ interface UseWorkspaceReturn {
files: WorkspaceFile[]; files: WorkspaceFile[];
dataSources: DataSource[]; dataSources: DataSource[];
featureDataSources: FeatureDataSource[]; featureDataSources: FeatureDataSource[];
refreshFeatureDataSources: () => void; refreshFeatureDataSources: () => Promise<void>;
agentProgress: AgentProgress | null; agentProgress: AgentProgress | null;
toolActivities: ToolActivity[]; toolActivities: ToolActivity[];
pendingEdits: FileEditProposal[]; pendingEdits: FileEditProposal[];
@ -106,7 +106,7 @@ interface UseWorkspaceReturn {
workflowId: string | null; workflowId: string | null;
workflowVersion: number; workflowVersion: number;
refreshFiles: () => void; refreshFiles: () => void;
refreshDataSources: () => void; refreshDataSources: () => Promise<void>;
dataSourceAccesses: DataSourceAccessEvent[]; dataSourceAccesses: DataSourceAccessEvent[];
/** /**
* Hydrated chip-bar state for the WorkspaceInput. Set by ``loadWorkflow`` * Hydrated chip-bar state for the WorkspaceInput. Set by ``loadWorkflow``
@ -148,18 +148,18 @@ export function useWorkspace(instanceId: string): UseWorkspaceReturn {
.catch(err => console.error('Failed to load workspace files:', err)); .catch(err => console.error('Failed to load workspace files:', err));
}, [instanceId]); }, [instanceId]);
const refreshDataSources = useCallback(() => { const refreshDataSources = useCallback((): Promise<void> => {
if (!instanceId) return; if (!instanceId) return Promise.resolve();
api.get(`/api/workspace/${instanceId}/datasources`) return api.get(`/api/workspace/${instanceId}/datasources`)
.then(res => setDataSources(res.data.dataSources || [])) .then(res => setDataSources(res.data.dataSources || []))
.catch(() => {}); .catch(err => console.error('Failed to load data sources:', err));
}, [instanceId]); }, [instanceId]);
const refreshFeatureDataSources = useCallback(() => { const refreshFeatureDataSources = useCallback((): Promise<void> => {
if (!instanceId) return; if (!instanceId) return Promise.resolve();
api.get(`/api/workspace/${instanceId}/feature-datasources`) return api.get(`/api/workspace/${instanceId}/feature-datasources`)
.then(res => setFeatureDataSources(res.data.featureDataSources || [])) .then(res => setFeatureDataSources(res.data.featureDataSources || []))
.catch(() => {}); .catch(err => console.error('Failed to load feature data sources:', err));
}, [instanceId]); }, [instanceId]);
useEffect(() => { useEffect(() => {