Spaces:
Sleeping
Sleeping
Commit ·
0a2fd47
1
Parent(s): bcd9d5d
fix: dedupe stream steps and exclude agent skills from plugins
Browse files- filter enabled plugins to non-agent plugin IDs before scrape requests
- prevent duplicate stream events and repeated session init logs in dashboard
- add start/stop locking to avoid accidental concurrent scrape starts
- align plugin count/accordion display with non-agent plugin selection
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
frontend/src/components/Dashboard.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import React, { useState, useRef, useCallback } from 'react';
|
| 2 |
import { useQuery } from '@tanstack/react-query';
|
| 3 |
import {
|
| 4 |
Activity,
|
|
@@ -93,6 +93,11 @@ const getStepColor = (action: string, status: string): string => {
|
|
| 93 |
return colorMap[action] || 'text-slate-400 bg-slate-500/20 border-slate-500/30';
|
| 94 |
};
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
// Step Accordion Component
|
| 97 |
interface StepAccordionItemProps {
|
| 98 |
step: ScrapeStep;
|
|
@@ -402,6 +407,9 @@ export const Dashboard: React.FC = () => {
|
|
| 402 |
const [progress, setProgress] = useState({ urlIndex: 0, totalUrls: 0, currentUrl: '' });
|
| 403 |
const [extractedData, setExtractedData] = useState<Record<string, unknown>>({});
|
| 404 |
const abortControllerRef = useRef<{ abort: () => void } | null>(null);
|
|
|
|
|
|
|
|
|
|
| 405 |
|
| 406 |
// Assets
|
| 407 |
const [assets, setAssets] = useState<Asset[]>([]);
|
|
@@ -475,15 +483,20 @@ export const Dashboard: React.FC = () => {
|
|
| 475 |
|
| 476 |
// Get installed plugins only
|
| 477 |
const getInstalledPlugins = () => {
|
| 478 |
-
if (!pluginsData?.plugins) return { mcps: [],
|
| 479 |
const result: Record<string, PluginInfo[]> = {};
|
| 480 |
for (const [category, plugins] of Object.entries(pluginsData.plugins)) {
|
|
|
|
| 481 |
result[category] = (plugins as PluginInfo[]).filter(p => p.installed);
|
| 482 |
}
|
| 483 |
return result;
|
| 484 |
};
|
| 485 |
|
| 486 |
const installedPlugins = getInstalledPlugins();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
|
| 488 |
// Get agents
|
| 489 |
const agents: AgentInfo[] = agentsData?.agent_types || [];
|
|
@@ -561,6 +574,10 @@ export const Dashboard: React.FC = () => {
|
|
| 561 |
// Start task with streaming
|
| 562 |
const handleStart = useCallback(() => {
|
| 563 |
if (taskInput.urls.length === 0 && !taskInput.instruction) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 564 |
|
| 565 |
setStats(prev => ({ ...prev, episodes: prev.episodes + 1, steps: 0, totalReward: 0, avgReward: 0 }));
|
| 566 |
setIsRunning(true);
|
|
@@ -583,7 +600,7 @@ export const Dashboard: React.FC = () => {
|
|
| 583 |
model: taskInput.selectedModel.split('/')[1] || 'llama-3.3-70b',
|
| 584 |
provider: taskInput.selectedModel.split('/')[0] || 'nvidia',
|
| 585 |
enable_memory: true,
|
| 586 |
-
enable_plugins:
|
| 587 |
selected_agents: taskInput.selectedAgents,
|
| 588 |
max_steps: 50,
|
| 589 |
};
|
|
@@ -602,6 +619,8 @@ export const Dashboard: React.FC = () => {
|
|
| 602 |
scrapeRequest,
|
| 603 |
// onInit
|
| 604 |
(sid) => {
|
|
|
|
|
|
|
| 605 |
setSessionId(sid);
|
| 606 |
setLogs(prev => [...prev, {
|
| 607 |
id: Date.now().toString(),
|
|
@@ -624,6 +643,10 @@ export const Dashboard: React.FC = () => {
|
|
| 624 |
},
|
| 625 |
// onStep
|
| 626 |
(step) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 627 |
setCurrentStep(step);
|
| 628 |
setAllSteps(prev => [...prev, step]);
|
| 629 |
setStats(prev => {
|
|
@@ -662,6 +685,8 @@ export const Dashboard: React.FC = () => {
|
|
| 662 |
},
|
| 663 |
// onComplete
|
| 664 |
(response) => {
|
|
|
|
|
|
|
| 665 |
setScrapeResult(response);
|
| 666 |
setIsRunning(false);
|
| 667 |
setStats(prev => ({
|
|
@@ -690,6 +715,11 @@ export const Dashboard: React.FC = () => {
|
|
| 690 |
},
|
| 691 |
// onError
|
| 692 |
(error, url) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 693 |
setLogs(prev => [...prev, {
|
| 694 |
id: Date.now().toString(),
|
| 695 |
timestamp: new Date().toISOString(),
|
|
@@ -699,7 +729,7 @@ export const Dashboard: React.FC = () => {
|
|
| 699 |
}]);
|
| 700 |
}
|
| 701 |
);
|
| 702 |
-
}, [taskInput]);
|
| 703 |
|
| 704 |
// Stop task
|
| 705 |
const handleStop = useCallback(() => {
|
|
@@ -707,6 +737,7 @@ export const Dashboard: React.FC = () => {
|
|
| 707 |
abortControllerRef.current.abort();
|
| 708 |
abortControllerRef.current = null;
|
| 709 |
}
|
|
|
|
| 710 |
setIsRunning(false);
|
| 711 |
setLogs(prev => [...prev, {
|
| 712 |
id: Date.now().toString(),
|
|
@@ -939,7 +970,7 @@ export const Dashboard: React.FC = () => {
|
|
| 939 |
className="px-5 py-3 bg-amber-500/10 hover:bg-amber-500/20 border border-amber-500/30 text-amber-400 rounded-xl text-sm font-medium transition-all flex items-center gap-2 shadow-lg shadow-amber-500/5"
|
| 940 |
>
|
| 941 |
<Plug className="w-4 h-4" />
|
| 942 |
-
Plugins {
|
| 943 |
</button>
|
| 944 |
|
| 945 |
{/* Task Type */}
|
|
@@ -1336,11 +1367,11 @@ export const Dashboard: React.FC = () => {
|
|
| 1336 |
</Accordion>
|
| 1337 |
|
| 1338 |
{/* Plugins */}
|
| 1339 |
-
<Accordion title="Plugins" icon={Plug} badge={
|
| 1340 |
-
{
|
| 1341 |
<p className="text-xs text-slate-500 p-2">No plugins enabled</p>
|
| 1342 |
) : (
|
| 1343 |
-
|
| 1344 |
<div key={pluginId} className="p-2 bg-amber-500/10 border border-amber-500/30 rounded-lg">
|
| 1345 |
<span className="text-xs text-white">{pluginId}</span>
|
| 1346 |
</div>
|
|
|
|
| 1 |
+
import React, { useMemo, useState, useRef, useCallback } from 'react';
|
| 2 |
import { useQuery } from '@tanstack/react-query';
|
| 3 |
import {
|
| 4 |
Activity,
|
|
|
|
| 93 |
return colorMap[action] || 'text-slate-400 bg-slate-500/20 border-slate-500/30';
|
| 94 |
};
|
| 95 |
|
| 96 |
+
const isAgentPluginId = (pluginId: string): boolean => {
|
| 97 |
+
const lowered = pluginId.toLowerCase();
|
| 98 |
+
return lowered.startsWith('skill-') || lowered === 'web_scraper';
|
| 99 |
+
};
|
| 100 |
+
|
| 101 |
// Step Accordion Component
|
| 102 |
interface StepAccordionItemProps {
|
| 103 |
step: ScrapeStep;
|
|
|
|
| 407 |
const [progress, setProgress] = useState({ urlIndex: 0, totalUrls: 0, currentUrl: '' });
|
| 408 |
const [extractedData, setExtractedData] = useState<Record<string, unknown>>({});
|
| 409 |
const abortControllerRef = useRef<{ abort: () => void } | null>(null);
|
| 410 |
+
const startLockRef = useRef(false);
|
| 411 |
+
const seenStepKeysRef = useRef<Set<string>>(new Set());
|
| 412 |
+
const lastSessionInitRef = useRef<string | null>(null);
|
| 413 |
|
| 414 |
// Assets
|
| 415 |
const [assets, setAssets] = useState<Asset[]>([]);
|
|
|
|
| 483 |
|
| 484 |
// Get installed plugins only
|
| 485 |
const getInstalledPlugins = () => {
|
| 486 |
+
if (!pluginsData?.plugins) return { mcps: [], apis: [], processors: [] };
|
| 487 |
const result: Record<string, PluginInfo[]> = {};
|
| 488 |
for (const [category, plugins] of Object.entries(pluginsData.plugins)) {
|
| 489 |
+
if (category === 'skills') continue;
|
| 490 |
result[category] = (plugins as PluginInfo[]).filter(p => p.installed);
|
| 491 |
}
|
| 492 |
return result;
|
| 493 |
};
|
| 494 |
|
| 495 |
const installedPlugins = getInstalledPlugins();
|
| 496 |
+
const enabledNonAgentPlugins = useMemo(
|
| 497 |
+
() => taskInput.enabledPlugins.filter((pluginId) => !isAgentPluginId(pluginId)),
|
| 498 |
+
[taskInput.enabledPlugins]
|
| 499 |
+
);
|
| 500 |
|
| 501 |
// Get agents
|
| 502 |
const agents: AgentInfo[] = agentsData?.agent_types || [];
|
|
|
|
| 574 |
// Start task with streaming
|
| 575 |
const handleStart = useCallback(() => {
|
| 576 |
if (taskInput.urls.length === 0 && !taskInput.instruction) return;
|
| 577 |
+
if (startLockRef.current || abortControllerRef.current) return;
|
| 578 |
+
startLockRef.current = true;
|
| 579 |
+
seenStepKeysRef.current.clear();
|
| 580 |
+
lastSessionInitRef.current = null;
|
| 581 |
|
| 582 |
setStats(prev => ({ ...prev, episodes: prev.episodes + 1, steps: 0, totalReward: 0, avgReward: 0 }));
|
| 583 |
setIsRunning(true);
|
|
|
|
| 600 |
model: taskInput.selectedModel.split('/')[1] || 'llama-3.3-70b',
|
| 601 |
provider: taskInput.selectedModel.split('/')[0] || 'nvidia',
|
| 602 |
enable_memory: true,
|
| 603 |
+
enable_plugins: enabledNonAgentPlugins,
|
| 604 |
selected_agents: taskInput.selectedAgents,
|
| 605 |
max_steps: 50,
|
| 606 |
};
|
|
|
|
| 619 |
scrapeRequest,
|
| 620 |
// onInit
|
| 621 |
(sid) => {
|
| 622 |
+
if (lastSessionInitRef.current === sid) return;
|
| 623 |
+
lastSessionInitRef.current = sid;
|
| 624 |
setSessionId(sid);
|
| 625 |
setLogs(prev => [...prev, {
|
| 626 |
id: Date.now().toString(),
|
|
|
|
| 643 |
},
|
| 644 |
// onStep
|
| 645 |
(step) => {
|
| 646 |
+
const stepKey = `${step.step_number}|${step.action}|${step.url ?? ''}|${step.status}|${step.message}|${step.timestamp}`;
|
| 647 |
+
if (seenStepKeysRef.current.has(stepKey)) return;
|
| 648 |
+
seenStepKeysRef.current.add(stepKey);
|
| 649 |
+
|
| 650 |
setCurrentStep(step);
|
| 651 |
setAllSteps(prev => [...prev, step]);
|
| 652 |
setStats(prev => {
|
|
|
|
| 685 |
},
|
| 686 |
// onComplete
|
| 687 |
(response) => {
|
| 688 |
+
startLockRef.current = false;
|
| 689 |
+
abortControllerRef.current = null;
|
| 690 |
setScrapeResult(response);
|
| 691 |
setIsRunning(false);
|
| 692 |
setStats(prev => ({
|
|
|
|
| 715 |
},
|
| 716 |
// onError
|
| 717 |
(error, url) => {
|
| 718 |
+
if (!url) {
|
| 719 |
+
startLockRef.current = false;
|
| 720 |
+
abortControllerRef.current = null;
|
| 721 |
+
setIsRunning(false);
|
| 722 |
+
}
|
| 723 |
setLogs(prev => [...prev, {
|
| 724 |
id: Date.now().toString(),
|
| 725 |
timestamp: new Date().toISOString(),
|
|
|
|
| 729 |
}]);
|
| 730 |
}
|
| 731 |
);
|
| 732 |
+
}, [taskInput, enabledNonAgentPlugins]);
|
| 733 |
|
| 734 |
// Stop task
|
| 735 |
const handleStop = useCallback(() => {
|
|
|
|
| 737 |
abortControllerRef.current.abort();
|
| 738 |
abortControllerRef.current = null;
|
| 739 |
}
|
| 740 |
+
startLockRef.current = false;
|
| 741 |
setIsRunning(false);
|
| 742 |
setLogs(prev => [...prev, {
|
| 743 |
id: Date.now().toString(),
|
|
|
|
| 970 |
className="px-5 py-3 bg-amber-500/10 hover:bg-amber-500/20 border border-amber-500/30 text-amber-400 rounded-xl text-sm font-medium transition-all flex items-center gap-2 shadow-lg shadow-amber-500/5"
|
| 971 |
>
|
| 972 |
<Plug className="w-4 h-4" />
|
| 973 |
+
Plugins {enabledNonAgentPlugins.length > 0 && `(${enabledNonAgentPlugins.length})`}
|
| 974 |
</button>
|
| 975 |
|
| 976 |
{/* Task Type */}
|
|
|
|
| 1367 |
</Accordion>
|
| 1368 |
|
| 1369 |
{/* Plugins */}
|
| 1370 |
+
<Accordion title="Plugins" icon={Plug} badge={enabledNonAgentPlugins.length} color="text-amber-400">
|
| 1371 |
+
{enabledNonAgentPlugins.length === 0 ? (
|
| 1372 |
<p className="text-xs text-slate-500 p-2">No plugins enabled</p>
|
| 1373 |
) : (
|
| 1374 |
+
enabledNonAgentPlugins.map((pluginId) => (
|
| 1375 |
<div key={pluginId} className="p-2 bg-amber-500/10 border border-amber-500/30 rounded-lg">
|
| 1376 |
<span className="text-xs text-white">{pluginId}</span>
|
| 1377 |
</div>
|