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 bottomRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const isAtBottomRef = useRef(true);
const audioQueue = useAudioQueue();
const enqueuedIdsRef = useRef<Set<string>>(new Set());
const [copiedId, setCopiedId] = useState<string | null>(null);
@ -51,8 +53,17 @@ export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
}).catch(() => {});
}, []);
const _handleScroll = useCallback(() => {
const el = scrollContainerRef.current;
if (!el) return;
const threshold = 80;
isAtBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
}, []);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
if (isAtBottomRef.current) {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [messages, agentProgress]);
useEffect(() => {
@ -71,15 +82,19 @@ export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
}, [messages, audioQueue]);
return (
<div style={{
flex: 1,
minHeight: 0,
overflowY: 'auto',
padding: '16px 24px',
display: 'flex',
flexDirection: 'column',
gap: 12,
}}>
<div
ref={scrollContainerRef}
onScroll={_handleScroll}
style={{
flex: 1,
minHeight: 0,
overflowY: 'auto',
padding: '16px 24px',
display: 'flex',
flexDirection: 'column',
gap: 12,
}}
>
{messages.map((msg) => (
<div
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;
onDataSourceDrop?: (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => void;
pendingAttachDsId?: string;
pendingAttachDsLabel?: string;
onPendingAttachDsConsumed?: () => void;
pendingAttachFdsId?: string;
onPendingAttachFdsConsumed?: () => void;
@ -124,6 +125,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
onFeatureSourceDrop,
onDataSourceDrop,
pendingAttachDsId,
pendingAttachDsLabel,
onPendingAttachDsConsumed,
pendingAttachFdsId,
onPendingAttachFdsConsumed,
@ -151,6 +153,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
const [attachedFeatureDataSourceIds, setAttachedFeatureDataSourceIds] = useState<string[]>([]);
const [neutralizeActive, setNeutralizeActive] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const dsLabelCache = useRef<Map<string, string>>(new Map());
const _appendAttachment = useCallback((item: AttachmentItem) => {
setAttachments(prev => prev.some(a => a.id === item.id) ? prev : [...prev, item]);
@ -182,6 +185,9 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
useEffect(() => {
if (!pendingAttachDsId) return;
if (pendingAttachDsLabel) {
dsLabelCache.current.set(pendingAttachDsId, pendingAttachDsLabel);
}
setAttachedDataSourceIds(prev => {
if (prev.includes(pendingAttachDsId)) return prev;
const next = [...prev, pendingAttachDsId];
@ -189,7 +195,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
return next;
});
onPendingAttachDsConsumed?.();
}, [pendingAttachDsId, onPendingAttachDsConsumed, _persistAttachments, attachedFeatureDataSourceIds]);
}, [pendingAttachDsId, pendingAttachDsLabel, onPendingAttachDsConsumed, _persistAttachments, attachedFeatureDataSourceIds]);
useEffect(() => {
if (!pendingAttachFdsId) return;
@ -657,7 +663,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
background: '#e8f5e9', color: '#2e7d32', fontWeight: 500,
}}
>
🔗 {ds?.label || ds?.path || dsId}
🔗 {ds?.label || ds?.path || dsLabelCache.current.get(dsId) || dsId}
<button
type="button"
onClick={() => _removeAttachedDataSource(dsId)}

View file

@ -332,16 +332,13 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
objectKey: params.objectKey,
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 =
res.data?.id ||
res.data?.featureDataSource?.id ||
res.data?.dataSource?.id ||
'';
workspace.refreshFeatureDataSources();
if (newId) {
await workspace.refreshFeatureDataSources();
setPendingAttachFdsId(newId);
}
} catch (err) {
@ -350,9 +347,10 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
}, [instanceId, workspace]);
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);
workspace.refreshDataSources();
}, [workspace]);
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;
if (newId) {
setPendingAttachDsLabel(params.label || params.displayPath || '');
await workspace.refreshDataSources();
setPendingAttachDsId(newId);
workspace.refreshDataSources();
}
} catch (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}
onDataSourceDrop={_handleDataSourceDrop}
pendingAttachDsId={pendingAttachDsId}
onPendingAttachDsConsumed={() => setPendingAttachDsId('')}
pendingAttachDsLabel={pendingAttachDsLabel}
onPendingAttachDsConsumed={() => { setPendingAttachDsId(''); setPendingAttachDsLabel(''); }}
pendingAttachFdsId={pendingAttachFdsId}
onPendingAttachFdsConsumed={() => setPendingAttachFdsId('')}
onPasteAsFile={_uploadAndAttach}

View file

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