Compare commits

..

No commits in common. "5ce871fb3ca05b90e3d415a532a339eca93e2e29" and "991952dde903c925dcdb0c6160970352e38989de" have entirely different histories.

4 changed files with 30 additions and 51 deletions

View file

@ -40,8 +40,6 @@ 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);
@ -53,17 +51,8 @@ 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(() => {
if (isAtBottomRef.current) {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [messages, agentProgress]);
useEffect(() => {
@ -82,10 +71,7 @@ export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
}, [messages, audioQueue]);
return (
<div
ref={scrollContainerRef}
onScroll={_handleScroll}
style={{
<div style={{
flex: 1,
minHeight: 0,
overflowY: 'auto',
@ -93,8 +79,7 @@ export const ChatStream: React.FC<ChatStreamProps> = ({ messages,
display: 'flex',
flexDirection: 'column',
gap: 12,
}}
>
}}>
{messages.map((msg) => (
<div
key={msg.id}

View file

@ -59,7 +59,6 @@ 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;
@ -125,7 +124,6 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
onFeatureSourceDrop,
onDataSourceDrop,
pendingAttachDsId,
pendingAttachDsLabel,
onPendingAttachDsConsumed,
pendingAttachFdsId,
onPendingAttachFdsConsumed,
@ -153,7 +151,6 @@ 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]);
@ -185,9 +182,6 @@ 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];
@ -195,7 +189,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
return next;
});
onPendingAttachDsConsumed?.();
}, [pendingAttachDsId, pendingAttachDsLabel, onPendingAttachDsConsumed, _persistAttachments, attachedFeatureDataSourceIds]);
}, [pendingAttachDsId, onPendingAttachDsConsumed, _persistAttachments, attachedFeatureDataSourceIds]);
useEffect(() => {
if (!pendingAttachFdsId) return;
@ -663,7 +657,7 @@ export const WorkspaceInput = forwardRef<WorkspaceInputHandle, WorkspaceInputPro
background: '#e8f5e9', color: '#2e7d32', fontWeight: 500,
}}
>
🔗 {ds?.label || ds?.path || dsLabelCache.current.get(dsId) || dsId}
🔗 {ds?.label || ds?.path || dsId}
<button
type="button"
onClick={() => _removeAttachedDataSource(dsId)}

View file

@ -332,13 +332,16 @@ 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) {
@ -347,10 +350,9 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
}, [instanceId, workspace]);
const [pendingAttachDsId, setPendingAttachDsId] = useState<string>('');
const [pendingAttachDsLabel, setPendingAttachDsLabel] = useState<string>('');
const _handleAttachDataSource = useCallback(async (dsId: string) => {
await workspace.refreshDataSources();
const _handleAttachDataSource = useCallback((dsId: string) => {
setPendingAttachDsId(dsId);
workspace.refreshDataSources();
}, [workspace]);
const _handleDataSourceDrop = useCallback(async (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => {
@ -364,9 +366,8 @@ 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);
@ -561,8 +562,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
onFeatureSourceDrop={_handleSendToChat_FeatureSource}
onDataSourceDrop={_handleDataSourceDrop}
pendingAttachDsId={pendingAttachDsId}
pendingAttachDsLabel={pendingAttachDsLabel}
onPendingAttachDsConsumed={() => { setPendingAttachDsId(''); setPendingAttachDsLabel(''); }}
onPendingAttachDsConsumed={() => setPendingAttachDsId('')}
pendingAttachFdsId={pendingAttachFdsId}
onPendingAttachFdsConsumed={() => setPendingAttachFdsId('')}
onPasteAsFile={_uploadAndAttach}

View file

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