NeerajCodz Copilot commited on
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: [], skills: [], apis: [], processors: [] };
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: taskInput.enabledPlugins,
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 {taskInput.enabledPlugins.length > 0 && `(${taskInput.enabledPlugins.length})`}
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={taskInput.enabledPlugins.length} color="text-amber-400">
1340
- {taskInput.enabledPlugins.length === 0 ? (
1341
  <p className="text-xs text-slate-500 p-2">No plugins enabled</p>
1342
  ) : (
1343
- taskInput.enabledPlugins.map((pluginId) => (
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>