alot of othe mess fro autoamtion overview an ddetiaeld run apge sonly

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-17 10:13:08 +00:00
parent 6b1fa0c1ee
commit 0435a5cf70
14 changed files with 1727 additions and 470 deletions

View File

@@ -1,150 +1,139 @@
/**
* Automation Overview Page
* Comprehensive dashboard showing automation status, metrics, cost estimation, and run history
* Meaningful dashboard showing actual production data, not estimates
*/
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { useSiteStore } from '../../store/siteStore';
import { automationService } from '../../services/automationService';
import { OverviewStatsResponse } from '../../types/automation';
import {
fetchKeywords,
fetchClusters,
fetchContentIdeas,
fetchTasks,
fetchContent,
fetchImages,
} from '../../services/api';
import RunHistory from '../../components/Automation/RunHistory';
import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import ComponentCard from '../../components/common/ComponentCard';
import RunStatisticsSummary from '../../components/Automation/DetailView/RunStatisticsSummary';
import PredictiveCostAnalysis from '../../components/Automation/DetailView/PredictiveCostAnalysis';
import AttentionItemsAlert from '../../components/Automation/DetailView/AttentionItemsAlert';
import EnhancedRunHistory from '../../components/Automation/DetailView/EnhancedRunHistory';
import MeaningfulRunHistory from '../../components/Automation/DetailView/MeaningfulRunHistory';
import ProductionSummary from '../../components/Automation/DetailView/ProductionSummary';
import {
ListIcon,
GroupIcon,
BoltIcon,
ClockIcon,
FileTextIcon,
FileIcon,
BoltIcon,
PaperPlaneIcon,
} from '../../icons';
interface ActualCounts {
keywords: number;
clusters: number;
ideas: number;
tasks: number;
content: number;
images: number;
}
interface AutomationTotals {
total_runs: number;
runs_with_output: number;
total_credits: number;
clusters_created: number;
ideas_created: number;
content_created: number;
images_created: number;
approved_via_automation: number;
clusters_total: number;
ideas_total: number;
content_total: number;
images_total: number;
}
interface Efficiency {
total_items_created: number;
credits_per_item: number;
}
interface MeaningfulRun {
run_id: string;
run_number: number;
status: string;
started_at: string;
duration_seconds: number;
total_credits: number;
stages: Array<{
stage: number;
name: string;
input: number;
output: number;
credits: number;
}>;
}
interface ProductionStats {
totals: AutomationTotals;
actual_counts: ActualCounts;
efficiency: Efficiency;
meaningful_runs: MeaningfulRun[];
}
const AutomationOverview: React.FC = () => {
const { activeSite } = useSiteStore();
const navigate = useNavigate();
const toast = useToast();
const [loading, setLoading] = useState(true);
const [metrics, setMetrics] = useState<any>(null);
const [overviewStats, setOverviewStats] = useState<OverviewStatsResponse | null>(null);
const [historyPage, setHistoryPage] = useState(1);
const [historyData, setHistoryData] = useState<any>(null);
const [productionStats, setProductionStats] = useState<ProductionStats | null>(null);
const [hasRunning, setHasRunning] = useState(false);
const [pendingCounts, setPendingCounts] = useState({
keywords: 0,
content: 0,
images: 0,
review: 0,
});
// Load metrics for the 5 metric cards
const loadMetrics = async () => {
const loadData = async () => {
if (!activeSite) return;
setLoading(true);
try {
const [
keywordsTotalRes, keywordsNewRes, keywordsMappedRes,
clustersTotalRes, clustersNewRes, clustersMappedRes,
ideasTotalRes, ideasNewRes, ideasQueuedRes, ideasCompletedRes,
tasksTotalRes,
contentTotalRes, contentDraftRes, contentReviewRes, contentPublishedRes,
contentNotPublishedRes, contentScheduledRes,
imagesTotalRes, imagesPendingRes,
] = await Promise.all([
fetchKeywords({ page_size: 1, site_id: activeSite.id }),
fetchKeywords({ page_size: 1, site_id: activeSite.id, status: 'new' }),
fetchKeywords({ page_size: 1, site_id: activeSite.id, status: 'mapped' }),
fetchClusters({ page_size: 1, site_id: activeSite.id }),
fetchClusters({ page_size: 1, site_id: activeSite.id, status: 'new' }),
fetchClusters({ page_size: 1, site_id: activeSite.id, status: 'mapped' }),
fetchContentIdeas({ page_size: 1, site_id: activeSite.id }),
fetchContentIdeas({ page_size: 1, site_id: activeSite.id, status: 'new' }),
fetchContentIdeas({ page_size: 1, site_id: activeSite.id, status: 'queued' }),
fetchContentIdeas({ page_size: 1, site_id: activeSite.id, status: 'completed' }),
fetchTasks({ page_size: 1, site_id: activeSite.id }),
fetchContent({ page_size: 1, site_id: activeSite.id }),
fetchContent({ page_size: 1, site_id: activeSite.id, status: 'draft' }),
fetchContent({ page_size: 1, site_id: activeSite.id, status: 'review' }),
fetchContent({ page_size: 1, site_id: activeSite.id, status__in: 'approved,published' }),
fetchContent({ page_size: 1, site_id: activeSite.id, status: 'approved' }),
fetchContent({ page_size: 1, site_id: activeSite.id, status: 'approved' }),
fetchImages({ page_size: 1 }),
fetchImages({ page_size: 1, status: 'pending' }),
const [stats, currentRun, pipeline] = await Promise.all([
automationService.getProductionStats(activeSite.id),
automationService.getCurrentRun(activeSite.id),
automationService.getPipelineOverview(activeSite.id),
]);
setMetrics({
keywords: { total: keywordsTotalRes.count || 0, new: keywordsNewRes.count || 0, mapped: keywordsMappedRes.count || 0 },
clusters: { total: clustersTotalRes.count || 0, new: clustersNewRes.count || 0, mapped: clustersMappedRes.count || 0 },
ideas: { total: ideasTotalRes.count || 0, new: ideasNewRes.count || 0, queued: ideasQueuedRes.count || 0, completed: ideasCompletedRes.count || 0 },
tasks: { total: tasksTotalRes.count || 0 },
content: {
total: contentTotalRes.count || 0,
draft: contentDraftRes.count || 0,
review: contentReviewRes.count || 0,
published: contentPublishedRes.count || 0,
not_published: contentNotPublishedRes.count || 0,
scheduled: contentScheduledRes.count || 0,
},
images: { total: imagesTotalRes.count || 0, pending: imagesPendingRes.count || 0 },
});
setProductionStats(stats);
setHasRunning(!!currentRun.run && (currentRun.run.status === 'running' || currentRun.run.status === 'paused'));
// Extract pending counts from pipeline
if (pipeline.stages) {
const stage1 = pipeline.stages.find((s: any) => s.number === 1);
const stage5 = pipeline.stages.find((s: any) => s.number === 5);
const stage6 = pipeline.stages.find((s: any) => s.number === 6);
const stage7 = pipeline.stages.find((s: any) => s.number === 7);
setPendingCounts({
keywords: stage1?.pending || 0,
content: stage5?.pending || 0,
images: stage6?.pending || 0,
review: stage7?.pending || 0,
});
}
} catch (e) {
console.warn('Failed to fetch metrics for automation overview', e);
}
};
// Load cost estimate
const loadOverviewStats = async () => {
if (!activeSite) return;
try {
const stats = await automationService.getOverviewStats(activeSite.id);
setOverviewStats(stats);
} catch (e) {
console.warn('Failed to fetch overview stats', e);
}
};
// Load enhanced history
const loadEnhancedHistory = async (page: number = 1) => {
if (!activeSite) return;
try {
const history = await automationService.getEnhancedHistory(activeSite.id, page, 10);
setHistoryData(history);
} catch (e) {
console.warn('Failed to fetch enhanced history', e);
// Set to null so fallback component shows
setHistoryData(null);
console.error('Failed to load production stats', e);
} finally {
setLoading(false);
}
};
useEffect(() => {
const loadData = async () => {
setLoading(true);
await Promise.all([loadMetrics(), loadOverviewStats(), loadEnhancedHistory(historyPage)]);
setLoading(false);
};
if (activeSite) {
loadData();
}
}, [activeSite, historyPage]);
}, [activeSite]);
// Helper to render metric rows
const renderMetricRow = (items: Array<{ label: string; value: number; colorCls: string }>) => {
return (
<div className="flex justify-between text-xs mt-2">
{items.map((item, idx) => (
<div key={idx} className="flex items-baseline gap-1">
<span className="text-gray-500 dark:text-gray-400">{item.label}</span>
<span className={`font-semibold ${item.colorCls}`}>{item.value}</span>
</div>
))}
</div>
);
const handleStartRun = async () => {
if (!activeSite) return;
try {
await automationService.runNow(activeSite.id);
toast.success('Automation started');
navigate('/automation');
} catch (e: any) {
toast.error(e?.message || 'Failed to start automation');
}
};
if (!activeSite) {
@@ -158,166 +147,120 @@ const AutomationOverview: React.FC = () => {
if (loading) {
return (
<div className="flex items-center justify-center min-h-[60vh]">
<div className="text-gray-500 dark:text-gray-400">Loading automation overview...</div>
<div className="text-gray-500 dark:text-gray-400">Loading automation data...</div>
</div>
);
}
const totals = productionStats?.totals || {
total_runs: 0,
runs_with_output: 0,
total_credits: 0,
clusters_created: 0,
ideas_created: 0,
content_created: 0,
images_created: 0,
approved_via_automation: 0,
clusters_total: 0,
ideas_total: 0,
content_total: 0,
images_total: 0,
};
const actual_counts = productionStats?.actual_counts || {
keywords: 0,
clusters: 0,
ideas: 0,
tasks: 0,
content: 0,
images: 0,
};
return (
<>
<PageMeta title="Automation Overview" description="Comprehensive automation dashboard" />
<PageMeta title="Automation Overview" description="Production dashboard" />
<div className="space-y-6">
<PageHeader
title="Automation Overview"
breadcrumb="Automation / Overview"
description="Comprehensive automation dashboard with metrics, cost estimation, and run history"
description="Your content production metrics and history"
/>
{/* Metrics Summary Cards */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
{/* Keywords */}
<div className="bg-white dark:bg-gray-900 rounded-xl p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="size-8 rounded-lg bg-brand-100 dark:bg-brand-900/30 flex items-center justify-center">
<ListIcon className="size-4 text-brand-600 dark:text-brand-400" />
</div>
<div className="text-base font-bold text-gray-900 dark:text-white">Keywords</div>
</div>
<div className="text-right">
<div className="text-3xl font-bold text-brand-600">{metrics?.keywords?.total || 0}</div>
</div>
</div>
{renderMetricRow([
{ label: 'New:', value: metrics?.keywords?.new || 0, colorCls: 'text-brand-600' },
{ label: 'Mapped:', value: metrics?.keywords?.mapped || 0, colorCls: 'text-brand-600' },
])}
</div>
{/* Quick Actions Row - Compact */}
<div className="flex items-center gap-3">
<button
onClick={hasRunning ? () => navigate('/automation') : handleStartRun}
className={`
flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all
${hasRunning
? 'bg-brand-100 text-brand-700 dark:bg-brand-900/30 dark:text-brand-400'
: 'bg-brand-600 text-white hover:bg-brand-700'
}
`}
>
<BoltIcon className="w-4 h-4" />
{hasRunning ? 'View Running' : 'Start Run'}
</button>
<button
onClick={() => navigate('/automation')}
className="flex items-center gap-2 px-4 py-2 rounded-lg font-medium bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
>
<ClockIcon className="w-4 h-4" />
Schedule
</button>
<button
onClick={() => navigate('/writer/content')}
className="flex items-center gap-2 px-4 py-2 rounded-lg font-medium bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
>
<FileTextIcon className="w-4 h-4" />
Content
{pendingCounts.content > 0 && (
<span className="ml-1 px-1.5 py-0.5 text-xs rounded-full bg-warning-500 text-white">
{pendingCounts.content}
</span>
)}
</button>
<button
onClick={() => navigate('/writer/content?status=review')}
className="flex items-center gap-2 px-4 py-2 rounded-lg font-medium bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all"
>
<PaperPlaneIcon className="w-4 h-4" />
Review
{pendingCounts.review > 0 && (
<span className="ml-1 px-1.5 py-0.5 text-xs rounded-full bg-success-500 text-white">
{pendingCounts.review}
</span>
)}
</button>
{/* Clusters */}
<div className="bg-white dark:bg-gray-900 rounded-xl p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="size-8 rounded-lg bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center">
<GroupIcon className="size-4 text-purple-600 dark:text-purple-400" />
</div>
<div className="text-base font-bold text-gray-900 dark:text-white">Clusters</div>
</div>
<div className="text-right">
<div className="text-3xl font-bold text-purple-600">{metrics?.clusters?.total || 0}</div>
</div>
</div>
{renderMetricRow([
{ label: 'New:', value: metrics?.clusters?.new || 0, colorCls: 'text-purple-600' },
{ label: 'Mapped:', value: metrics?.clusters?.mapped || 0, colorCls: 'text-purple-600' },
])}
</div>
{/* Ideas */}
<div className="bg-white dark:bg-gray-900 rounded-xl p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="size-8 rounded-lg bg-warning-100 dark:bg-warning-900/30 flex items-center justify-center">
<BoltIcon className="size-4 text-warning-600 dark:text-warning-400" />
</div>
<div className="text-base font-bold text-gray-900 dark:text-white">Ideas</div>
</div>
<div className="text-right">
<div className="text-3xl font-bold text-warning-600">{metrics?.ideas?.total || 0}</div>
</div>
</div>
{renderMetricRow([
{ label: 'New:', value: metrics?.ideas?.new || 0, colorCls: 'text-warning-600' },
{ label: 'Queued:', value: metrics?.ideas?.queued || 0, colorCls: 'text-warning-600' },
{ label: 'Done:', value: metrics?.ideas?.completed || 0, colorCls: 'text-warning-600' },
])}
</div>
{/* Content */}
<div className="bg-white dark:bg-gray-900 rounded-xl p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="size-8 rounded-lg bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
<FileTextIcon className="size-4 text-success-600 dark:text-success-400" />
</div>
<div className="text-base font-bold text-gray-900 dark:text-white">Content</div>
</div>
<div className="text-right">
<div className="text-3xl font-bold text-success-600">{metrics?.content?.total || 0}</div>
</div>
</div>
{renderMetricRow([
{ label: 'Draft:', value: metrics?.content?.draft || 0, colorCls: 'text-success-600' },
{ label: 'Review:', value: metrics?.content?.review || 0, colorCls: 'text-success-600' },
{ label: 'Publish:', value: metrics?.content?.published || 0, colorCls: 'text-success-600' },
])}
</div>
{/* Images */}
<div className="bg-white dark:bg-gray-900 rounded-xl p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="size-8 rounded-lg bg-info-100 dark:bg-info-900/30 flex items-center justify-center">
<FileIcon className="size-4 text-info-600 dark:text-info-400" />
</div>
<div className="text-base font-bold text-gray-900 dark:text-white">Images</div>
</div>
<div className="text-right">
<div className="text-3xl font-bold text-info-600">{metrics?.images?.total || 0}</div>
</div>
</div>
{renderMetricRow([
{ label: 'Pending:', value: metrics?.images?.pending || 0, colorCls: 'text-info-600' },
])}
</div>
{/* Pipeline ready indicator */}
{(pendingCounts.keywords > 0 || pendingCounts.images > 0) && (
<span className="ml-auto text-sm text-gray-500 dark:text-gray-400">
Pipeline: {pendingCounts.keywords > 0 && `${pendingCounts.keywords} keywords`}
{pendingCounts.keywords > 0 && pendingCounts.images > 0 && ', '}
{pendingCounts.images > 0 && `${pendingCounts.images} pending images`}
</span>
)}
</div>
{/* Cost Estimation Card */}
{overviewStats ? (
<>
{/* Attention Items Alert */}
{overviewStats.attention_items && (
<AttentionItemsAlert items={overviewStats.attention_items} />
)}
{/* Production Summary - now uses actual_counts */}
<ProductionSummary
totals={totals}
actual_counts={actual_counts}
efficiency={productionStats?.efficiency}
loading={loading}
/>
{/* Statistics and Predictive Analysis */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{overviewStats.run_statistics && (
<RunStatisticsSummary statistics={overviewStats.run_statistics} loading={loading} />
)}
{overviewStats.predictive_analysis && (
<PredictiveCostAnalysis analysis={overviewStats.predictive_analysis} loading={loading} />
)}
</div>
</>
) : !loading && (
<div className="bg-white dark:bg-gray-900 rounded-xl p-6">
<p className="text-gray-600 dark:text-gray-400">Loading automation statistics...</p>
</div>
)}
{/* Enhanced Run History */}
{historyData && historyData.runs && (
<div>
<div className="mb-4">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Run History</h2>
<p className="text-sm text-gray-600 dark:text-gray-400">
Click on any run to view detailed analysis
</p>
</div>
<EnhancedRunHistory
runs={historyData.runs}
loading={loading}
currentPage={historyData.pagination?.page || 1}
totalPages={historyData.pagination?.total_pages || 1}
onPageChange={setHistoryPage}
/>
</div>
)}
{/* Fallback: Old Run History (if enhanced data not available) */}
{!historyData && activeSite && <RunHistory siteId={activeSite.id} />}
{/* Meaningful Run History - Full Width */}
<MeaningfulRunHistory
runs={productionStats?.meaningful_runs || []}
loading={loading}
maxRuns={10}
/>
</div>
</>
);

View File

@@ -2,7 +2,7 @@
* Automation Run Detail Page
* Comprehensive view of a single automation run
*/
import React, { useState, useEffect } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useSiteStore } from '../../store/siteStore';
import { automationService } from '../../services/automationService';
@@ -20,29 +20,74 @@ const AutomationRunDetail: React.FC = () => {
const { runId } = useParams<{ runId: string }>();
const navigate = useNavigate();
const { activeSite } = useSiteStore();
const toast = useToast();
const { error: toastError } = useToast();
const [loading, setLoading] = useState(true);
const [runDetail, setRunDetail] = useState<RunDetailResponse | null>(null);
const [error, setError] = useState<string | null>(null);
const lastRequestKey = useRef<string | null>(null);
useEffect(() => {
loadRunDetail();
}, [runId, activeSite]);
const loadRunDetail = async () => {
if (!activeSite || !runId) return;
const decodeTitle = (value: string | undefined | null) => {
if (!value) return '';
try {
setLoading(true);
const data = await automationService.getRunDetail(activeSite.id, runId);
setRunDetail(data);
} catch (error: any) {
console.error('Failed to load run detail', error);
toast.error(error.message || 'Failed to load run detail');
} finally {
setLoading(false);
return decodeURIComponent(value);
} catch {
return value;
}
};
const getDisplayTitle = () => {
const run = runDetail?.run;
if (!run) return 'Automation Run';
if (run.site_name) return run.site_name;
if (run.site_domain) return run.site_domain.replace('www.', '');
const decoded = decodeTitle(run.run_title);
if (decoded) return decoded;
if (run.run_number) return `Run #${run.run_number}`;
return 'Automation Run';
};
useEffect(() => {
const loadRunDetail = async () => {
if (!runId) {
setError('Missing run id');
setLoading(false);
return;
}
if (!activeSite) {
setError('Please select a site to view automation run details.');
setLoading(false);
return;
}
const requestKey = `${activeSite.id}-${runId}`;
if (lastRequestKey.current === requestKey) {
return;
}
lastRequestKey.current = requestKey;
try {
setLoading(true);
setError(null);
const data = await automationService.getRunDetail(activeSite.id, runId);
setRunDetail(data);
} catch (err: any) {
console.error('Failed to load run detail', err);
const message = err?.message === 'Internal server error'
? 'Run detail is temporarily unavailable (server error). Please try again later.'
: err?.message || 'Failed to load run detail';
setError(message);
toastError(message);
lastRequestKey.current = null;
} finally {
setLoading(false);
}
};
loadRunDetail();
}, [runId, activeSite, toastError]);
if (!activeSite) {
return (
<div className="p-6">
@@ -59,6 +104,14 @@ const AutomationRunDetail: React.FC = () => {
);
}
if (error) {
return (
<div className="p-6">
<p className="text-gray-600 dark:text-gray-400">{error}</p>
</div>
);
}
if (!runDetail) {
return (
<div className="p-6">
@@ -67,26 +120,71 @@ const AutomationRunDetail: React.FC = () => {
);
}
const displayTitle = getDisplayTitle();
const breadcrumbLabel = runDetail.run?.run_number ? `Run #${runDetail.run.run_number}` : displayTitle;
const normalizedRun = runDetail.run ? { ...runDetail.run, run_title: displayTitle } : null;
const stageSummary = (runDetail.stages || []).reduce(
(acc, stage) => {
acc.itemsProcessed += stage.items_processed || 0;
acc.itemsCreated += stage.items_created || 0;
if (stage.stage_number === 4) acc.contentCreated += stage.items_created || 0;
if (stage.stage_number === 6) acc.imagesGenerated += stage.items_created || 0;
return acc;
},
{ itemsProcessed: 0, itemsCreated: 0, contentCreated: 0, imagesGenerated: 0 }
);
const derivedInsights = [] as RunDetailResponse['insights'];
if (normalizedRun) {
if ((normalizedRun.total_credits_used || 0) > 0 && stageSummary.itemsCreated === 0) {
derivedInsights.push({
type: 'warning',
severity: 'warning',
message: 'Credits were spent but no outputs were recorded. Review stage errors and retry failed steps.',
});
}
if (normalizedRun.status === 'running') {
derivedInsights.push({
type: 'success',
severity: 'info',
message: `Run is currently active in stage ${normalizedRun.current_stage || 1}.`,
});
}
}
if ((runDetail.stages || []).some(stage => stage.status === 'failed')) {
const failedStage = runDetail.stages.find(stage => stage.status === 'failed');
derivedInsights.push({
type: 'error',
severity: 'error',
message: `Stage ${failedStage?.stage_number} failed. Review the stage details and error message for remediation.`,
});
}
const combinedInsights = runDetail.insights && runDetail.insights.length > 0
? [...runDetail.insights, ...derivedInsights]
: derivedInsights;
return (
<>
<PageMeta
title={`Run Detail - ${runDetail.run?.run_title || 'Automation Run'}`}
title={`Run Detail - ${displayTitle}`}
description="Detailed automation run analysis"
/>
<div className="space-y-6">
<PageHeader
title={runDetail.run?.run_title || 'Automation Run'}
breadcrumb={`Automation / Runs / ${runDetail.run?.run_title || 'Detail'}`}
title={displayTitle}
breadcrumb={`Automation / Runs / ${breadcrumbLabel || 'Detail'}`}
description="Comprehensive run analysis with stage breakdown and performance metrics"
/>
{/* Run Summary */}
{runDetail.run && <RunSummaryCard run={runDetail.run} />}
{normalizedRun && <RunSummaryCard run={normalizedRun} summary={stageSummary} />}
{/* Insights Panel */}
{runDetail.insights && runDetail.insights.length > 0 && (
<InsightsPanel insights={runDetail.insights} />
{combinedInsights.length > 0 && (
<InsightsPanel insights={combinedInsights} />
)}
{/* Two Column Layout */}