fixes doc generation and renderers
All checks were successful
Deploy Nyla Frontend to Integration / deploy (push) Successful in 1m23s
All checks were successful
Deploy Nyla Frontend to Integration / deploy (push) Successful in 1m23s
This commit is contained in:
parent
7876a528f5
commit
59b1e1f6a7
4 changed files with 51 additions and 30 deletions
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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)}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue