Spaces:
Running
Running
Pulastya B
commited on
Commit
·
e82da77
1
Parent(s):
c10fec5
Add live tool execution progress, fix malformed responses, enable dataset/model downloads
Browse files
FRRONTEEEND/components/ChatInterface.tsx
CHANGED
|
@@ -55,25 +55,15 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
|
|
| 55 |
|
| 56 |
const activeSession = sessions.find(s => s.id === activeSessionId) || sessions[0];
|
| 57 |
|
| 58 |
-
// Cleanup progress interval on unmount
|
| 59 |
-
useEffect(() => {
|
| 60 |
-
return () => {
|
| 61 |
-
if (progressIntervalRef.current) {
|
| 62 |
-
clearInterval(progressIntervalRef.current);
|
| 63 |
-
}
|
| 64 |
-
};
|
| 65 |
-
}, []);
|
| 66 |
-
|
| 67 |
useEffect(() => {
|
| 68 |
if (scrollRef.current) {
|
| 69 |
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
| 70 |
}
|
| 71 |
}, [activeSession.messages, isTyping]);
|
| 72 |
|
| 73 |
-
//
|
| 74 |
useEffect(() => {
|
| 75 |
if (!isTyping) {
|
| 76 |
-
// Stop polling when not typing
|
| 77 |
if (progressIntervalRef.current) {
|
| 78 |
clearInterval(progressIntervalRef.current);
|
| 79 |
progressIntervalRef.current = null;
|
|
@@ -94,15 +84,11 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
|
|
| 94 |
|
| 95 |
if (steps.length > 0) {
|
| 96 |
const latestStep = steps[steps.length - 1];
|
|
|
|
| 97 |
const toolName = latestStep.tool
|
| 98 |
.replace(/_/g, ' ')
|
| 99 |
-
.replace(/generate/gi, 'Generating')
|
| 100 |
-
.replace(/train/gi, 'Training')
|
| 101 |
-
.replace(/clean/gi, 'Cleaning')
|
| 102 |
-
.replace(/create/gi, 'Creating')
|
| 103 |
-
.replace(/perform/gi, 'Performing')
|
| 104 |
.replace(/\b\w/g, (l: string) => l.toUpperCase());
|
| 105 |
-
setCurrentStep(toolName);
|
| 106 |
}
|
| 107 |
}
|
| 108 |
} catch (err) {
|
|
@@ -110,10 +96,9 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
|
|
| 110 |
}
|
| 111 |
};
|
| 112 |
|
| 113 |
-
// Start polling
|
| 114 |
progressIntervalRef.current = setInterval(pollProgress, 1000);
|
| 115 |
|
| 116 |
-
// Cleanup on unmount or when isTyping becomes false
|
| 117 |
return () => {
|
| 118 |
if (progressIntervalRef.current) {
|
| 119 |
clearInterval(progressIntervalRef.current);
|
|
@@ -285,6 +270,24 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
|
|
| 285 |
} else {
|
| 286 |
throw new Error('Invalid response from API');
|
| 287 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
updateSession(activeSessionId, [...newMessages, {
|
| 290 |
id: (Date.now() + 1).toString(),
|
|
@@ -607,24 +610,16 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
|
|
| 607 |
<Bot className="w-4 h-4 text-indigo-400" />
|
| 608 |
</div>
|
| 609 |
<div className="bg-white/[0.03] p-4 rounded-2xl border border-white/5">
|
| 610 |
-
|
| 611 |
-
<div className="flex items-center gap-3">
|
| 612 |
-
<div className="flex gap-1">
|
| 613 |
-
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-bounce [animation-delay:-0.3s]"></span>
|
| 614 |
-
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-bounce [animation-delay:-0.15s]"></span>
|
| 615 |
-
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-bounce"></span>
|
| 616 |
-
</div>
|
| 617 |
-
<span className="text-sm text-white/60">
|
| 618 |
-
🔧 {currentStep.replace(/_/g, ' ').replace('train', 'Training').replace('clean', 'Cleaning').replace('generate', 'Generating').replace(/\b\w/g, l => l.toUpperCase())}...
|
| 619 |
-
</span>
|
| 620 |
-
</div>
|
| 621 |
-
) : (
|
| 622 |
<div className="flex gap-1">
|
| 623 |
-
<span className="w-1.5 h-1.5 bg-
|
| 624 |
-
<span className="w-1.5 h-1.5 bg-
|
| 625 |
-
<span className="w-1.5 h-1.5 bg-
|
| 626 |
</div>
|
| 627 |
-
|
|
|
|
|
|
|
|
|
|
| 628 |
</div>
|
| 629 |
</div>
|
| 630 |
)}
|
|
@@ -776,18 +771,26 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
|
|
| 776 |
<h4 className="text-xs font-bold uppercase tracking-wider text-white/60">Data Files ({uniqueDataFiles.length})</h4>
|
| 777 |
</div>
|
| 778 |
<div className="space-y-2">
|
| 779 |
-
{uniqueDataFiles.map((file, idx) =>
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 791 |
</div>
|
| 792 |
</div>
|
| 793 |
)}
|
|
@@ -800,17 +803,42 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
|
|
| 800 |
<h4 className="text-xs font-bold uppercase tracking-wider text-white/60">Models ({uniqueModels.length})</h4>
|
| 801 |
</div>
|
| 802 |
<div className="space-y-2">
|
| 803 |
-
{uniqueModels.map((model, idx) =>
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 814 |
</div>
|
| 815 |
</div>
|
| 816 |
)}
|
|
|
|
| 55 |
|
| 56 |
const activeSession = sessions.find(s => s.id === activeSessionId) || sessions[0];
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
useEffect(() => {
|
| 59 |
if (scrollRef.current) {
|
| 60 |
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
| 61 |
}
|
| 62 |
}, [activeSession.messages, isTyping]);
|
| 63 |
|
| 64 |
+
// Poll for progress ONLY when isTyping is true
|
| 65 |
useEffect(() => {
|
| 66 |
if (!isTyping) {
|
|
|
|
| 67 |
if (progressIntervalRef.current) {
|
| 68 |
clearInterval(progressIntervalRef.current);
|
| 69 |
progressIntervalRef.current = null;
|
|
|
|
| 84 |
|
| 85 |
if (steps.length > 0) {
|
| 86 |
const latestStep = steps[steps.length - 1];
|
| 87 |
+
// Format tool name nicely
|
| 88 |
const toolName = latestStep.tool
|
| 89 |
.replace(/_/g, ' ')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
.replace(/\b\w/g, (l: string) => l.toUpperCase());
|
| 91 |
+
setCurrentStep(`Executing: ${toolName}`);
|
| 92 |
}
|
| 93 |
}
|
| 94 |
} catch (err) {
|
|
|
|
| 96 |
}
|
| 97 |
};
|
| 98 |
|
| 99 |
+
// Start polling every 1 second when workflow is active
|
| 100 |
progressIntervalRef.current = setInterval(pollProgress, 1000);
|
| 101 |
|
|
|
|
| 102 |
return () => {
|
| 103 |
if (progressIntervalRef.current) {
|
| 104 |
clearInterval(progressIntervalRef.current);
|
|
|
|
| 270 |
} else {
|
| 271 |
throw new Error('Invalid response from API');
|
| 272 |
}
|
| 273 |
+
|
| 274 |
+
// Aggressive text cleaning to remove malformed content
|
| 275 |
+
assistantContent = assistantContent
|
| 276 |
+
// Remove broken markdown tables (lines with just | symbols)
|
| 277 |
+
.replace(/^\s*\|\s*\|\s*$/gm, '')
|
| 278 |
+
// Remove confusing phrases
|
| 279 |
+
.replace(/Printed in logs \(see above\)/gi, '')
|
| 280 |
+
.replace(/\(see above\)/gi, '')
|
| 281 |
+
.replace(/see above/gi, '')
|
| 282 |
+
// Remove broken table rows (just dashes and pipes)
|
| 283 |
+
.replace(/^\s*[-|]+\s*$/gm, '')
|
| 284 |
+
// Remove code block markers without content
|
| 285 |
+
.replace(/```\s*```/g, '')
|
| 286 |
+
// Remove empty markdown sections
|
| 287 |
+
.replace(/\n{3,}/g, '\n\n')
|
| 288 |
+
// Clean up broken table syntax
|
| 289 |
+
.replace(/\|\s*\n\s*\|/g, '')
|
| 290 |
+
.trim();
|
| 291 |
|
| 292 |
updateSession(activeSessionId, [...newMessages, {
|
| 293 |
id: (Date.now() + 1).toString(),
|
|
|
|
| 610 |
<Bot className="w-4 h-4 text-indigo-400" />
|
| 611 |
</div>
|
| 612 |
<div className="bg-white/[0.03] p-4 rounded-2xl border border-white/5">
|
| 613 |
+
<div className="flex items-center gap-3">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
<div className="flex gap-1">
|
| 615 |
+
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-bounce [animation-delay:-0.3s]"></span>
|
| 616 |
+
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-bounce [animation-delay:-0.15s]"></span>
|
| 617 |
+
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-bounce"></span>
|
| 618 |
</div>
|
| 619 |
+
<span className="text-sm text-white/60">
|
| 620 |
+
{currentStep || '🔧 Starting analysis...'}
|
| 621 |
+
</span>
|
| 622 |
+
</div>
|
| 623 |
</div>
|
| 624 |
</div>
|
| 625 |
)}
|
|
|
|
| 771 |
<h4 className="text-xs font-bold uppercase tracking-wider text-white/60">Data Files ({uniqueDataFiles.length})</h4>
|
| 772 |
</div>
|
| 773 |
<div className="space-y-2">
|
| 774 |
+
{uniqueDataFiles.map((file, idx) => {
|
| 775 |
+
// Extract filename from path
|
| 776 |
+
const fileName = file.split('/').pop() || file;
|
| 777 |
+
// Create download URL
|
| 778 |
+
const downloadUrl = file.startsWith('/') ? file : `/${file}`;
|
| 779 |
+
|
| 780 |
+
return (
|
| 781 |
+
<button
|
| 782 |
+
key={idx}
|
| 783 |
+
onClick={() => window.open(downloadUrl, '_blank')}
|
| 784 |
+
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 hover:bg-blue-500/10 hover:border-blue-500/30 transition-all text-left group"
|
| 785 |
+
>
|
| 786 |
+
<div className="flex items-center justify-between">
|
| 787 |
+
<span className="text-sm text-white/80 truncate flex-1">{fileName}</span>
|
| 788 |
+
<ChevronRight className="w-4 h-4 text-white/40 group-hover:text-blue-400 transition-all" />
|
| 789 |
+
</div>
|
| 790 |
+
<span className="text-xs text-white/40 mt-1 block">Click to download</span>
|
| 791 |
+
</button>
|
| 792 |
+
);
|
| 793 |
+
})}
|
| 794 |
</div>
|
| 795 |
</div>
|
| 796 |
)}
|
|
|
|
| 803 |
<h4 className="text-xs font-bold uppercase tracking-wider text-white/60">Models ({uniqueModels.length})</h4>
|
| 804 |
</div>
|
| 805 |
<div className="space-y-2">
|
| 806 |
+
{uniqueModels.map((model, idx) => {
|
| 807 |
+
// Find the model file path from workflow history
|
| 808 |
+
let modelPath = '';
|
| 809 |
+
activeSession.messages.forEach(msg => {
|
| 810 |
+
if (msg.role === 'assistant' && msg.content.includes(model)) {
|
| 811 |
+
// Try to extract model path (typically in ./outputs/models/)
|
| 812 |
+
const match = msg.content.match(/\.\/outputs\/models\/[^\s)]+\.pkl/);
|
| 813 |
+
if (match) modelPath = match[0].replace('./', '/');
|
| 814 |
+
}
|
| 815 |
+
});
|
| 816 |
+
|
| 817 |
+
// Fallback: construct typical path
|
| 818 |
+
if (!modelPath) {
|
| 819 |
+
modelPath = `/outputs/models/${model.toLowerCase().replace(/\s+/g, '_')}_model.pkl`;
|
| 820 |
+
}
|
| 821 |
+
|
| 822 |
+
return (
|
| 823 |
+
<button
|
| 824 |
+
key={idx}
|
| 825 |
+
onClick={() => {
|
| 826 |
+
// Trigger download
|
| 827 |
+
const link = document.createElement('a');
|
| 828 |
+
link.href = modelPath;
|
| 829 |
+
link.download = `${model.toLowerCase().replace(/\s+/g, '_')}_model.pkl`;
|
| 830 |
+
link.click();
|
| 831 |
+
}}
|
| 832 |
+
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 hover:bg-purple-500/10 hover:border-purple-500/30 transition-all text-left group"
|
| 833 |
+
>
|
| 834 |
+
<div className="flex items-center justify-between">
|
| 835 |
+
<span className="text-sm text-white/80 truncate flex-1">{model}</span>
|
| 836 |
+
<ChevronRight className="w-4 h-4 text-white/40 group-hover:text-purple-400 transition-all" />
|
| 837 |
+
</div>
|
| 838 |
+
<span className="text-xs text-white/40 mt-1 block">Click to download</span>
|
| 839 |
+
</button>
|
| 840 |
+
);
|
| 841 |
+
})}
|
| 842 |
</div>
|
| 843 |
</div>
|
| 844 |
)}
|