@defai.digital/cli 13.4.4 → 13.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/agents/architect.json +117 -0
- package/bundled/agents/auditor.json +114 -0
- package/bundled/agents/bug-hunter.json +128 -0
- package/bundled/agents/builder.json +128 -0
- package/bundled/agents/ceo.json +6 -1
- package/bundled/agents/executor.json +150 -0
- package/bundled/agents/fullstack.json +10 -2
- package/bundled/agents/operator.json +119 -0
- package/bundled/agents/researcher.json +42 -13
- package/bundled/agents/reviewer.json +90 -42
- package/bundled/templates/monorepo/contract-index.ts.hbs +7 -0
- package/bundled/templates/monorepo/contract-test.ts.hbs +130 -0
- package/bundled/templates/monorepo/contracts-package.json.hbs +29 -0
- package/bundled/templates/monorepo/domain-index.ts.hbs +115 -0
- package/bundled/templates/monorepo/domain-package.json.hbs +27 -0
- package/bundled/templates/monorepo/gitignore.hbs +32 -0
- package/bundled/templates/monorepo/invariants.md.hbs +43 -0
- package/bundled/templates/monorepo/package.json.hbs +28 -0
- package/bundled/templates/monorepo/pnpm-workspace.yaml.hbs +5 -0
- package/bundled/templates/monorepo/schema.ts.hbs +82 -0
- package/bundled/templates/monorepo/template.json +106 -0
- package/bundled/templates/monorepo/tsconfig.json.hbs +22 -0
- package/bundled/templates/standalone/contract-index.ts.hbs +5 -0
- package/bundled/templates/standalone/contract-test.ts.hbs +95 -0
- package/bundled/templates/standalone/contracts-root-index.ts.hbs +7 -0
- package/bundled/templates/standalone/domain-index.ts.hbs +6 -0
- package/bundled/templates/standalone/domain-repository.ts.hbs +44 -0
- package/bundled/templates/standalone/domain-service.ts.hbs +102 -0
- package/bundled/templates/standalone/gitignore.hbs +27 -0
- package/bundled/templates/standalone/invariants.md.hbs +35 -0
- package/bundled/templates/standalone/package.json.hbs +41 -0
- package/bundled/templates/standalone/schema.ts.hbs +61 -0
- package/bundled/templates/standalone/src-index.ts.hbs +11 -0
- package/bundled/templates/standalone/template.json +91 -0
- package/bundled/templates/standalone/tsconfig.json.hbs +20 -0
- package/bundled/templates/standalone/vitest.config.ts.hbs +8 -0
- package/bundled/workflows/adversarial-debate.yaml +222 -0
- package/bundled/workflows/analyst.yaml +115 -0
- package/bundled/workflows/assistant.yaml +74 -0
- package/bundled/workflows/code-review-discussion.yaml +166 -0
- package/bundled/workflows/code-reviewer.yaml +94 -0
- package/bundled/workflows/contract-first-project.yaml +356 -0
- package/bundled/workflows/debugger.yaml +107 -0
- package/bundled/workflows/designer.yaml +113 -0
- package/bundled/workflows/developer.yaml +105 -0
- package/bundled/workflows/discuss-step-examples.yaml +153 -0
- package/bundled/workflows/infrastructure-automation.yaml +283 -0
- package/bundled/workflows/ml-ab-testing.yaml +311 -0
- package/bundled/workflows/ml-experiment-tracker.yaml +150 -0
- package/bundled/workflows/ml-feature-engineering.yaml +242 -0
- package/bundled/workflows/ml-model-evaluation.yaml +234 -0
- package/bundled/workflows/ml-model-monitoring.yaml +227 -0
- package/bundled/workflows/ml-model-registry.yaml +232 -0
- package/bundled/workflows/mlops-deployment.yaml +267 -0
- package/bundled/workflows/mobile-development.yaml +312 -0
- package/bundled/workflows/multi-model-discussion.yaml +243 -0
- package/bundled/workflows/product-discovery.yaml +295 -0
- package/bundled/workflows/qa-specialist.yaml +116 -0
- package/bundled/workflows/refactoring.yaml +105 -0
- package/bundled/workflows/security-audit.yaml +135 -0
- package/bundled/workflows/std/analysis.yaml +190 -0
- package/bundled/workflows/std/code-review.yaml +117 -0
- package/bundled/workflows/std/debugging.yaml +155 -0
- package/bundled/workflows/std/documentation.yaml +180 -0
- package/bundled/workflows/std/implementation.yaml +197 -0
- package/bundled/workflows/std/refactoring.yaml +180 -0
- package/bundled/workflows/std/testing.yaml +200 -0
- package/bundled/workflows/strategic-planning.yaml +235 -0
- package/bundled/workflows/technology-research.yaml +239 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +10 -6
- package/dist/bootstrap.js.map +1 -1
- package/dist/commands/discuss.d.ts.map +1 -1
- package/dist/commands/discuss.js +4 -1
- package/dist/commands/discuss.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +65 -5
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/monitor.d.ts.map +1 -1
- package/dist/commands/monitor.js +29 -1
- package/dist/commands/monitor.js.map +1 -1
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +6 -3
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +119 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts +10 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +151 -49
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +1 -43
- package/dist/commands/update.js.map +1 -1
- package/dist/web/api.d.ts +18 -0
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +480 -39
- package/dist/web/api.js.map +1 -1
- package/dist/web/dashboard.d.ts.map +1 -1
- package/dist/web/dashboard.js +1449 -132
- package/dist/web/dashboard.js.map +1 -1
- package/package.json +21 -21
package/dist/web/dashboard.js
CHANGED
|
@@ -431,7 +431,6 @@ export function createDashboardHTML() {
|
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
.provider-latency.fast { color: var(--accent-green); }
|
|
434
|
-
.provider-latency.medium { color: var(--accent-yellow); }
|
|
435
434
|
.provider-latency.slow { color: var(--accent-red); }
|
|
436
435
|
|
|
437
436
|
/* Agent list */
|
|
@@ -553,6 +552,26 @@ export function createDashboardHTML() {
|
|
|
553
552
|
cursor: pointer;
|
|
554
553
|
}
|
|
555
554
|
|
|
555
|
+
.refresh-button {
|
|
556
|
+
padding: 8px 16px;
|
|
557
|
+
background: var(--accent-blue);
|
|
558
|
+
border: none;
|
|
559
|
+
border-radius: 6px;
|
|
560
|
+
color: white;
|
|
561
|
+
font-size: 14px;
|
|
562
|
+
cursor: pointer;
|
|
563
|
+
transition: background 0.2s, opacity 0.2s;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.refresh-button:hover:not(:disabled) {
|
|
567
|
+
background: var(--accent-blue-hover, #2563eb);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.refresh-button:disabled {
|
|
571
|
+
opacity: 0.6;
|
|
572
|
+
cursor: not-allowed;
|
|
573
|
+
}
|
|
574
|
+
|
|
556
575
|
.history-table {
|
|
557
576
|
width: 100%;
|
|
558
577
|
border-collapse: collapse;
|
|
@@ -1485,6 +1504,17 @@ export function createDashboardHTML() {
|
|
|
1485
1504
|
.conversation-message .metadata-badges {
|
|
1486
1505
|
opacity: 1 !important;
|
|
1487
1506
|
}
|
|
1507
|
+
|
|
1508
|
+
.conversation-message .message-bubble {
|
|
1509
|
+
padding: 16px !important;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/* Remove border from last conversation message */
|
|
1514
|
+
.conversation-message:last-child {
|
|
1515
|
+
border-bottom: none !important;
|
|
1516
|
+
margin-bottom: 0 !important;
|
|
1517
|
+
padding-bottom: 0 !important;
|
|
1488
1518
|
}
|
|
1489
1519
|
|
|
1490
1520
|
@media (max-width: 480px) {
|
|
@@ -1621,6 +1651,10 @@ export function createDashboardHTML() {
|
|
|
1621
1651
|
'discussion.provider': '\\u25C6', // ◆ Provider response
|
|
1622
1652
|
'discussion.consensus': '\\u2726', // ✦ Consensus reached
|
|
1623
1653
|
'discussion.end': '\\u2713',
|
|
1654
|
+
// Workflow/Agent step events
|
|
1655
|
+
'workflow.start': '\\u25B6',
|
|
1656
|
+
'workflow.step': '\\u2699', // ⚙ Step execution
|
|
1657
|
+
'workflow.end': '\\u25A0',
|
|
1624
1658
|
'memory.write': '\\u270E',
|
|
1625
1659
|
'memory.read': '\\u25B7',
|
|
1626
1660
|
'error': '\\u26A0',
|
|
@@ -1948,9 +1982,10 @@ export function createDashboardHTML() {
|
|
|
1948
1982
|
};
|
|
1949
1983
|
|
|
1950
1984
|
// Render expanded details
|
|
1951
|
-
const renderDetails = (startEvent, endEvent) => {
|
|
1985
|
+
const renderDetails = (startEvent, endEvent, eventType) => {
|
|
1952
1986
|
const payload = { ...(startEvent?.payload || {}), ...(endEvent?.payload || {}) };
|
|
1953
1987
|
const context = { ...(startEvent?.context || {}), ...(endEvent?.context || {}) };
|
|
1988
|
+
const type = eventType || startEvent?.type || endEvent?.type || '';
|
|
1954
1989
|
|
|
1955
1990
|
const detailItems = [];
|
|
1956
1991
|
|
|
@@ -1989,6 +2024,69 @@ export function createDashboardHTML() {
|
|
|
1989
2024
|
detailItems.push({ label: 'Votes', value: voteStr });
|
|
1990
2025
|
}
|
|
1991
2026
|
|
|
2027
|
+
// workflow.step event details (Agent execution steps)
|
|
2028
|
+
if (type === 'workflow.step') {
|
|
2029
|
+
if (payload.stepId) detailItems.push({ label: 'Step', value: payload.stepId });
|
|
2030
|
+
if (payload.stepIndex !== undefined) detailItems.push({ label: 'Index', value: \`#\${payload.stepIndex + 1}\` });
|
|
2031
|
+
if (payload.provider) detailItems.push({ label: 'Provider', value: payload.provider });
|
|
2032
|
+
if (context.tokenUsage) {
|
|
2033
|
+
const usage = context.tokenUsage;
|
|
2034
|
+
if (usage.total) detailItems.push({ label: 'Tokens', value: usage.total.toLocaleString() });
|
|
2035
|
+
else if (usage.input || usage.output) {
|
|
2036
|
+
detailItems.push({ label: 'Tokens', value: \`\${(usage.input || 0).toLocaleString()} in / \${(usage.output || 0).toLocaleString()} out\` });
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// Agent-specific details (from enriched run.end payload)
|
|
2042
|
+
if (payload.agentDisplayName) detailItems.push({ label: 'Agent', value: payload.agentDisplayName });
|
|
2043
|
+
if (payload.inputTask && !payload.topic) {
|
|
2044
|
+
const taskPreview = String(payload.inputTask).substring(0, 150);
|
|
2045
|
+
detailItems.push({ label: 'Task', value: taskPreview + (payload.inputTask.length > 150 ? '...' : '') });
|
|
2046
|
+
}
|
|
2047
|
+
if (payload.tokenUsage && typeof payload.tokenUsage === 'object') {
|
|
2048
|
+
const usage = payload.tokenUsage;
|
|
2049
|
+
if (usage.total) detailItems.push({ label: 'Tokens', value: usage.total.toLocaleString() });
|
|
2050
|
+
else if (usage.input || usage.output) {
|
|
2051
|
+
detailItems.push({ label: 'Tokens', value: \`\${(usage.input || 0).toLocaleString()} in / \${(usage.output || 0).toLocaleString()} out\` });
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
// Research-specific details
|
|
2056
|
+
if (payload.tool?.startsWith('research')) {
|
|
2057
|
+
if (payload.query) {
|
|
2058
|
+
const queryPreview = String(payload.query).substring(0, 150);
|
|
2059
|
+
detailItems.push({ label: 'Query', value: queryPreview + (payload.query.length > 150 ? '...' : '') });
|
|
2060
|
+
}
|
|
2061
|
+
if (payload.sourceCount !== undefined) detailItems.push({ label: 'Sources', value: payload.sourceCount });
|
|
2062
|
+
if (payload.confidence !== undefined) detailItems.push({ label: 'Confidence', value: \`\${(payload.confidence * 100).toFixed(0)}%\` });
|
|
2063
|
+
if (payload.url) detailItems.push({ label: 'URL', value: payload.url });
|
|
2064
|
+
if (payload.title) detailItems.push({ label: 'Title', value: payload.title });
|
|
2065
|
+
if (payload.reliability) detailItems.push({ label: 'Reliability', value: payload.reliability });
|
|
2066
|
+
if (payload.contentLength) detailItems.push({ label: 'Content', value: \`\${payload.contentLength.toLocaleString()} chars\` });
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
// Review-specific details
|
|
2070
|
+
if (payload.tool?.startsWith('review')) {
|
|
2071
|
+
if (payload.focus) detailItems.push({ label: 'Focus', value: payload.focus });
|
|
2072
|
+
if (payload.summary?.verdict) detailItems.push({ label: 'Verdict', value: payload.summary.verdict });
|
|
2073
|
+
if (payload.summary?.healthScore !== undefined) {
|
|
2074
|
+
detailItems.push({ label: 'Health', value: \`\${(payload.summary.healthScore * 100).toFixed(0)}%\` });
|
|
2075
|
+
}
|
|
2076
|
+
if (payload.summary?.bySeverity) {
|
|
2077
|
+
const sev = payload.summary.bySeverity;
|
|
2078
|
+
const parts = [];
|
|
2079
|
+
if (sev.critical) parts.push(\`\${sev.critical} critical\`);
|
|
2080
|
+
if (sev.warning) parts.push(\`\${sev.warning} warning\`);
|
|
2081
|
+
if (sev.suggestion) parts.push(\`\${sev.suggestion} suggestion\`);
|
|
2082
|
+
if (sev.note) parts.push(\`\${sev.note} note\`);
|
|
2083
|
+
if (parts.length > 0) detailItems.push({ label: 'Issues', value: parts.join(', ') });
|
|
2084
|
+
}
|
|
2085
|
+
if (payload.filesReviewedCount !== undefined) detailItems.push({ label: 'Files', value: payload.filesReviewedCount });
|
|
2086
|
+
if (payload.linesAnalyzed !== undefined) detailItems.push({ label: 'Lines', value: payload.linesAnalyzed.toLocaleString() });
|
|
2087
|
+
if (payload.providerId) detailItems.push({ label: 'Provider', value: payload.providerId });
|
|
2088
|
+
}
|
|
2089
|
+
|
|
1992
2090
|
if (payload.error) detailItems.push({ label: 'Error', value: typeof payload.error === 'object' ? payload.error.message : payload.error, isError: true });
|
|
1993
2091
|
|
|
1994
2092
|
if (detailItems.length === 0) return null;
|
|
@@ -2137,7 +2235,7 @@ export function createDashboardHTML() {
|
|
|
2137
2235
|
</div>
|
|
2138
2236
|
)}
|
|
2139
2237
|
|
|
2140
|
-
{isExpanded && renderDetails(event, endEvent)}
|
|
2238
|
+
{isExpanded && renderDetails(event, endEvent, baseType)}
|
|
2141
2239
|
</div>
|
|
2142
2240
|
</div>
|
|
2143
2241
|
);
|
|
@@ -2609,57 +2707,104 @@ export function createDashboardHTML() {
|
|
|
2609
2707
|
}
|
|
2610
2708
|
// For 'discuss' command: topic as input, responses grouped by provider with multi-round support
|
|
2611
2709
|
else if (traceData.commandType === 'discuss') {
|
|
2612
|
-
// Get the discussion topic
|
|
2710
|
+
// Get the discussion topic
|
|
2613
2711
|
const topic = traceData.input?.topic;
|
|
2614
|
-
const configuredRounds = traceData.input?.rounds || 1;
|
|
2615
|
-
|
|
2616
|
-
// Provider responses: {provider: [response1, response2, ...]} or {provider: "single response"}
|
|
2617
|
-
const responses = traceData.output?.responses;
|
|
2618
|
-
if (responses && typeof responses === 'object') {
|
|
2619
|
-
Object.entries(responses).forEach(([provider, providerResponses]) => {
|
|
2620
|
-
const exchanges = [];
|
|
2621
2712
|
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2713
|
+
// Use providerConversations for real prompt/response content (preferred)
|
|
2714
|
+
// Falls back to output.responses if providerConversations not available
|
|
2715
|
+
if (traceData.providerConversations && traceData.providerConversations.length > 0) {
|
|
2716
|
+
// Group by provider
|
|
2717
|
+
const providerMap = {};
|
|
2718
|
+
traceData.providerConversations.forEach(conv => {
|
|
2719
|
+
if (!providerMap[conv.provider]) {
|
|
2720
|
+
providerMap[conv.provider] = [];
|
|
2721
|
+
}
|
|
2722
|
+
providerMap[conv.provider].push(conv);
|
|
2723
|
+
});
|
|
2626
2724
|
|
|
2627
|
-
|
|
2628
|
-
|
|
2725
|
+
// Build exchanges for each provider
|
|
2726
|
+
Object.entries(providerMap).forEach(([provider, conversations]) => {
|
|
2727
|
+
const exchanges = [];
|
|
2728
|
+
// Sort by round then timestamp
|
|
2729
|
+
const sorted = conversations.sort((a, b) => {
|
|
2730
|
+
if (a.round !== b.round) return a.round - b.round;
|
|
2731
|
+
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
|
|
2732
|
+
});
|
|
2629
2733
|
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2734
|
+
sorted.forEach((conv, idx) => {
|
|
2735
|
+
// Add the actual prompt
|
|
2736
|
+
if (conv.prompt) {
|
|
2633
2737
|
exchanges.push({
|
|
2634
|
-
input:
|
|
2738
|
+
input: conv.prompt,
|
|
2635
2739
|
output: null,
|
|
2636
|
-
roundLabel: \`Ask \${
|
|
2637
|
-
});
|
|
2638
|
-
} else {
|
|
2639
|
-
// Rounds 2+: Cross-discussion prompt
|
|
2640
|
-
exchanges.push({
|
|
2641
|
-
input: \`Round \${roundNum}: Respond to other providers' perspectives from the previous round.\`,
|
|
2642
|
-
output: null,
|
|
2643
|
-
roundLabel: \`Ask \${roundNum}\`
|
|
2740
|
+
roundLabel: \`Ask \${conv.round}\`
|
|
2644
2741
|
});
|
|
2645
2742
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
if (response) {
|
|
2743
|
+
// Add the response
|
|
2744
|
+
if (conv.content) {
|
|
2649
2745
|
exchanges.push({
|
|
2650
2746
|
input: null,
|
|
2651
|
-
output:
|
|
2652
|
-
|
|
2747
|
+
output: conv.content,
|
|
2748
|
+
latencyMs: conv.durationMs,
|
|
2749
|
+
roundLabel: \`Reply \${conv.round}\`
|
|
2653
2750
|
});
|
|
2654
2751
|
}
|
|
2655
2752
|
});
|
|
2656
2753
|
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
});
|
|
2754
|
+
if (exchanges.length > 0) {
|
|
2755
|
+
groups.push({ provider, exchanges });
|
|
2756
|
+
}
|
|
2661
2757
|
});
|
|
2662
2758
|
}
|
|
2759
|
+
// Fallback: use output.responses (legacy format)
|
|
2760
|
+
else {
|
|
2761
|
+
const responses = traceData.output?.responses;
|
|
2762
|
+
if (responses && typeof responses === 'object') {
|
|
2763
|
+
Object.entries(responses).forEach(([provider, providerResponses]) => {
|
|
2764
|
+
const exchanges = [];
|
|
2765
|
+
|
|
2766
|
+
// Handle both array (multi-round) and string (single response) formats
|
|
2767
|
+
const responseArray = Array.isArray(providerResponses)
|
|
2768
|
+
? providerResponses
|
|
2769
|
+
: [providerResponses];
|
|
2770
|
+
|
|
2771
|
+
responseArray.forEach((response, roundIndex) => {
|
|
2772
|
+
const roundNum = roundIndex + 1;
|
|
2773
|
+
|
|
2774
|
+
// Add the prompt/ask for this round
|
|
2775
|
+
if (roundNum === 1) {
|
|
2776
|
+
// Round 1: The original topic
|
|
2777
|
+
exchanges.push({
|
|
2778
|
+
input: topic || 'Discussion topic',
|
|
2779
|
+
output: null,
|
|
2780
|
+
roundLabel: \`Ask \${roundNum}\`
|
|
2781
|
+
});
|
|
2782
|
+
} else {
|
|
2783
|
+
// Rounds 2+: Cross-discussion prompt
|
|
2784
|
+
exchanges.push({
|
|
2785
|
+
input: \`Round \${roundNum}: Respond to other providers' perspectives from the previous round.\`,
|
|
2786
|
+
output: null,
|
|
2787
|
+
roundLabel: \`Ask \${roundNum}\`
|
|
2788
|
+
});
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
// Add the response/reply for this round
|
|
2792
|
+
if (response) {
|
|
2793
|
+
exchanges.push({
|
|
2794
|
+
input: null,
|
|
2795
|
+
output: response,
|
|
2796
|
+
roundLabel: \`Reply \${roundNum}\`
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
});
|
|
2800
|
+
|
|
2801
|
+
groups.push({
|
|
2802
|
+
provider: provider,
|
|
2803
|
+
exchanges
|
|
2804
|
+
});
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2663
2808
|
|
|
2664
2809
|
// Show synthesis/consensus if available
|
|
2665
2810
|
const synthesisText = traceData.output?.synthesis;
|
|
@@ -2694,20 +2839,56 @@ export function createDashboardHTML() {
|
|
|
2694
2839
|
}
|
|
2695
2840
|
// For 'agent' command: task as initial input, then workflow steps
|
|
2696
2841
|
else if (traceData.commandType === 'agent') {
|
|
2697
|
-
//
|
|
2842
|
+
// Get trace-level provider as fallback (from run.end finalProvider)
|
|
2843
|
+
const traceProvider = traceData.provider || traceData.output?.finalProvider;
|
|
2844
|
+
|
|
2845
|
+
// Initial task - show agent name and provider
|
|
2698
2846
|
const task = traceData.input?.task;
|
|
2699
2847
|
if (task) {
|
|
2700
2848
|
const taskContent = typeof task === 'object' ? task.task || JSON.stringify(task) : task;
|
|
2849
|
+
const agentHeader = traceProvider
|
|
2850
|
+
? \`Agent: \${traceData.input?.agentId || 'unknown'} (via \${traceProvider})\`
|
|
2851
|
+
: \`Agent: \${traceData.input?.agentId || 'unknown'}\`;
|
|
2701
2852
|
groups.push({
|
|
2702
|
-
provider:
|
|
2853
|
+
provider: agentHeader,
|
|
2703
2854
|
exchanges: [{ input: taskContent, output: null }]
|
|
2704
2855
|
});
|
|
2705
2856
|
}
|
|
2706
2857
|
|
|
2707
|
-
//
|
|
2708
|
-
if (traceData.
|
|
2858
|
+
// Use agentStepConversations for real LLM content (preferred, from workflow.step events)
|
|
2859
|
+
if (traceData.agentStepConversations && traceData.agentStepConversations.length > 0) {
|
|
2860
|
+
traceData.agentStepConversations.forEach((step, idx) => {
|
|
2861
|
+
// Use step provider, fallback to trace-level provider, then 'unknown'
|
|
2862
|
+
const stepProvider = step.provider || traceProvider || 'unknown';
|
|
2863
|
+
const exchanges = [];
|
|
2864
|
+
|
|
2865
|
+
// Add response content if available
|
|
2866
|
+
if (step.content) {
|
|
2867
|
+
exchanges.push({
|
|
2868
|
+
input: null,
|
|
2869
|
+
output: step.content,
|
|
2870
|
+
latencyMs: step.durationMs,
|
|
2871
|
+
success: step.success,
|
|
2872
|
+
error: step.error,
|
|
2873
|
+
tokenCount: step.tokenCount,
|
|
2874
|
+
roundLabel: \`Step \${step.stepIndex + 1}\`
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
if (exchanges.length > 0) {
|
|
2879
|
+
groups.push({
|
|
2880
|
+
provider: stepProvider,
|
|
2881
|
+
stepId: step.stepId || \`Step \${idx + 1}\`,
|
|
2882
|
+
exchanges
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
// Fallback: use workflowSteps (legacy format without LLM content)
|
|
2888
|
+
else if (traceData.workflowSteps && traceData.workflowSteps.length > 0) {
|
|
2709
2889
|
traceData.workflowSteps.forEach((step, idx) => {
|
|
2710
|
-
|
|
2890
|
+
// Use step provider, fallback to trace-level provider, then 'unknown'
|
|
2891
|
+
const stepProvider = step.provider || traceProvider || 'unknown';
|
|
2711
2892
|
const exchanges = [];
|
|
2712
2893
|
|
|
2713
2894
|
// Add prompt if available
|
|
@@ -2741,18 +2922,158 @@ export function createDashboardHTML() {
|
|
|
2741
2922
|
});
|
|
2742
2923
|
}
|
|
2743
2924
|
|
|
2744
|
-
// Final result
|
|
2925
|
+
// Final result (or use finalContent from run.end payload)
|
|
2745
2926
|
const result = traceData.output?.result;
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2927
|
+
const finalContent = traceData.output?.finalContent;
|
|
2928
|
+
const displayResult = result || finalContent;
|
|
2929
|
+
if (displayResult) {
|
|
2930
|
+
const resultContent = typeof displayResult === 'object'
|
|
2931
|
+
? (displayResult.content || JSON.stringify(displayResult, null, 2))
|
|
2932
|
+
: displayResult;
|
|
2750
2933
|
groups.push({
|
|
2751
2934
|
provider: 'Final Result',
|
|
2752
2935
|
exchanges: [{ input: null, output: resultContent }]
|
|
2753
2936
|
});
|
|
2754
2937
|
}
|
|
2755
2938
|
}
|
|
2939
|
+
// For 'review' command: show review summary and findings
|
|
2940
|
+
else if (traceData.commandType === 'review' || traceData.workflowId?.startsWith('review')) {
|
|
2941
|
+
const output = traceData.output || {};
|
|
2942
|
+
const input = traceData.input || {};
|
|
2943
|
+
const summary = output.summary;
|
|
2944
|
+
const comments = output.comments || [];
|
|
2945
|
+
const filesReviewed = output.filesReviewed || [];
|
|
2946
|
+
// Get provider from output or top-level trace data
|
|
2947
|
+
const reviewProvider = output.providerId || traceData.provider;
|
|
2948
|
+
|
|
2949
|
+
// Review parameters
|
|
2950
|
+
const paths = input.paths || [];
|
|
2951
|
+
const focus = input.focus || 'all';
|
|
2952
|
+
const pathsText = Array.isArray(paths) ? paths.join(', ') : paths;
|
|
2953
|
+
|
|
2954
|
+
// Show provider in the header if available
|
|
2955
|
+
const headerLabel = reviewProvider
|
|
2956
|
+
? \`Code Review (\${focus}) via \${reviewProvider}\`
|
|
2957
|
+
: \`Code Review (\${focus})\`;
|
|
2958
|
+
|
|
2959
|
+
groups.push({
|
|
2960
|
+
provider: headerLabel,
|
|
2961
|
+
exchanges: [{
|
|
2962
|
+
input: \`Review \${pathsText}\`,
|
|
2963
|
+
output: null
|
|
2964
|
+
}]
|
|
2965
|
+
});
|
|
2966
|
+
|
|
2967
|
+
// Summary section
|
|
2968
|
+
if (summary) {
|
|
2969
|
+
const severityLine = summary.bySeverity
|
|
2970
|
+
? \`Critical: \${summary.bySeverity.critical || 0}, Warning: \${summary.bySeverity.warning || 0}, Suggestion: \${summary.bySeverity.suggestion || 0}, Note: \${summary.bySeverity.note || 0}\`
|
|
2971
|
+
: '';
|
|
2972
|
+
const summaryText = [
|
|
2973
|
+
\`**Verdict**: \${summary.verdict || 'N/A'}\`,
|
|
2974
|
+
\`**Health Score**: \${summary.healthScore !== undefined ? (summary.healthScore * 100).toFixed(0) + '%' : 'N/A'}\`,
|
|
2975
|
+
severityLine ? \`**Issues**: \${severityLine}\` : '',
|
|
2976
|
+
\`**Files Reviewed**: \${output.filesReviewedCount || filesReviewed.length}\`,
|
|
2977
|
+
\`**Lines Analyzed**: \${output.linesAnalyzed?.toLocaleString() || 'N/A'}\`,
|
|
2978
|
+
].filter(Boolean).join('\\n');
|
|
2979
|
+
|
|
2980
|
+
groups.push({
|
|
2981
|
+
provider: 'Summary',
|
|
2982
|
+
exchanges: [{ input: null, output: summaryText }]
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
// Issues found
|
|
2987
|
+
if (comments.length > 0) {
|
|
2988
|
+
const issuesText = comments.map((c, idx) => {
|
|
2989
|
+
const severityIcon = c.severity === 'critical' ? '🔴' :
|
|
2990
|
+
c.severity === 'warning' ? '🟠' :
|
|
2991
|
+
c.severity === 'suggestion' ? '🟡' : '🔵';
|
|
2992
|
+
const location = c.file ? \`\${c.file}\${c.line ? ':' + c.line : ''}\` : '';
|
|
2993
|
+
return [
|
|
2994
|
+
\`### \${severityIcon} \${c.title || 'Issue ' + (idx + 1)}\`,
|
|
2995
|
+
location ? \`**Location**: \${location}\` : '',
|
|
2996
|
+
c.category ? \`**Category**: \${c.category}\` : '',
|
|
2997
|
+
c.body ? \`\\n\${c.body}\` : '',
|
|
2998
|
+
c.suggestion ? \`\\n**Suggestion**: \${c.suggestion}\` : '',
|
|
2999
|
+
c.confidence ? \`\\n*Confidence: \${(c.confidence * 100).toFixed(0)}%*\` : '',
|
|
3000
|
+
].filter(Boolean).join('\\n');
|
|
3001
|
+
}).join('\\n\\n---\\n\\n');
|
|
3002
|
+
|
|
3003
|
+
groups.push({
|
|
3004
|
+
provider: \`Issues Found (\${output.commentCount || comments.length})\`,
|
|
3005
|
+
exchanges: [{ input: null, output: issuesText }]
|
|
3006
|
+
});
|
|
3007
|
+
} else if (output.commentCount === 0) {
|
|
3008
|
+
groups.push({
|
|
3009
|
+
provider: 'Result',
|
|
3010
|
+
exchanges: [{ input: null, output: '✅ No issues found! Code looks good.' }]
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
// Files reviewed
|
|
3015
|
+
if (filesReviewed.length > 0 && filesReviewed.length <= 20) {
|
|
3016
|
+
const filesText = filesReviewed.map((f, idx) => \`\${idx + 1}. \${f}\`).join('\\n');
|
|
3017
|
+
groups.push({
|
|
3018
|
+
provider: 'Files Reviewed',
|
|
3019
|
+
exchanges: [{ input: null, output: filesText }]
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
// For 'research' command: query as input, sources and synthesis as output
|
|
3024
|
+
else if (traceData.commandType === 'research' || traceData.workflowId?.startsWith('research')) {
|
|
3025
|
+
const output = traceData.output || {};
|
|
3026
|
+
const query = output.query || traceData.input?.query;
|
|
3027
|
+
const sources = output.sources || [];
|
|
3028
|
+
const synthesis = output.synthesis;
|
|
3029
|
+
const confidence = output.confidence;
|
|
3030
|
+
const codeExamples = output.codeExamples || [];
|
|
3031
|
+
|
|
3032
|
+
// Research query
|
|
3033
|
+
if (query) {
|
|
3034
|
+
groups.push({
|
|
3035
|
+
provider: 'Research Query',
|
|
3036
|
+
exchanges: [{ input: query, output: null }]
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
// Sources found
|
|
3041
|
+
if (sources.length > 0) {
|
|
3042
|
+
const sourcesText = sources.map((s, idx) => {
|
|
3043
|
+
const reliability = s.reliability ? \` [\${s.reliability}]\` : '';
|
|
3044
|
+
const relevance = s.relevanceScore ? \` (relevance: \${(s.relevanceScore * 100).toFixed(0)}%)\` : '';
|
|
3045
|
+
return \`\${idx + 1}. **\${s.title || 'Untitled'}**\${reliability}\${relevance}\\n \${s.url || 'No URL'}\\n \${s.snippet ? s.snippet.substring(0, 200) + (s.snippet.length > 200 ? '...' : '') : ''}\`;
|
|
3046
|
+
}).join('\\n\\n');
|
|
3047
|
+
|
|
3048
|
+
groups.push({
|
|
3049
|
+
provider: \`Sources (\${sources.length})\`,
|
|
3050
|
+
exchanges: [{ input: null, output: sourcesText }]
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
// Code examples
|
|
3055
|
+
if (codeExamples.length > 0) {
|
|
3056
|
+
const codeText = codeExamples.map((code, idx) => {
|
|
3057
|
+
const lang = code.language || 'code';
|
|
3058
|
+
const content = typeof code === 'string' ? code : (code.content || code.code || JSON.stringify(code));
|
|
3059
|
+
return \`**Example \${idx + 1}** (\${lang}):\\n\\\`\\\`\\\`\${lang}\\n\${content}\\n\\\`\\\`\\\`\`;
|
|
3060
|
+
}).join('\\n\\n');
|
|
3061
|
+
|
|
3062
|
+
groups.push({
|
|
3063
|
+
provider: \`Code Examples (\${codeExamples.length})\`,
|
|
3064
|
+
exchanges: [{ input: null, output: codeText }]
|
|
3065
|
+
});
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
// Synthesis result
|
|
3069
|
+
if (synthesis) {
|
|
3070
|
+
const confidenceText = confidence !== undefined ? \` (Confidence: \${(confidence * 100).toFixed(0)}%)\` : '';
|
|
3071
|
+
groups.push({
|
|
3072
|
+
provider: 'Synthesis' + confidenceText,
|
|
3073
|
+
exchanges: [{ input: 'Synthesized answer from all sources', output: synthesis }]
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
2756
3077
|
|
|
2757
3078
|
return groups;
|
|
2758
3079
|
};
|
|
@@ -2966,24 +3287,27 @@ export function createDashboardHTML() {
|
|
|
2966
3287
|
onMouseEnter={() => setShowMeta(true)}
|
|
2967
3288
|
onMouseLeave={() => setShowMeta(false)}
|
|
2968
3289
|
style={{
|
|
2969
|
-
marginBottom: '
|
|
3290
|
+
marginBottom: '32px',
|
|
3291
|
+
paddingBottom: '24px',
|
|
3292
|
+
borderBottom: '1px solid var(--border-color)',
|
|
2970
3293
|
}}
|
|
2971
3294
|
>
|
|
2972
3295
|
<div className="message-container" style={{
|
|
2973
3296
|
display: 'flex',
|
|
2974
|
-
gap: '
|
|
3297
|
+
gap: '16px',
|
|
2975
3298
|
}}>
|
|
2976
3299
|
{/* Avatar - hidden on mobile via CSS class */}
|
|
2977
3300
|
<div className="message-avatar" style={{
|
|
2978
|
-
width: '
|
|
2979
|
-
height: '
|
|
3301
|
+
width: '44px',
|
|
3302
|
+
height: '44px',
|
|
2980
3303
|
borderRadius: '50%',
|
|
2981
|
-
background: isAsk ? 'rgba(88, 166, 255, 0.
|
|
3304
|
+
background: isAsk ? 'rgba(88, 166, 255, 0.2)' : 'rgba(63, 185, 80, 0.2)',
|
|
2982
3305
|
display: 'flex',
|
|
2983
3306
|
alignItems: 'center',
|
|
2984
3307
|
justifyContent: 'center',
|
|
2985
|
-
fontSize: '
|
|
3308
|
+
fontSize: '20px',
|
|
2986
3309
|
flexShrink: 0,
|
|
3310
|
+
border: isAsk ? '2px solid rgba(88, 166, 255, 0.3)' : '2px solid rgba(63, 185, 80, 0.3)',
|
|
2987
3311
|
}}>
|
|
2988
3312
|
{isAsk ? '👤' : '🤖'}
|
|
2989
3313
|
</div>
|
|
@@ -2994,16 +3318,18 @@ export function createDashboardHTML() {
|
|
|
2994
3318
|
<div style={{
|
|
2995
3319
|
display: 'flex',
|
|
2996
3320
|
alignItems: 'center',
|
|
2997
|
-
gap: '
|
|
2998
|
-
marginBottom: '
|
|
3321
|
+
gap: '12px',
|
|
3322
|
+
marginBottom: '12px',
|
|
2999
3323
|
flexWrap: 'wrap',
|
|
3000
3324
|
}}>
|
|
3001
3325
|
<span style={{
|
|
3002
|
-
fontSize: '
|
|
3003
|
-
fontWeight:
|
|
3326
|
+
fontSize: '14px',
|
|
3327
|
+
fontWeight: 700,
|
|
3004
3328
|
color: isAsk ? 'var(--accent-blue)' : 'var(--accent-green)',
|
|
3329
|
+
textTransform: 'uppercase',
|
|
3330
|
+
letterSpacing: '0.05em',
|
|
3005
3331
|
}}>
|
|
3006
|
-
{label}
|
|
3332
|
+
{isAsk ? '📤 ' : '📥 '}{label}
|
|
3007
3333
|
</span>
|
|
3008
3334
|
|
|
3009
3335
|
{/* Metadata badges - hidden by default, show on hover */}
|
|
@@ -3042,19 +3368,21 @@ export function createDashboardHTML() {
|
|
|
3042
3368
|
<div
|
|
3043
3369
|
className="message-bubble"
|
|
3044
3370
|
style={{
|
|
3045
|
-
background: isAsk ? 'rgba(88, 166, 255, 0.
|
|
3046
|
-
border: isAsk ? '1px solid rgba(88, 166, 255, 0.
|
|
3047
|
-
borderLeft: \`
|
|
3048
|
-
padding: '
|
|
3371
|
+
background: isAsk ? 'rgba(88, 166, 255, 0.08)' : 'rgba(63, 185, 80, 0.04)',
|
|
3372
|
+
border: isAsk ? '1px solid rgba(88, 166, 255, 0.2)' : '1px solid rgba(63, 185, 80, 0.15)',
|
|
3373
|
+
borderLeft: \`5px solid \${isAsk ? 'var(--accent-blue)' : 'var(--accent-green)'}\`,
|
|
3374
|
+
padding: '20px 24px',
|
|
3049
3375
|
borderRadius: '4px 12px 12px 4px',
|
|
3050
|
-
maxWidth: '
|
|
3376
|
+
maxWidth: '100%',
|
|
3377
|
+
boxShadow: isAsk ? '0 2px 8px rgba(88, 166, 255, 0.08)' : '0 2px 8px rgba(0, 0, 0, 0.1)',
|
|
3051
3378
|
}}
|
|
3052
3379
|
>
|
|
3053
3380
|
{/* Rendered content with code blocks */}
|
|
3054
3381
|
<div style={{
|
|
3055
|
-
fontSize: '
|
|
3056
|
-
lineHeight: '1.
|
|
3382
|
+
fontSize: '15px',
|
|
3383
|
+
lineHeight: '1.8',
|
|
3057
3384
|
color: 'var(--text-primary)',
|
|
3385
|
+
letterSpacing: '0.01em',
|
|
3058
3386
|
}}>
|
|
3059
3387
|
{contentParts.map((part, idx) => {
|
|
3060
3388
|
if (part.type === 'code') {
|
|
@@ -3064,6 +3392,7 @@ export function createDashboardHTML() {
|
|
|
3064
3392
|
<div key={idx} style={{
|
|
3065
3393
|
whiteSpace: 'pre-wrap',
|
|
3066
3394
|
wordBreak: 'break-word',
|
|
3395
|
+
marginBottom: idx < contentParts.length - 1 ? '12px' : 0,
|
|
3067
3396
|
}}>
|
|
3068
3397
|
{part.content}
|
|
3069
3398
|
</div>
|
|
@@ -3185,24 +3514,58 @@ export function createDashboardHTML() {
|
|
|
3185
3514
|
<span>Events: {trace.summary?.eventCount || trace.eventCount}</span>
|
|
3186
3515
|
{trace.durationMs && <span>Duration: {trace.durationMs}ms</span>}
|
|
3187
3516
|
{trace.commandType && <span>Type: {trace.commandType}</span>}
|
|
3517
|
+
{/* Provider info - prominently displayed */}
|
|
3518
|
+
{trace.provider && (
|
|
3519
|
+
<span style={{
|
|
3520
|
+
background: 'var(--accent-blue)',
|
|
3521
|
+
color: 'white',
|
|
3522
|
+
padding: '2px 8px',
|
|
3523
|
+
borderRadius: '4px',
|
|
3524
|
+
fontWeight: 600,
|
|
3525
|
+
}}>
|
|
3526
|
+
Provider: {trace.provider}
|
|
3527
|
+
</span>
|
|
3528
|
+
)}
|
|
3529
|
+
{trace.model && <span>Model: {trace.model}</span>}
|
|
3530
|
+
{/* For discuss: show providers list */}
|
|
3531
|
+
{trace.commandType === 'discuss' && trace.input?.providers && Array.isArray(trace.input.providers) && (
|
|
3532
|
+
<span style={{
|
|
3533
|
+
background: 'var(--accent-purple)',
|
|
3534
|
+
color: 'white',
|
|
3535
|
+
padding: '2px 8px',
|
|
3536
|
+
borderRadius: '4px',
|
|
3537
|
+
fontWeight: 600,
|
|
3538
|
+
}}>
|
|
3539
|
+
Providers: {trace.input.providers.join(', ')}
|
|
3540
|
+
</span>
|
|
3541
|
+
)}
|
|
3542
|
+
{/* PRD-2026-003: Show classification badge if available */}
|
|
3543
|
+
{trace.classification && (
|
|
3544
|
+
<ClassificationHealthBadge classification={trace.classification} showDetails={true} />
|
|
3545
|
+
)}
|
|
3188
3546
|
</div>
|
|
3189
3547
|
</div>
|
|
3190
3548
|
|
|
3191
3549
|
{/* Provider Sub-tabs for Conversations */}
|
|
3192
3550
|
{providerKeys.length > 0 && (
|
|
3193
|
-
<div className="card" style={{ marginBottom: 16 }}>
|
|
3194
|
-
<div className="card-header" style={{ borderBottom: 'none', paddingBottom: 0 }}>
|
|
3195
|
-
<span className="card-title">
|
|
3196
|
-
|
|
3551
|
+
<div className="card" style={{ marginBottom: 16, border: '2px solid var(--accent-purple)', borderRadius: '12px' }}>
|
|
3552
|
+
<div className="card-header" style={{ borderBottom: 'none', paddingBottom: 0, background: 'rgba(163, 113, 247, 0.08)' }}>
|
|
3553
|
+
<span className="card-title" style={{ fontSize: '18px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
3554
|
+
💬 Conversations
|
|
3555
|
+
</span>
|
|
3556
|
+
<span className="card-badge" style={{ background: 'var(--accent-purple)', color: 'white' }}>
|
|
3557
|
+
{providerKeys.length} provider{providerKeys.length !== 1 ? 's' : ''}
|
|
3558
|
+
</span>
|
|
3197
3559
|
</div>
|
|
3198
3560
|
|
|
3199
3561
|
{/* Provider tabs */}
|
|
3200
3562
|
<div style={{
|
|
3201
3563
|
display: 'flex',
|
|
3202
|
-
gap:
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3564
|
+
gap: '8px',
|
|
3565
|
+
padding: '12px 16px',
|
|
3566
|
+
overflowX: 'auto',
|
|
3567
|
+
background: 'var(--bg-tertiary)',
|
|
3568
|
+
borderBottom: '2px solid var(--border-color)',
|
|
3206
3569
|
}}>
|
|
3207
3570
|
{providerKeys.map(providerKey => {
|
|
3208
3571
|
const group = providerGroups[providerKey];
|
|
@@ -3215,34 +3578,43 @@ export function createDashboardHTML() {
|
|
|
3215
3578
|
key={providerKey}
|
|
3216
3579
|
onClick={() => setActiveProviderTab(providerKey)}
|
|
3217
3580
|
style={{
|
|
3218
|
-
padding: '
|
|
3219
|
-
background: '
|
|
3220
|
-
border: '
|
|
3221
|
-
|
|
3222
|
-
color: isActive ? '
|
|
3581
|
+
padding: '10px 16px',
|
|
3582
|
+
background: isActive ? 'var(--accent-blue)' : 'var(--bg-secondary)',
|
|
3583
|
+
border: isActive ? '2px solid var(--accent-blue)' : '2px solid var(--border-color)',
|
|
3584
|
+
borderRadius: '8px',
|
|
3585
|
+
color: isActive ? 'white' : 'var(--text-secondary)',
|
|
3223
3586
|
cursor: 'pointer',
|
|
3224
3587
|
fontSize: 13,
|
|
3225
|
-
fontWeight:
|
|
3588
|
+
fontWeight: 600,
|
|
3226
3589
|
display: 'flex',
|
|
3227
3590
|
alignItems: 'center',
|
|
3228
|
-
gap:
|
|
3591
|
+
gap: 10,
|
|
3229
3592
|
whiteSpace: 'nowrap',
|
|
3230
|
-
transition: 'all 0.15s ease'
|
|
3593
|
+
transition: 'all 0.15s ease',
|
|
3594
|
+
boxShadow: isActive ? '0 2px 8px rgba(88, 166, 255, 0.3)' : 'none',
|
|
3231
3595
|
}}
|
|
3232
3596
|
>
|
|
3233
3597
|
<span style={{
|
|
3234
|
-
background: isActive ? '
|
|
3235
|
-
color: isActive ? 'white' : 'var(--text-
|
|
3236
|
-
padding: '
|
|
3237
|
-
borderRadius:
|
|
3238
|
-
fontSize:
|
|
3239
|
-
fontWeight:
|
|
3598
|
+
background: isActive ? 'rgba(255, 255, 255, 0.2)' : 'var(--bg-tertiary)',
|
|
3599
|
+
color: isActive ? 'white' : 'var(--text-primary)',
|
|
3600
|
+
padding: '4px 10px',
|
|
3601
|
+
borderRadius: 6,
|
|
3602
|
+
fontSize: 12,
|
|
3603
|
+
fontWeight: 700,
|
|
3604
|
+
textTransform: 'uppercase',
|
|
3605
|
+
letterSpacing: '0.03em',
|
|
3240
3606
|
}}>
|
|
3241
3607
|
{providerKey}
|
|
3242
3608
|
</span>
|
|
3243
|
-
<span style={{
|
|
3609
|
+
<span style={{
|
|
3610
|
+
fontSize: 12,
|
|
3611
|
+
color: isActive ? 'rgba(255, 255, 255, 0.9)' : 'var(--text-muted)',
|
|
3612
|
+
background: isActive ? 'rgba(255, 255, 255, 0.15)' : 'var(--bg-tertiary)',
|
|
3613
|
+
padding: '3px 8px',
|
|
3614
|
+
borderRadius: 4,
|
|
3615
|
+
}}>
|
|
3244
3616
|
{askCount > 0 && \`\${askCount} ask\`}
|
|
3245
|
-
{askCount > 0 && replyCount > 0 && '
|
|
3617
|
+
{askCount > 0 && replyCount > 0 && ' · '}
|
|
3246
3618
|
{replyCount > 0 && \`\${replyCount} reply\`}
|
|
3247
3619
|
</span>
|
|
3248
3620
|
</button>
|
|
@@ -3255,7 +3627,13 @@ export function createDashboardHTML() {
|
|
|
3255
3627
|
<section
|
|
3256
3628
|
aria-label="Chat History"
|
|
3257
3629
|
role="log"
|
|
3258
|
-
style={{
|
|
3630
|
+
style={{
|
|
3631
|
+
padding: '24px',
|
|
3632
|
+
background: 'var(--bg-secondary)',
|
|
3633
|
+
borderRadius: '0 0 8px 8px',
|
|
3634
|
+
maxHeight: '80vh',
|
|
3635
|
+
overflowY: 'auto',
|
|
3636
|
+
}}
|
|
3259
3637
|
>
|
|
3260
3638
|
{providerGroups[effectiveActiveTab].model && (
|
|
3261
3639
|
<div style={{
|
|
@@ -3296,12 +3674,16 @@ export function createDashboardHTML() {
|
|
|
3296
3674
|
<div style={{
|
|
3297
3675
|
color: 'var(--text-muted)',
|
|
3298
3676
|
textAlign: 'center',
|
|
3299
|
-
padding: '
|
|
3677
|
+
padding: '64px 32px',
|
|
3300
3678
|
background: 'var(--bg-tertiary)',
|
|
3301
|
-
borderRadius: '
|
|
3679
|
+
borderRadius: '12px',
|
|
3680
|
+
border: '2px dashed var(--border-color)',
|
|
3302
3681
|
}}>
|
|
3303
|
-
<div style={{ fontSize: '
|
|
3304
|
-
<div>No conversation data available</div>
|
|
3682
|
+
<div style={{ fontSize: '48px', marginBottom: '16px', opacity: 0.3 }}>💬</div>
|
|
3683
|
+
<div style={{ fontSize: '16px', fontWeight: 500 }}>No conversation data available</div>
|
|
3684
|
+
<div style={{ fontSize: '13px', marginTop: '8px', opacity: 0.7 }}>
|
|
3685
|
+
Conversation history will appear here when available
|
|
3686
|
+
</div>
|
|
3305
3687
|
</div>
|
|
3306
3688
|
)}
|
|
3307
3689
|
</section>
|
|
@@ -3424,6 +3806,40 @@ export function createDashboardHTML() {
|
|
|
3424
3806
|
);
|
|
3425
3807
|
}
|
|
3426
3808
|
|
|
3809
|
+
const formatDuration = (ms) => {
|
|
3810
|
+
if (!ms) return '-';
|
|
3811
|
+
if (ms < 1000) return \`\${ms}ms\`;
|
|
3812
|
+
if (ms < 60000) return \`\${(ms / 1000).toFixed(1)}s\`;
|
|
3813
|
+
return \`\${(ms / 60000).toFixed(1)}m\`;
|
|
3814
|
+
};
|
|
3815
|
+
|
|
3816
|
+
const formatTime = (isoString) => {
|
|
3817
|
+
if (!isoString) return '-';
|
|
3818
|
+
const date = new Date(isoString);
|
|
3819
|
+
return date.toLocaleString();
|
|
3820
|
+
};
|
|
3821
|
+
|
|
3822
|
+
const getStepTypeIcon = (type) => {
|
|
3823
|
+
switch (type) {
|
|
3824
|
+
case 'prompt': return '💬';
|
|
3825
|
+
case 'tool': return '🔧';
|
|
3826
|
+
case 'conditional': return '🔀';
|
|
3827
|
+
case 'loop': return '🔄';
|
|
3828
|
+
case 'parallel': return '⚡';
|
|
3829
|
+
case 'delegate': return '👥';
|
|
3830
|
+
default: return '📋';
|
|
3831
|
+
}
|
|
3832
|
+
};
|
|
3833
|
+
|
|
3834
|
+
const getStatusColor = (status) => {
|
|
3835
|
+
switch (status) {
|
|
3836
|
+
case 'success': return 'var(--accent-green)';
|
|
3837
|
+
case 'failure': return 'var(--accent-red)';
|
|
3838
|
+
case 'running': return 'var(--accent-blue)';
|
|
3839
|
+
default: return 'var(--text-muted)';
|
|
3840
|
+
}
|
|
3841
|
+
};
|
|
3842
|
+
|
|
3427
3843
|
return (
|
|
3428
3844
|
<div>
|
|
3429
3845
|
<button className="back-button" onClick={onBack}>
|
|
@@ -3435,11 +3851,219 @@ export function createDashboardHTML() {
|
|
|
3435
3851
|
<span>ID: {workflow.workflowId}</span>
|
|
3436
3852
|
<span>Version: {workflow.version}</span>
|
|
3437
3853
|
<span>Steps: {(workflow.steps && workflow.steps.length) || 0}</span>
|
|
3854
|
+
{workflow.executionCount > 0 && (
|
|
3855
|
+
<span>Executions: {workflow.executionCount}</span>
|
|
3856
|
+
)}
|
|
3438
3857
|
</div>
|
|
3439
3858
|
{workflow.description && (
|
|
3440
3859
|
<p style={{ marginTop: 8, color: 'var(--text-secondary)' }}>{workflow.description}</p>
|
|
3441
3860
|
)}
|
|
3442
3861
|
</div>
|
|
3862
|
+
|
|
3863
|
+
{/* Metadata section */}
|
|
3864
|
+
{workflow.metadata && (
|
|
3865
|
+
<div className="card" style={{ marginBottom: 16 }}>
|
|
3866
|
+
<div className="card-header">
|
|
3867
|
+
<span className="card-title">Metadata</span>
|
|
3868
|
+
</div>
|
|
3869
|
+
<div style={{ padding: '12px 16px', display: 'flex', flexWrap: 'wrap', gap: 16 }}>
|
|
3870
|
+
{workflow.metadata.category && (
|
|
3871
|
+
<div>
|
|
3872
|
+
<span style={{ color: 'var(--text-muted)', fontSize: 11 }}>Category</span>
|
|
3873
|
+
<div style={{ color: 'var(--accent-blue)', fontWeight: 500 }}>{workflow.metadata.category}</div>
|
|
3874
|
+
</div>
|
|
3875
|
+
)}
|
|
3876
|
+
{workflow.metadata.tags && workflow.metadata.tags.length > 0 && (
|
|
3877
|
+
<div>
|
|
3878
|
+
<span style={{ color: 'var(--text-muted)', fontSize: 11 }}>Tags</span>
|
|
3879
|
+
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginTop: 2 }}>
|
|
3880
|
+
{workflow.metadata.tags.map((tag, i) => (
|
|
3881
|
+
<span key={i} style={{
|
|
3882
|
+
background: 'var(--bg-tertiary)',
|
|
3883
|
+
padding: '2px 8px',
|
|
3884
|
+
borderRadius: 4,
|
|
3885
|
+
fontSize: 11,
|
|
3886
|
+
color: 'var(--text-secondary)'
|
|
3887
|
+
}}>{tag}</span>
|
|
3888
|
+
))}
|
|
3889
|
+
</div>
|
|
3890
|
+
</div>
|
|
3891
|
+
)}
|
|
3892
|
+
{workflow.metadata.requiredAbilities && workflow.metadata.requiredAbilities.length > 0 && (
|
|
3893
|
+
<div>
|
|
3894
|
+
<span style={{ color: 'var(--text-muted)', fontSize: 11 }}>Required Abilities</span>
|
|
3895
|
+
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginTop: 2 }}>
|
|
3896
|
+
{workflow.metadata.requiredAbilities.map((ability, i) => (
|
|
3897
|
+
<span key={i} style={{
|
|
3898
|
+
background: 'rgba(163, 113, 247, 0.15)',
|
|
3899
|
+
padding: '2px 8px',
|
|
3900
|
+
borderRadius: 4,
|
|
3901
|
+
fontSize: 11,
|
|
3902
|
+
color: 'var(--accent-purple)'
|
|
3903
|
+
}}>{ability}</span>
|
|
3904
|
+
))}
|
|
3905
|
+
</div>
|
|
3906
|
+
</div>
|
|
3907
|
+
)}
|
|
3908
|
+
</div>
|
|
3909
|
+
</div>
|
|
3910
|
+
)}
|
|
3911
|
+
|
|
3912
|
+
{/* Workflow Steps with details */}
|
|
3913
|
+
<div className="card" style={{ marginBottom: 16 }}>
|
|
3914
|
+
<div className="card-header">
|
|
3915
|
+
<span className="card-title">Workflow Steps ({workflow.steps?.length || 0})</span>
|
|
3916
|
+
</div>
|
|
3917
|
+
<div style={{ padding: '8px 0' }}>
|
|
3918
|
+
{workflow.steps && workflow.steps.map((step, index) => (
|
|
3919
|
+
<div key={step.stepId} style={{
|
|
3920
|
+
padding: '12px 16px',
|
|
3921
|
+
borderBottom: index < workflow.steps.length - 1 ? '1px solid var(--border-color)' : 'none'
|
|
3922
|
+
}}>
|
|
3923
|
+
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
|
|
3924
|
+
<div style={{
|
|
3925
|
+
width: 32,
|
|
3926
|
+
height: 32,
|
|
3927
|
+
borderRadius: 8,
|
|
3928
|
+
background: 'var(--bg-tertiary)',
|
|
3929
|
+
display: 'flex',
|
|
3930
|
+
alignItems: 'center',
|
|
3931
|
+
justifyContent: 'center',
|
|
3932
|
+
fontSize: 16,
|
|
3933
|
+
flexShrink: 0
|
|
3934
|
+
}}>
|
|
3935
|
+
{getStepTypeIcon(step.type)}
|
|
3936
|
+
</div>
|
|
3937
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
3938
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
3939
|
+
<span style={{ fontWeight: 500 }}>{step.name}</span>
|
|
3940
|
+
<span style={{
|
|
3941
|
+
background: 'var(--bg-tertiary)',
|
|
3942
|
+
padding: '2px 6px',
|
|
3943
|
+
borderRadius: 4,
|
|
3944
|
+
fontSize: 10,
|
|
3945
|
+
color: 'var(--text-muted)',
|
|
3946
|
+
textTransform: 'uppercase'
|
|
3947
|
+
}}>{step.type}</span>
|
|
3948
|
+
{step.timeout && (
|
|
3949
|
+
<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
|
|
3950
|
+
⏱ {formatDuration(step.timeout)}
|
|
3951
|
+
</span>
|
|
3952
|
+
)}
|
|
3953
|
+
</div>
|
|
3954
|
+
<div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'monospace', marginTop: 2 }}>
|
|
3955
|
+
{step.stepId}
|
|
3956
|
+
</div>
|
|
3957
|
+
{/* Step configuration details */}
|
|
3958
|
+
{step.config && (
|
|
3959
|
+
<div style={{
|
|
3960
|
+
marginTop: 8,
|
|
3961
|
+
padding: 10,
|
|
3962
|
+
background: 'var(--bg-primary)',
|
|
3963
|
+
borderRadius: 6,
|
|
3964
|
+
fontSize: 12
|
|
3965
|
+
}}>
|
|
3966
|
+
{step.config.agentId && (
|
|
3967
|
+
<div style={{ marginBottom: 4 }}>
|
|
3968
|
+
<span style={{ color: 'var(--text-muted)' }}>Agent: </span>
|
|
3969
|
+
<span style={{ color: 'var(--accent-blue)' }}>{step.config.agentId}</span>
|
|
3970
|
+
</div>
|
|
3971
|
+
)}
|
|
3972
|
+
{step.config.tool && (
|
|
3973
|
+
<div style={{ marginBottom: 4 }}>
|
|
3974
|
+
<span style={{ color: 'var(--text-muted)' }}>Tool: </span>
|
|
3975
|
+
<span style={{ color: 'var(--accent-green)' }}>{step.config.tool}</span>
|
|
3976
|
+
</div>
|
|
3977
|
+
)}
|
|
3978
|
+
{step.config.prompt && (
|
|
3979
|
+
<div>
|
|
3980
|
+
<span style={{ color: 'var(--text-muted)' }}>Prompt: </span>
|
|
3981
|
+
<pre style={{
|
|
3982
|
+
margin: '4px 0 0 0',
|
|
3983
|
+
padding: 8,
|
|
3984
|
+
background: 'var(--bg-secondary)',
|
|
3985
|
+
borderRadius: 4,
|
|
3986
|
+
fontSize: 11,
|
|
3987
|
+
color: 'var(--text-secondary)',
|
|
3988
|
+
whiteSpace: 'pre-wrap',
|
|
3989
|
+
wordBreak: 'break-word',
|
|
3990
|
+
maxHeight: 150,
|
|
3991
|
+
overflow: 'auto'
|
|
3992
|
+
}}>{step.config.prompt}</pre>
|
|
3993
|
+
</div>
|
|
3994
|
+
)}
|
|
3995
|
+
{step.config.args && Object.keys(step.config.args).length > 0 && (
|
|
3996
|
+
<div style={{ marginTop: 4 }}>
|
|
3997
|
+
<span style={{ color: 'var(--text-muted)' }}>Args: </span>
|
|
3998
|
+
<code style={{
|
|
3999
|
+
fontSize: 11,
|
|
4000
|
+
color: 'var(--text-secondary)',
|
|
4001
|
+
background: 'var(--bg-secondary)',
|
|
4002
|
+
padding: '2px 4px',
|
|
4003
|
+
borderRadius: 2
|
|
4004
|
+
}}>{JSON.stringify(step.config.args)}</code>
|
|
4005
|
+
</div>
|
|
4006
|
+
)}
|
|
4007
|
+
</div>
|
|
4008
|
+
)}
|
|
4009
|
+
{/* Dependencies */}
|
|
4010
|
+
{step.dependencies && step.dependencies.length > 0 && (
|
|
4011
|
+
<div style={{ marginTop: 6, fontSize: 11, color: 'var(--text-muted)' }}>
|
|
4012
|
+
Depends on: {step.dependencies.join(', ')}
|
|
4013
|
+
</div>
|
|
4014
|
+
)}
|
|
4015
|
+
</div>
|
|
4016
|
+
</div>
|
|
4017
|
+
</div>
|
|
4018
|
+
))}
|
|
4019
|
+
</div>
|
|
4020
|
+
</div>
|
|
4021
|
+
|
|
4022
|
+
{/* Recent Executions */}
|
|
4023
|
+
{workflow.recentExecutions && workflow.recentExecutions.length > 0 && (
|
|
4024
|
+
<div className="card" style={{ marginBottom: 16 }}>
|
|
4025
|
+
<div className="card-header">
|
|
4026
|
+
<span className="card-title">Recent Executions ({workflow.executionCount})</span>
|
|
4027
|
+
</div>
|
|
4028
|
+
<div style={{ padding: '8px 0' }}>
|
|
4029
|
+
{workflow.recentExecutions.map((exec, index) => (
|
|
4030
|
+
<div key={exec.traceId} style={{
|
|
4031
|
+
padding: '10px 16px',
|
|
4032
|
+
borderBottom: index < workflow.recentExecutions.length - 1 ? '1px solid var(--border-color)' : 'none',
|
|
4033
|
+
display: 'flex',
|
|
4034
|
+
alignItems: 'center',
|
|
4035
|
+
justifyContent: 'space-between'
|
|
4036
|
+
}}>
|
|
4037
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
4038
|
+
<span style={{
|
|
4039
|
+
width: 8,
|
|
4040
|
+
height: 8,
|
|
4041
|
+
borderRadius: '50%',
|
|
4042
|
+
background: getStatusColor(exec.status)
|
|
4043
|
+
}}></span>
|
|
4044
|
+
<span style={{ fontFamily: 'monospace', fontSize: 12, color: 'var(--text-secondary)' }}>
|
|
4045
|
+
{exec.traceId.slice(0, 8)}
|
|
4046
|
+
</span>
|
|
4047
|
+
<span style={{
|
|
4048
|
+
fontSize: 10,
|
|
4049
|
+
padding: '2px 6px',
|
|
4050
|
+
borderRadius: 4,
|
|
4051
|
+
background: exec.status === 'success' ? 'rgba(63, 185, 80, 0.15)' :
|
|
4052
|
+
exec.status === 'failure' ? 'rgba(248, 81, 73, 0.15)' : 'var(--bg-tertiary)',
|
|
4053
|
+
color: getStatusColor(exec.status)
|
|
4054
|
+
}}>{exec.status}</span>
|
|
4055
|
+
</div>
|
|
4056
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 16, fontSize: 12, color: 'var(--text-muted)' }}>
|
|
4057
|
+
<span>{formatDuration(exec.durationMs)}</span>
|
|
4058
|
+
<span>{formatTime(exec.startTime)}</span>
|
|
4059
|
+
</div>
|
|
4060
|
+
</div>
|
|
4061
|
+
))}
|
|
4062
|
+
</div>
|
|
4063
|
+
</div>
|
|
4064
|
+
)}
|
|
4065
|
+
|
|
4066
|
+
{/* Visual DAG */}
|
|
3443
4067
|
<div className="card">
|
|
3444
4068
|
<div className="card-header">
|
|
3445
4069
|
<span className="card-title">Workflow DAG</span>
|
|
@@ -3457,6 +4081,23 @@ export function createDashboardHTML() {
|
|
|
3457
4081
|
const [currentPage, setCurrentPage] = useState(0);
|
|
3458
4082
|
const pageSize = 20;
|
|
3459
4083
|
|
|
4084
|
+
// Provider colors matching the Provider Usage histogram
|
|
4085
|
+
const providerColors = {
|
|
4086
|
+
claude: { bg: 'rgba(255, 145, 77, 0.15)', color: 'var(--accent-orange)' },
|
|
4087
|
+
grok: { bg: 'rgba(163, 113, 247, 0.15)', color: 'var(--accent-purple)' },
|
|
4088
|
+
gemini: { bg: 'rgba(88, 166, 255, 0.15)', color: 'var(--accent-blue)' },
|
|
4089
|
+
codex: { bg: 'rgba(63, 185, 80, 0.15)', color: 'var(--accent-green)' },
|
|
4090
|
+
opencode: { bg: 'rgba(63, 185, 185, 0.15)', color: 'var(--accent-cyan)' },
|
|
4091
|
+
antigravity: { bg: 'rgba(255, 200, 77, 0.15)', color: '#ffc84d' },
|
|
4092
|
+
cursor: { bg: 'rgba(88, 166, 255, 0.15)', color: 'var(--accent-blue)' },
|
|
4093
|
+
'local-llm': { bg: 'rgba(163, 163, 163, 0.15)', color: 'var(--text-secondary)' },
|
|
4094
|
+
};
|
|
4095
|
+
|
|
4096
|
+
const getProviderStyle = (provider) => {
|
|
4097
|
+
const lowerProvider = provider?.toLowerCase() || '';
|
|
4098
|
+
return providerColors[lowerProvider] || { bg: 'var(--bg-secondary)', color: 'var(--text-secondary)' };
|
|
4099
|
+
};
|
|
4100
|
+
|
|
3460
4101
|
const filteredTraces = traces.filter(trace => {
|
|
3461
4102
|
if (filter !== 'all' && trace.status !== filter) return false;
|
|
3462
4103
|
if (search && !trace.traceId.toLowerCase().includes(search.toLowerCase()) &&
|
|
@@ -3539,6 +4180,7 @@ export function createDashboardHTML() {
|
|
|
3539
4180
|
<thead>
|
|
3540
4181
|
<tr>
|
|
3541
4182
|
<th>Name</th>
|
|
4183
|
+
<th>Providers</th>
|
|
3542
4184
|
<th>Status</th>
|
|
3543
4185
|
<th>Events</th>
|
|
3544
4186
|
<th>Duration</th>
|
|
@@ -3548,12 +4190,12 @@ export function createDashboardHTML() {
|
|
|
3548
4190
|
<tbody>
|
|
3549
4191
|
{paginatedTraces.length === 0 ? (
|
|
3550
4192
|
<tr>
|
|
3551
|
-
<td colSpan="
|
|
4193
|
+
<td colSpan="6" style={{ padding: '3rem 1rem', textAlign: 'center' }}>
|
|
3552
4194
|
<div className="table-empty-state">
|
|
3553
4195
|
<div style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>📋</div>
|
|
3554
4196
|
<div style={{ fontWeight: 500, marginBottom: '0.25rem' }}>No traces found</div>
|
|
3555
4197
|
<div style={{ color: 'var(--text-muted)', fontSize: '0.85rem' }}>
|
|
3556
|
-
{
|
|
4198
|
+
{filter === 'all'
|
|
3557
4199
|
? 'Run an agent or workflow to see execution traces here'
|
|
3558
4200
|
: 'Try changing the status filter to see more results'}
|
|
3559
4201
|
</div>
|
|
@@ -3565,7 +4207,44 @@ export function createDashboardHTML() {
|
|
|
3565
4207
|
<tr key={trace.traceId} onClick={() => onSelectTrace(trace.traceId)}>
|
|
3566
4208
|
<td style={{ fontFamily: 'monospace' }} title={trace.traceId}>{trace.name || trace.traceId.slice(0, 12) + '...'}</td>
|
|
3567
4209
|
<td>
|
|
3568
|
-
|
|
4210
|
+
{trace.providers && trace.providers.length > 0 ? (
|
|
4211
|
+
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
|
4212
|
+
{trace.providers.slice(0, 3).map(provider => {
|
|
4213
|
+
const style = getProviderStyle(provider);
|
|
4214
|
+
return (
|
|
4215
|
+
<span
|
|
4216
|
+
key={provider}
|
|
4217
|
+
style={{
|
|
4218
|
+
padding: '2px 8px',
|
|
4219
|
+
borderRadius: '10px',
|
|
4220
|
+
background: style.bg,
|
|
4221
|
+
color: style.color,
|
|
4222
|
+
fontSize: '10px',
|
|
4223
|
+
fontWeight: 600,
|
|
4224
|
+
border: \`1px solid \${style.color}30\`,
|
|
4225
|
+
whiteSpace: 'nowrap',
|
|
4226
|
+
}}
|
|
4227
|
+
>
|
|
4228
|
+
{provider}
|
|
4229
|
+
</span>
|
|
4230
|
+
);
|
|
4231
|
+
})}
|
|
4232
|
+
{trace.providers.length > 3 && (
|
|
4233
|
+
<span style={{
|
|
4234
|
+
padding: '2px 6px',
|
|
4235
|
+
color: 'var(--text-muted)',
|
|
4236
|
+
fontSize: '10px',
|
|
4237
|
+
}}>
|
|
4238
|
+
+{trace.providers.length - 3}
|
|
4239
|
+
</span>
|
|
4240
|
+
)}
|
|
4241
|
+
</div>
|
|
4242
|
+
) : (
|
|
4243
|
+
<span style={{ color: 'var(--text-muted)', fontSize: '11px' }}>-</span>
|
|
4244
|
+
)}
|
|
4245
|
+
</td>
|
|
4246
|
+
<td>
|
|
4247
|
+
<span className={\`history-status \${trace.status}\`}>
|
|
3569
4248
|
<StatusIcon status={trace.status} />
|
|
3570
4249
|
{trace.status}
|
|
3571
4250
|
</span>
|
|
@@ -3642,9 +4321,28 @@ export function createDashboardHTML() {
|
|
|
3642
4321
|
}
|
|
3643
4322
|
|
|
3644
4323
|
// Full-page Providers view
|
|
3645
|
-
function ProvidersView({ providers }) {
|
|
4324
|
+
function ProvidersView({ providers: initialProviders }) {
|
|
3646
4325
|
const [filter, setFilter] = useState('all');
|
|
3647
4326
|
const [search, setSearch] = useState('');
|
|
4327
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
4328
|
+
const [providers, setProviders] = useState(initialProviders);
|
|
4329
|
+
const [lastRefresh, setLastRefresh] = useState(null);
|
|
4330
|
+
|
|
4331
|
+
const handleRefresh = async () => {
|
|
4332
|
+
setRefreshing(true);
|
|
4333
|
+
try {
|
|
4334
|
+
const response = await fetch('/api/providers/refresh', { method: 'POST' });
|
|
4335
|
+
const result = await response.json();
|
|
4336
|
+
if (result.success && result.data?.providers) {
|
|
4337
|
+
setProviders(result.data.providers);
|
|
4338
|
+
setLastRefresh(new Date().toLocaleTimeString());
|
|
4339
|
+
}
|
|
4340
|
+
} catch (err) {
|
|
4341
|
+
console.error('Failed to refresh providers:', err);
|
|
4342
|
+
} finally {
|
|
4343
|
+
setRefreshing(false);
|
|
4344
|
+
}
|
|
4345
|
+
};
|
|
3648
4346
|
|
|
3649
4347
|
const filteredProviders = providers.filter(provider => {
|
|
3650
4348
|
if (filter === 'available' && !provider.available) return false;
|
|
@@ -3674,6 +4372,19 @@ export function createDashboardHTML() {
|
|
|
3674
4372
|
<option value="available">Available ({availableCount})</option>
|
|
3675
4373
|
<option value="unavailable">Unavailable ({providers.length - availableCount})</option>
|
|
3676
4374
|
</select>
|
|
4375
|
+
<button
|
|
4376
|
+
className="refresh-button"
|
|
4377
|
+
onClick={handleRefresh}
|
|
4378
|
+
disabled={refreshing}
|
|
4379
|
+
title="Check provider health (sends test prompt to each provider)"
|
|
4380
|
+
>
|
|
4381
|
+
{refreshing ? '⏳ Checking...' : '🔄 Refresh Status'}
|
|
4382
|
+
</button>
|
|
4383
|
+
{lastRefresh && (
|
|
4384
|
+
<span style={{ color: 'var(--text-muted)', fontSize: '0.85rem', marginLeft: '0.5rem' }}>
|
|
4385
|
+
Last refresh: {lastRefresh}
|
|
4386
|
+
</span>
|
|
4387
|
+
)}
|
|
3677
4388
|
</div>
|
|
3678
4389
|
|
|
3679
4390
|
<div className="card">
|
|
@@ -3682,14 +4393,13 @@ export function createDashboardHTML() {
|
|
|
3682
4393
|
<tr>
|
|
3683
4394
|
<th>Provider</th>
|
|
3684
4395
|
<th>Status</th>
|
|
3685
|
-
<th>
|
|
3686
|
-
<th>Last Check</th>
|
|
4396
|
+
<th>Response Time</th>
|
|
3687
4397
|
</tr>
|
|
3688
4398
|
</thead>
|
|
3689
4399
|
<tbody>
|
|
3690
4400
|
{filteredProviders.length === 0 ? (
|
|
3691
4401
|
<tr>
|
|
3692
|
-
<td colSpan="
|
|
4402
|
+
<td colSpan="3" style={{ padding: '3rem 1rem', textAlign: 'center' }}>
|
|
3693
4403
|
<div className="table-empty-state">
|
|
3694
4404
|
<div style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>🔌</div>
|
|
3695
4405
|
<div style={{ fontWeight: 500, marginBottom: '0.25rem' }}>No providers found</div>
|
|
@@ -3717,15 +4427,11 @@ export function createDashboardHTML() {
|
|
|
3717
4427
|
<td style={{ fontFamily: 'monospace' }}>
|
|
3718
4428
|
<span className={\`provider-latency \${
|
|
3719
4429
|
!provider.latencyMs ? '' :
|
|
3720
|
-
provider.latencyMs <
|
|
3721
|
-
provider.latencyMs < 300 ? 'medium' : 'slow'
|
|
4430
|
+
provider.latencyMs < 5000 ? 'fast' : 'slow'
|
|
3722
4431
|
}\`}>
|
|
3723
4432
|
{provider.latencyMs ? \`\${provider.latencyMs}ms\` : '-'}
|
|
3724
4433
|
</span>
|
|
3725
4434
|
</td>
|
|
3726
|
-
<td style={{ color: 'var(--text-muted)' }}>
|
|
3727
|
-
{provider.lastCheck ? formatRelativeTime(provider.lastCheck) : '-'}
|
|
3728
|
-
</td>
|
|
3729
4435
|
</tr>
|
|
3730
4436
|
))
|
|
3731
4437
|
)}
|
|
@@ -4151,8 +4857,7 @@ export function createDashboardHTML() {
|
|
|
4151
4857
|
</div>
|
|
4152
4858
|
<span className={\`provider-latency \${
|
|
4153
4859
|
!provider.latencyMs ? '' :
|
|
4154
|
-
provider.latencyMs <
|
|
4155
|
-
provider.latencyMs < 300 ? 'medium' : 'slow'
|
|
4860
|
+
provider.latencyMs < 5000 ? 'fast' : 'slow'
|
|
4156
4861
|
}\`}>
|
|
4157
4862
|
{provider.latencyMs ? \`\${provider.latencyMs}ms\` : '-'}
|
|
4158
4863
|
</span>
|
|
@@ -4245,9 +4950,48 @@ export function createDashboardHTML() {
|
|
|
4245
4950
|
<div className="card" style={{ marginBottom: 16 }}>
|
|
4246
4951
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
|
4247
4952
|
<div>
|
|
4248
|
-
<
|
|
4249
|
-
{
|
|
4250
|
-
|
|
4953
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 8 }}>
|
|
4954
|
+
<h2 style={{ margin: 0, color: 'var(--text-primary)' }}>
|
|
4955
|
+
{agent.displayName || agent.agentId}
|
|
4956
|
+
</h2>
|
|
4957
|
+
{/* PRD-2026-004: Meta-agent badges */}
|
|
4958
|
+
{agent.metaAgent && (
|
|
4959
|
+
<span style={{
|
|
4960
|
+
padding: '2px 8px',
|
|
4961
|
+
borderRadius: 10,
|
|
4962
|
+
background: 'rgba(163, 113, 247, 0.2)',
|
|
4963
|
+
color: 'var(--accent-purple)',
|
|
4964
|
+
fontSize: 11,
|
|
4965
|
+
fontWeight: 600,
|
|
4966
|
+
}}>
|
|
4967
|
+
🎯 Meta-Agent
|
|
4968
|
+
</span>
|
|
4969
|
+
)}
|
|
4970
|
+
{agent.archetype && (
|
|
4971
|
+
<span style={{
|
|
4972
|
+
padding: '2px 8px',
|
|
4973
|
+
borderRadius: 10,
|
|
4974
|
+
background: 'rgba(88, 166, 255, 0.2)',
|
|
4975
|
+
color: 'var(--accent-blue)',
|
|
4976
|
+
fontSize: 11,
|
|
4977
|
+
fontWeight: 600,
|
|
4978
|
+
}}>
|
|
4979
|
+
🏗️ Archetype
|
|
4980
|
+
</span>
|
|
4981
|
+
)}
|
|
4982
|
+
{agent.category && (
|
|
4983
|
+
<span style={{
|
|
4984
|
+
padding: '2px 8px',
|
|
4985
|
+
borderRadius: 10,
|
|
4986
|
+
background: 'rgba(139, 148, 158, 0.15)',
|
|
4987
|
+
color: 'var(--text-muted)',
|
|
4988
|
+
fontSize: 11,
|
|
4989
|
+
textTransform: 'capitalize',
|
|
4990
|
+
}}>
|
|
4991
|
+
{agent.category}
|
|
4992
|
+
</span>
|
|
4993
|
+
)}
|
|
4994
|
+
</div>
|
|
4251
4995
|
<p style={{ margin: 0, color: 'var(--text-secondary)', maxWidth: 600 }}>
|
|
4252
4996
|
{agent.description}
|
|
4253
4997
|
</p>
|
|
@@ -4349,6 +5093,239 @@ export function createDashboardHTML() {
|
|
|
4349
5093
|
</div>
|
|
4350
5094
|
)}
|
|
4351
5095
|
|
|
5096
|
+
{/* PRD-2026-004: Orchestration Relationships */}
|
|
5097
|
+
{(agent.orchestrates?.length > 0 || agent.canDelegateToArchetypes?.length > 0 || agent.canDelegateToMetaAgents?.length > 0 || agent.replaces?.length > 0) && (
|
|
5098
|
+
<div className="card" style={{ marginBottom: 16 }}>
|
|
5099
|
+
<h3 style={{ margin: 0, marginBottom: 16, fontSize: 14, color: 'var(--text-secondary)' }}>
|
|
5100
|
+
🔗 Orchestration
|
|
5101
|
+
</h3>
|
|
5102
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
5103
|
+
{agent.orchestrates?.length > 0 && (
|
|
5104
|
+
<div>
|
|
5105
|
+
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Orchestrates</div>
|
|
5106
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
5107
|
+
{agent.orchestrates.map((id, idx) => (
|
|
5108
|
+
<span key={idx} style={{
|
|
5109
|
+
padding: '4px 10px',
|
|
5110
|
+
borderRadius: 12,
|
|
5111
|
+
background: 'rgba(163, 113, 247, 0.15)',
|
|
5112
|
+
color: 'var(--accent-purple)',
|
|
5113
|
+
fontSize: 12,
|
|
5114
|
+
}}>
|
|
5115
|
+
{id}
|
|
5116
|
+
</span>
|
|
5117
|
+
))}
|
|
5118
|
+
</div>
|
|
5119
|
+
</div>
|
|
5120
|
+
)}
|
|
5121
|
+
{agent.canDelegateToArchetypes?.length > 0 && (
|
|
5122
|
+
<div>
|
|
5123
|
+
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Can Delegate to Archetypes</div>
|
|
5124
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
5125
|
+
{agent.canDelegateToArchetypes.map((id, idx) => (
|
|
5126
|
+
<span key={idx} style={{
|
|
5127
|
+
padding: '4px 10px',
|
|
5128
|
+
borderRadius: 12,
|
|
5129
|
+
background: 'rgba(88, 166, 255, 0.15)',
|
|
5130
|
+
color: 'var(--accent-blue)',
|
|
5131
|
+
fontSize: 12,
|
|
5132
|
+
}}>
|
|
5133
|
+
{id}
|
|
5134
|
+
</span>
|
|
5135
|
+
))}
|
|
5136
|
+
</div>
|
|
5137
|
+
</div>
|
|
5138
|
+
)}
|
|
5139
|
+
{agent.canDelegateToMetaAgents?.length > 0 && (
|
|
5140
|
+
<div>
|
|
5141
|
+
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Can Delegate to Meta-Agents</div>
|
|
5142
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
5143
|
+
{agent.canDelegateToMetaAgents.map((id, idx) => (
|
|
5144
|
+
<span key={idx} style={{
|
|
5145
|
+
padding: '4px 10px',
|
|
5146
|
+
borderRadius: 12,
|
|
5147
|
+
background: 'rgba(163, 113, 247, 0.15)',
|
|
5148
|
+
color: 'var(--accent-purple)',
|
|
5149
|
+
fontSize: 12,
|
|
5150
|
+
}}>
|
|
5151
|
+
{id}
|
|
5152
|
+
</span>
|
|
5153
|
+
))}
|
|
5154
|
+
</div>
|
|
5155
|
+
</div>
|
|
5156
|
+
)}
|
|
5157
|
+
{agent.replaces?.length > 0 && (
|
|
5158
|
+
<div>
|
|
5159
|
+
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>Replaces (Legacy Agents)</div>
|
|
5160
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
5161
|
+
{agent.replaces.map((id, idx) => (
|
|
5162
|
+
<span key={idx} style={{
|
|
5163
|
+
padding: '4px 10px',
|
|
5164
|
+
borderRadius: 12,
|
|
5165
|
+
background: 'rgba(139, 148, 158, 0.15)',
|
|
5166
|
+
color: 'var(--text-muted)',
|
|
5167
|
+
fontSize: 12,
|
|
5168
|
+
textDecoration: 'line-through',
|
|
5169
|
+
}}>
|
|
5170
|
+
{id}
|
|
5171
|
+
</span>
|
|
5172
|
+
))}
|
|
5173
|
+
</div>
|
|
5174
|
+
</div>
|
|
5175
|
+
)}
|
|
5176
|
+
</div>
|
|
5177
|
+
</div>
|
|
5178
|
+
)}
|
|
5179
|
+
|
|
5180
|
+
{/* PRD-2026-004: Task Classifier Config */}
|
|
5181
|
+
{agent.taskClassifier && (
|
|
5182
|
+
<div className="card" style={{ marginBottom: 16 }}>
|
|
5183
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
|
5184
|
+
<h3 style={{ margin: 0, fontSize: 14, color: 'var(--text-secondary)' }}>
|
|
5185
|
+
🎯 Task Classifier
|
|
5186
|
+
</h3>
|
|
5187
|
+
<span style={{
|
|
5188
|
+
padding: '2px 8px',
|
|
5189
|
+
borderRadius: 10,
|
|
5190
|
+
background: agent.taskClassifier.enabled !== false ? 'rgba(63, 185, 80, 0.2)' : 'rgba(139, 148, 158, 0.2)',
|
|
5191
|
+
color: agent.taskClassifier.enabled !== false ? 'var(--accent-green)' : 'var(--text-muted)',
|
|
5192
|
+
fontSize: 11,
|
|
5193
|
+
}}>
|
|
5194
|
+
{agent.taskClassifier.enabled !== false ? 'Enabled' : 'Disabled'}
|
|
5195
|
+
</span>
|
|
5196
|
+
</div>
|
|
5197
|
+
{agent.taskClassifier.defaultWorkflow && (
|
|
5198
|
+
<div style={{ marginBottom: 16, padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8 }}>
|
|
5199
|
+
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 }}>Default Workflow</div>
|
|
5200
|
+
<code style={{ color: 'var(--accent-cyan)', fontSize: 13 }}>{agent.taskClassifier.defaultWorkflow}</code>
|
|
5201
|
+
</div>
|
|
5202
|
+
)}
|
|
5203
|
+
{agent.taskClassifier.rules?.length > 0 && (
|
|
5204
|
+
<div>
|
|
5205
|
+
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 }}>
|
|
5206
|
+
Classification Rules ({agent.taskClassifier.rules.length})
|
|
5207
|
+
</div>
|
|
5208
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
5209
|
+
{agent.taskClassifier.rules
|
|
5210
|
+
.sort((a, b) => (b.priority ?? 50) - (a.priority ?? 50))
|
|
5211
|
+
.map((rule, idx) => {
|
|
5212
|
+
const priority = rule.priority ?? 50;
|
|
5213
|
+
return (
|
|
5214
|
+
<div key={idx} style={{
|
|
5215
|
+
padding: 12,
|
|
5216
|
+
background: 'var(--bg-tertiary)',
|
|
5217
|
+
borderRadius: 8,
|
|
5218
|
+
borderLeft: \`3px solid \${
|
|
5219
|
+
priority >= 80 ? 'var(--accent-red)' :
|
|
5220
|
+
priority >= 70 ? 'var(--accent-orange)' :
|
|
5221
|
+
'var(--accent-blue)'
|
|
5222
|
+
}\`,
|
|
5223
|
+
}}>
|
|
5224
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 8 }}>
|
|
5225
|
+
<span style={{
|
|
5226
|
+
padding: '2px 8px',
|
|
5227
|
+
borderRadius: 10,
|
|
5228
|
+
background: 'rgba(88, 166, 255, 0.15)',
|
|
5229
|
+
color: 'var(--accent-blue)',
|
|
5230
|
+
fontSize: 11,
|
|
5231
|
+
fontWeight: 500,
|
|
5232
|
+
}}>
|
|
5233
|
+
{rule.taskType}
|
|
5234
|
+
</span>
|
|
5235
|
+
<span style={{
|
|
5236
|
+
padding: '2px 8px',
|
|
5237
|
+
borderRadius: 10,
|
|
5238
|
+
background: priority >= 80 ? 'rgba(248, 81, 73, 0.15)' :
|
|
5239
|
+
priority >= 70 ? 'rgba(227, 179, 65, 0.15)' :
|
|
5240
|
+
'rgba(139, 148, 158, 0.15)',
|
|
5241
|
+
color: priority >= 80 ? 'var(--accent-red)' :
|
|
5242
|
+
priority >= 70 ? 'var(--accent-orange)' :
|
|
5243
|
+
'var(--text-muted)',
|
|
5244
|
+
fontSize: 10,
|
|
5245
|
+
}}>
|
|
5246
|
+
Priority: {priority}
|
|
5247
|
+
</span>
|
|
5248
|
+
</div>
|
|
5249
|
+
<div style={{ marginBottom: 4 }}>
|
|
5250
|
+
<code style={{ fontSize: 11, color: 'var(--accent-purple)', wordBreak: 'break-all' }}>
|
|
5251
|
+
/{rule.pattern}/i
|
|
5252
|
+
</code>
|
|
5253
|
+
</div>
|
|
5254
|
+
<div style={{ fontSize: 11, color: 'var(--text-muted)' }}>
|
|
5255
|
+
→ {rule.workflow}
|
|
5256
|
+
</div>
|
|
5257
|
+
</div>
|
|
5258
|
+
);})}
|
|
5259
|
+
</div>
|
|
5260
|
+
</div>
|
|
5261
|
+
)}
|
|
5262
|
+
</div>
|
|
5263
|
+
)}
|
|
5264
|
+
|
|
5265
|
+
{/* PRD-2026-004: Capability Mappings */}
|
|
5266
|
+
{agent.capabilityMappings?.length > 0 && (
|
|
5267
|
+
<div className="card" style={{ marginBottom: 16 }}>
|
|
5268
|
+
<h3 style={{ margin: 0, marginBottom: 16, fontSize: 14, color: 'var(--text-secondary)' }}>
|
|
5269
|
+
📋 Capability Mappings ({agent.capabilityMappings.length})
|
|
5270
|
+
</h3>
|
|
5271
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
5272
|
+
{agent.capabilityMappings.map((mapping, idx) => (
|
|
5273
|
+
<div key={idx} style={{
|
|
5274
|
+
padding: 12,
|
|
5275
|
+
background: 'var(--bg-tertiary)',
|
|
5276
|
+
borderRadius: 8,
|
|
5277
|
+
}}>
|
|
5278
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
|
5279
|
+
<span style={{
|
|
5280
|
+
padding: '2px 8px',
|
|
5281
|
+
borderRadius: 10,
|
|
5282
|
+
background: 'rgba(88, 166, 255, 0.15)',
|
|
5283
|
+
color: 'var(--accent-blue)',
|
|
5284
|
+
fontSize: 11,
|
|
5285
|
+
fontWeight: 500,
|
|
5286
|
+
}}>
|
|
5287
|
+
{mapping.taskType}
|
|
5288
|
+
</span>
|
|
5289
|
+
{mapping.priority !== undefined && (
|
|
5290
|
+
<span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
|
|
5291
|
+
Priority: {mapping.priority}
|
|
5292
|
+
</span>
|
|
5293
|
+
)}
|
|
5294
|
+
</div>
|
|
5295
|
+
{mapping.description && (
|
|
5296
|
+
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginBottom: 8 }}>
|
|
5297
|
+
{mapping.description}
|
|
5298
|
+
</div>
|
|
5299
|
+
)}
|
|
5300
|
+
{mapping.abilities?.length > 0 && (
|
|
5301
|
+
<div style={{ marginBottom: 8 }}>
|
|
5302
|
+
<div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 4 }}>Abilities:</div>
|
|
5303
|
+
<div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
|
|
5304
|
+
{mapping.abilities.map((ability, i) => (
|
|
5305
|
+
<span key={i} style={{
|
|
5306
|
+
padding: '2px 6px',
|
|
5307
|
+
borderRadius: 8,
|
|
5308
|
+
background: 'rgba(63, 185, 80, 0.15)',
|
|
5309
|
+
color: 'var(--accent-green)',
|
|
5310
|
+
fontSize: 10,
|
|
5311
|
+
}}>
|
|
5312
|
+
{ability}
|
|
5313
|
+
</span>
|
|
5314
|
+
))}
|
|
5315
|
+
</div>
|
|
5316
|
+
</div>
|
|
5317
|
+
)}
|
|
5318
|
+
{mapping.workflowRef && (
|
|
5319
|
+
<div style={{ fontSize: 11, color: 'var(--text-muted)' }}>
|
|
5320
|
+
Workflow: <code style={{ color: 'var(--accent-cyan)' }}>{mapping.workflowRef}</code>
|
|
5321
|
+
</div>
|
|
5322
|
+
)}
|
|
5323
|
+
</div>
|
|
5324
|
+
))}
|
|
5325
|
+
</div>
|
|
5326
|
+
</div>
|
|
5327
|
+
)}
|
|
5328
|
+
|
|
4352
5329
|
{/* Workflow Steps */}
|
|
4353
5330
|
{agent.workflow?.length > 0 && (
|
|
4354
5331
|
<div className="card" style={{ marginBottom: 16 }}>
|
|
@@ -4482,10 +5459,39 @@ export function createDashboardHTML() {
|
|
|
4482
5459
|
onClick={() => onSelectAgent(agent.agentId)}
|
|
4483
5460
|
style={{ cursor: 'pointer' }}
|
|
4484
5461
|
>
|
|
4485
|
-
<
|
|
4486
|
-
{agent.
|
|
4487
|
-
|
|
4488
|
-
|
|
5462
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flex: 1, minWidth: 0 }}>
|
|
5463
|
+
<span className="agent-name" title={agent.description || ''} style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
5464
|
+
{agent.displayName || agent.agentId}
|
|
5465
|
+
</span>
|
|
5466
|
+
{/* PRD-2026-004: Meta-agent badges */}
|
|
5467
|
+
{agent.metaAgent && (
|
|
5468
|
+
<span style={{
|
|
5469
|
+
padding: '1px 6px',
|
|
5470
|
+
borderRadius: 8,
|
|
5471
|
+
background: 'rgba(163, 113, 247, 0.2)',
|
|
5472
|
+
color: 'var(--accent-purple)',
|
|
5473
|
+
fontSize: 9,
|
|
5474
|
+
fontWeight: 600,
|
|
5475
|
+
flexShrink: 0,
|
|
5476
|
+
}}>
|
|
5477
|
+
META
|
|
5478
|
+
</span>
|
|
5479
|
+
)}
|
|
5480
|
+
{agent.archetype && (
|
|
5481
|
+
<span style={{
|
|
5482
|
+
padding: '1px 6px',
|
|
5483
|
+
borderRadius: 8,
|
|
5484
|
+
background: 'rgba(88, 166, 255, 0.2)',
|
|
5485
|
+
color: 'var(--accent-blue)',
|
|
5486
|
+
fontSize: 9,
|
|
5487
|
+
fontWeight: 600,
|
|
5488
|
+
flexShrink: 0,
|
|
5489
|
+
}}>
|
|
5490
|
+
ARCH
|
|
5491
|
+
</span>
|
|
5492
|
+
)}
|
|
5493
|
+
</div>
|
|
5494
|
+
<span style={{ color: agent.enabled ? 'var(--accent-green)' : 'var(--text-muted)', flexShrink: 0 }}>
|
|
4489
5495
|
{agent.enabled ? 'active' : 'disabled'}
|
|
4490
5496
|
</span>
|
|
4491
5497
|
</div>
|
|
@@ -4542,6 +5548,24 @@ export function createDashboardHTML() {
|
|
|
4542
5548
|
|
|
4543
5549
|
// Traces card component (clickable)
|
|
4544
5550
|
function TracesCard({ traces, onSelectTrace }) {
|
|
5551
|
+
// Provider colors matching the Provider Usage histogram
|
|
5552
|
+
const providerColors = {
|
|
5553
|
+
claude: { bg: 'rgba(255, 145, 77, 0.15)', color: 'var(--accent-orange)' },
|
|
5554
|
+
grok: { bg: 'rgba(163, 113, 247, 0.15)', color: 'var(--accent-purple)' },
|
|
5555
|
+
gemini: { bg: 'rgba(88, 166, 255, 0.15)', color: 'var(--accent-blue)' },
|
|
5556
|
+
codex: { bg: 'rgba(63, 185, 80, 0.15)', color: 'var(--accent-green)' },
|
|
5557
|
+
opencode: { bg: 'rgba(63, 185, 185, 0.15)', color: 'var(--accent-cyan)' },
|
|
5558
|
+
antigravity: { bg: 'rgba(255, 200, 77, 0.15)', color: '#ffc84d' },
|
|
5559
|
+
cursor: { bg: 'rgba(88, 166, 255, 0.15)', color: 'var(--accent-blue)' },
|
|
5560
|
+
'local-llm': { bg: 'rgba(163, 163, 163, 0.15)', color: 'var(--text-secondary)' },
|
|
5561
|
+
};
|
|
5562
|
+
|
|
5563
|
+
const getProviderStyle = (provider) => {
|
|
5564
|
+
const lowerProvider = provider?.toLowerCase() || '';
|
|
5565
|
+
const colors = providerColors[lowerProvider] || { bg: 'var(--bg-secondary)', color: 'var(--text-secondary)' };
|
|
5566
|
+
return colors;
|
|
5567
|
+
};
|
|
5568
|
+
|
|
4545
5569
|
const formatDuration = (ms) => {
|
|
4546
5570
|
if (!ms) return null;
|
|
4547
5571
|
if (ms < 1000) return \`\${Math.round(ms)}ms\`;
|
|
@@ -4708,7 +5732,7 @@ export function createDashboardHTML() {
|
|
|
4708
5732
|
</span>
|
|
4709
5733
|
</div>
|
|
4710
5734
|
|
|
4711
|
-
{/* Providers row */}
|
|
5735
|
+
{/* Providers row - colored bubbles */}
|
|
4712
5736
|
{trace.providers && trace.providers.length > 0 && (
|
|
4713
5737
|
<div style={{
|
|
4714
5738
|
display: 'flex',
|
|
@@ -4716,21 +5740,25 @@ export function createDashboardHTML() {
|
|
|
4716
5740
|
marginTop: '6px',
|
|
4717
5741
|
flexWrap: 'wrap',
|
|
4718
5742
|
}}>
|
|
4719
|
-
{trace.providers.slice(0, 4).map(provider =>
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
5743
|
+
{trace.providers.slice(0, 4).map(provider => {
|
|
5744
|
+
const style = getProviderStyle(provider);
|
|
5745
|
+
return (
|
|
5746
|
+
<span
|
|
5747
|
+
key={provider}
|
|
5748
|
+
style={{
|
|
5749
|
+
padding: '2px 8px',
|
|
5750
|
+
borderRadius: '10px',
|
|
5751
|
+
background: style.bg,
|
|
5752
|
+
color: style.color,
|
|
5753
|
+
fontSize: '10px',
|
|
5754
|
+
fontWeight: 600,
|
|
5755
|
+
border: \`1px solid \${style.color}30\`,
|
|
5756
|
+
}}
|
|
5757
|
+
>
|
|
5758
|
+
{provider}
|
|
5759
|
+
</span>
|
|
5760
|
+
);
|
|
5761
|
+
})}
|
|
4734
5762
|
{trace.providers.length > 4 && (
|
|
4735
5763
|
<span style={{
|
|
4736
5764
|
padding: '2px 6px',
|
|
@@ -4944,6 +5972,293 @@ export function createDashboardHTML() {
|
|
|
4944
5972
|
);
|
|
4945
5973
|
}
|
|
4946
5974
|
|
|
5975
|
+
// PRD-2026-003: Classification Health Badge
|
|
5976
|
+
// Compact badge showing classification confidence and guard status
|
|
5977
|
+
function ClassificationHealthBadge({ classification, showDetails = false }) {
|
|
5978
|
+
if (!classification) {
|
|
5979
|
+
return null;
|
|
5980
|
+
}
|
|
5981
|
+
|
|
5982
|
+
const { taskType, confidence, guardResults } = classification;
|
|
5983
|
+
const guardPassRate = guardResults && guardResults.length > 0
|
|
5984
|
+
? guardResults.filter(g => g.passed).length / guardResults.length
|
|
5985
|
+
: 1.0;
|
|
5986
|
+
|
|
5987
|
+
// Determine badge color based on confidence
|
|
5988
|
+
const getConfidenceLevel = (conf) => {
|
|
5989
|
+
if (conf >= 0.8) return { color: 'var(--accent-green)', label: 'high', bg: 'rgba(63, 185, 80, 0.15)' };
|
|
5990
|
+
if (conf >= 0.6) return { color: 'var(--accent-yellow)', label: 'medium', bg: 'rgba(210, 153, 34, 0.15)' };
|
|
5991
|
+
return { color: 'var(--accent-red)', label: 'low', bg: 'rgba(248, 81, 73, 0.15)' };
|
|
5992
|
+
};
|
|
5993
|
+
|
|
5994
|
+
const confidenceLevel = getConfidenceLevel(confidence || 0);
|
|
5995
|
+
const guardStatus = guardPassRate >= 0.95 ? 'pass' : guardPassRate >= 0.5 ? 'partial' : 'fail';
|
|
5996
|
+
|
|
5997
|
+
const badgeStyle = {
|
|
5998
|
+
display: 'inline-flex',
|
|
5999
|
+
alignItems: 'center',
|
|
6000
|
+
gap: '4px',
|
|
6001
|
+
padding: '2px 8px',
|
|
6002
|
+
borderRadius: '12px',
|
|
6003
|
+
fontSize: '11px',
|
|
6004
|
+
fontWeight: 500,
|
|
6005
|
+
background: confidenceLevel.bg,
|
|
6006
|
+
color: confidenceLevel.color,
|
|
6007
|
+
};
|
|
6008
|
+
|
|
6009
|
+
if (!showDetails) {
|
|
6010
|
+
// Compact mode - just show task type with confidence indicator
|
|
6011
|
+
return (
|
|
6012
|
+
<span style={badgeStyle} title={\`\${taskType} (confidence: \${((confidence || 0) * 100).toFixed(0)}%)\`}>
|
|
6013
|
+
🎯 {taskType}
|
|
6014
|
+
</span>
|
|
6015
|
+
);
|
|
6016
|
+
}
|
|
6017
|
+
|
|
6018
|
+
// Detailed mode - show confidence and guard status
|
|
6019
|
+
return (
|
|
6020
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
6021
|
+
<span style={badgeStyle}>
|
|
6022
|
+
🎯 {taskType}
|
|
6023
|
+
</span>
|
|
6024
|
+
<span style={{
|
|
6025
|
+
...badgeStyle,
|
|
6026
|
+
background: confidenceLevel.bg,
|
|
6027
|
+
color: confidenceLevel.color,
|
|
6028
|
+
}}>
|
|
6029
|
+
{((confidence || 0) * 100).toFixed(0)}% conf
|
|
6030
|
+
</span>
|
|
6031
|
+
{guardResults && guardResults.length > 0 && (
|
|
6032
|
+
<span style={{
|
|
6033
|
+
...badgeStyle,
|
|
6034
|
+
background: guardStatus === 'pass' ? 'rgba(63, 185, 80, 0.15)' :
|
|
6035
|
+
guardStatus === 'partial' ? 'rgba(210, 153, 34, 0.15)' :
|
|
6036
|
+
'rgba(248, 81, 73, 0.15)',
|
|
6037
|
+
color: guardStatus === 'pass' ? 'var(--accent-green)' :
|
|
6038
|
+
guardStatus === 'partial' ? 'var(--accent-yellow)' :
|
|
6039
|
+
'var(--accent-red)',
|
|
6040
|
+
}}>
|
|
6041
|
+
🛡️ {guardResults.filter(g => g.passed).length}/{guardResults.length}
|
|
6042
|
+
</span>
|
|
6043
|
+
)}
|
|
6044
|
+
</div>
|
|
6045
|
+
);
|
|
6046
|
+
}
|
|
6047
|
+
|
|
6048
|
+
// PRD-2026-003: Task Classification Card
|
|
6049
|
+
function TaskClassificationCard({ classification }) {
|
|
6050
|
+
if (!classification || classification.totalClassifications === 0) {
|
|
6051
|
+
return null; // Don't show card if no classification data
|
|
6052
|
+
}
|
|
6053
|
+
|
|
6054
|
+
const {
|
|
6055
|
+
totalClassifications,
|
|
6056
|
+
byTaskType,
|
|
6057
|
+
guardPassRate,
|
|
6058
|
+
averageConfidence,
|
|
6059
|
+
fallbackRate,
|
|
6060
|
+
sampleSize,
|
|
6061
|
+
} = classification;
|
|
6062
|
+
|
|
6063
|
+
const taskTypes = Object.entries(byTaskType || {}).sort((a, b) => b[1] - a[1]);
|
|
6064
|
+
const topTaskTypes = taskTypes.slice(0, 5);
|
|
6065
|
+
|
|
6066
|
+
const getConfidenceColor = (confidence) => {
|
|
6067
|
+
if (confidence >= 0.8) return 'var(--accent-green)';
|
|
6068
|
+
if (confidence >= 0.6) return 'var(--accent-yellow)';
|
|
6069
|
+
return 'var(--accent-red)';
|
|
6070
|
+
};
|
|
6071
|
+
|
|
6072
|
+
const getGuardColor = (rate) => {
|
|
6073
|
+
if (rate >= 0.95) return 'var(--accent-green)';
|
|
6074
|
+
if (rate >= 0.8) return 'var(--accent-yellow)';
|
|
6075
|
+
return 'var(--accent-red)';
|
|
6076
|
+
};
|
|
6077
|
+
|
|
6078
|
+
const statBoxStyle = {
|
|
6079
|
+
background: 'var(--bg-tertiary)',
|
|
6080
|
+
borderRadius: '8px',
|
|
6081
|
+
padding: '16px',
|
|
6082
|
+
display: 'flex',
|
|
6083
|
+
flexDirection: 'column',
|
|
6084
|
+
gap: '8px',
|
|
6085
|
+
};
|
|
6086
|
+
|
|
6087
|
+
const iconStyle = {
|
|
6088
|
+
width: '32px',
|
|
6089
|
+
height: '32px',
|
|
6090
|
+
borderRadius: '8px',
|
|
6091
|
+
display: 'flex',
|
|
6092
|
+
alignItems: 'center',
|
|
6093
|
+
justifyContent: 'center',
|
|
6094
|
+
fontSize: '16px',
|
|
6095
|
+
marginBottom: '4px',
|
|
6096
|
+
};
|
|
6097
|
+
|
|
6098
|
+
return (
|
|
6099
|
+
<div className="card">
|
|
6100
|
+
<div className="card-header">
|
|
6101
|
+
<span className="card-title">Task Classification (PRD-2026-003)</span>
|
|
6102
|
+
<span style={{
|
|
6103
|
+
background: 'var(--accent-purple)',
|
|
6104
|
+
color: 'white',
|
|
6105
|
+
padding: '2px 8px',
|
|
6106
|
+
borderRadius: '12px',
|
|
6107
|
+
fontSize: '11px',
|
|
6108
|
+
fontWeight: 500,
|
|
6109
|
+
}}>
|
|
6110
|
+
{totalClassifications} classified
|
|
6111
|
+
</span>
|
|
6112
|
+
</div>
|
|
6113
|
+
|
|
6114
|
+
<div style={{
|
|
6115
|
+
display: 'grid',
|
|
6116
|
+
gridTemplateColumns: 'repeat(4, 1fr)',
|
|
6117
|
+
gap: '12px',
|
|
6118
|
+
marginTop: '12px'
|
|
6119
|
+
}}>
|
|
6120
|
+
{/* Total Classifications */}
|
|
6121
|
+
<div style={statBoxStyle}>
|
|
6122
|
+
<div style={{ ...iconStyle, background: 'rgba(163, 113, 247, 0.15)' }}>
|
|
6123
|
+
<span style={{ color: 'var(--accent-purple)' }}>🎯</span>
|
|
6124
|
+
</div>
|
|
6125
|
+
<div style={{ fontSize: '28px', fontWeight: 700, color: 'var(--text-primary)', lineHeight: 1 }}>
|
|
6126
|
+
{totalClassifications}
|
|
6127
|
+
</div>
|
|
6128
|
+
<div style={{ fontSize: '12px', color: 'var(--text-muted)', fontWeight: 500 }}>Classifications</div>
|
|
6129
|
+
</div>
|
|
6130
|
+
|
|
6131
|
+
{/* Average Confidence */}
|
|
6132
|
+
<div style={statBoxStyle}>
|
|
6133
|
+
<div style={{ ...iconStyle, background: 'rgba(63, 185, 80, 0.15)' }}>
|
|
6134
|
+
<span style={{ color: 'var(--accent-green)' }}>📊</span>
|
|
6135
|
+
</div>
|
|
6136
|
+
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4px' }}>
|
|
6137
|
+
<span style={{ fontSize: '28px', fontWeight: 700, color: getConfidenceColor(averageConfidence), lineHeight: 1 }}>
|
|
6138
|
+
{(averageConfidence * 100).toFixed(0)}
|
|
6139
|
+
</span>
|
|
6140
|
+
<span style={{ fontSize: '14px', color: 'var(--text-muted)' }}>%</span>
|
|
6141
|
+
</div>
|
|
6142
|
+
<div style={{ fontSize: '12px', color: 'var(--text-muted)', fontWeight: 500 }}>Avg Confidence</div>
|
|
6143
|
+
<div style={{
|
|
6144
|
+
height: '4px',
|
|
6145
|
+
background: 'var(--bg-secondary)',
|
|
6146
|
+
borderRadius: '2px',
|
|
6147
|
+
overflow: 'hidden',
|
|
6148
|
+
marginTop: '4px'
|
|
6149
|
+
}}>
|
|
6150
|
+
<div style={{
|
|
6151
|
+
height: '100%',
|
|
6152
|
+
width: \`\${averageConfidence * 100}%\`,
|
|
6153
|
+
background: getConfidenceColor(averageConfidence),
|
|
6154
|
+
borderRadius: '2px',
|
|
6155
|
+
transition: 'width 0.3s ease'
|
|
6156
|
+
}}></div>
|
|
6157
|
+
</div>
|
|
6158
|
+
</div>
|
|
6159
|
+
|
|
6160
|
+
{/* Guard Pass Rate */}
|
|
6161
|
+
<div style={statBoxStyle}>
|
|
6162
|
+
<div style={{ ...iconStyle, background: 'rgba(56, 211, 159, 0.15)' }}>
|
|
6163
|
+
<span style={{ color: 'var(--accent-cyan)' }}>🛡️</span>
|
|
6164
|
+
</div>
|
|
6165
|
+
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4px' }}>
|
|
6166
|
+
<span style={{ fontSize: '28px', fontWeight: 700, color: getGuardColor(guardPassRate), lineHeight: 1 }}>
|
|
6167
|
+
{(guardPassRate * 100).toFixed(0)}
|
|
6168
|
+
</span>
|
|
6169
|
+
<span style={{ fontSize: '14px', color: 'var(--text-muted)' }}>%</span>
|
|
6170
|
+
</div>
|
|
6171
|
+
<div style={{ fontSize: '12px', color: 'var(--text-muted)', fontWeight: 500 }}>Guard Pass Rate</div>
|
|
6172
|
+
<div style={{
|
|
6173
|
+
height: '4px',
|
|
6174
|
+
background: 'var(--bg-secondary)',
|
|
6175
|
+
borderRadius: '2px',
|
|
6176
|
+
overflow: 'hidden',
|
|
6177
|
+
marginTop: '4px'
|
|
6178
|
+
}}>
|
|
6179
|
+
<div style={{
|
|
6180
|
+
height: '100%',
|
|
6181
|
+
width: \`\${guardPassRate * 100}%\`,
|
|
6182
|
+
background: getGuardColor(guardPassRate),
|
|
6183
|
+
borderRadius: '2px',
|
|
6184
|
+
transition: 'width 0.3s ease'
|
|
6185
|
+
}}></div>
|
|
6186
|
+
</div>
|
|
6187
|
+
</div>
|
|
6188
|
+
|
|
6189
|
+
{/* Fallback Rate */}
|
|
6190
|
+
<div style={statBoxStyle}>
|
|
6191
|
+
<div style={{ ...iconStyle, background: 'rgba(248, 81, 73, 0.15)' }}>
|
|
6192
|
+
<span style={{ color: 'var(--accent-red)' }}>⚠️</span>
|
|
6193
|
+
</div>
|
|
6194
|
+
<div style={{ display: 'flex', alignItems: 'baseline', gap: '4px' }}>
|
|
6195
|
+
<span style={{ fontSize: '28px', fontWeight: 700, color: fallbackRate > 0.1 ? 'var(--accent-yellow)' : 'var(--accent-green)', lineHeight: 1 }}>
|
|
6196
|
+
{(fallbackRate * 100).toFixed(0)}
|
|
6197
|
+
</span>
|
|
6198
|
+
<span style={{ fontSize: '14px', color: 'var(--text-muted)' }}>%</span>
|
|
6199
|
+
</div>
|
|
6200
|
+
<div style={{ fontSize: '12px', color: 'var(--text-muted)', fontWeight: 500 }}>Fallback Rate</div>
|
|
6201
|
+
<div style={{ fontSize: '10px', color: 'var(--text-muted)', marginTop: '2px' }}>
|
|
6202
|
+
No mapping selected
|
|
6203
|
+
</div>
|
|
6204
|
+
</div>
|
|
6205
|
+
</div>
|
|
6206
|
+
|
|
6207
|
+
{/* Task Type Distribution */}
|
|
6208
|
+
{topTaskTypes.length > 0 && (
|
|
6209
|
+
<div style={{ marginTop: '16px' }}>
|
|
6210
|
+
<div style={{ fontSize: '12px', color: 'var(--text-muted)', marginBottom: '8px', fontWeight: 500 }}>
|
|
6211
|
+
Task Type Distribution
|
|
6212
|
+
</div>
|
|
6213
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
|
6214
|
+
{topTaskTypes.map(([taskType, count]) => {
|
|
6215
|
+
const percentage = (count / totalClassifications) * 100;
|
|
6216
|
+
// PRD-2026-004: Task type colors including new types
|
|
6217
|
+
const taskTypeColors = {
|
|
6218
|
+
implementation: 'var(--accent-blue)',
|
|
6219
|
+
debugging: 'var(--accent-red)',
|
|
6220
|
+
refactoring: 'var(--accent-orange)',
|
|
6221
|
+
testing: 'var(--accent-green)',
|
|
6222
|
+
documentation: 'var(--accent-cyan)',
|
|
6223
|
+
analysis: 'var(--accent-purple)',
|
|
6224
|
+
review: 'var(--accent-yellow)',
|
|
6225
|
+
deployment: '#f85149', // Red-orange for deploy actions
|
|
6226
|
+
research: '#a371f7', // Purple for research
|
|
6227
|
+
unknown: 'var(--text-muted)',
|
|
6228
|
+
};
|
|
6229
|
+
const barColor = taskTypeColors[taskType] || 'var(--accent-purple)';
|
|
6230
|
+
return (
|
|
6231
|
+
<div key={taskType} style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
6232
|
+
<div style={{ width: '100px', fontSize: '12px', color: 'var(--text-secondary)', textTransform: 'capitalize' }}>
|
|
6233
|
+
{taskType}
|
|
6234
|
+
</div>
|
|
6235
|
+
<div style={{ flex: 1, height: '8px', background: 'var(--bg-secondary)', borderRadius: '4px', overflow: 'hidden' }}>
|
|
6236
|
+
<div style={{
|
|
6237
|
+
height: '100%',
|
|
6238
|
+
width: \`\${percentage}%\`,
|
|
6239
|
+
background: barColor,
|
|
6240
|
+
borderRadius: '4px',
|
|
6241
|
+
transition: 'width 0.3s ease'
|
|
6242
|
+
}}></div>
|
|
6243
|
+
</div>
|
|
6244
|
+
<div style={{ width: '60px', fontSize: '11px', color: 'var(--text-muted)', textAlign: 'right' }}>
|
|
6245
|
+
{count} ({percentage.toFixed(0)}%)
|
|
6246
|
+
</div>
|
|
6247
|
+
</div>
|
|
6248
|
+
);
|
|
6249
|
+
})}
|
|
6250
|
+
</div>
|
|
6251
|
+
{taskTypes.length > 5 && (
|
|
6252
|
+
<div style={{ fontSize: '10px', color: 'var(--text-muted)', marginTop: '8px' }}>
|
|
6253
|
+
+{taskTypes.length - 5} more task types
|
|
6254
|
+
</div>
|
|
6255
|
+
)}
|
|
6256
|
+
</div>
|
|
6257
|
+
)}
|
|
6258
|
+
</div>
|
|
6259
|
+
);
|
|
6260
|
+
}
|
|
6261
|
+
|
|
4947
6262
|
// Main Dashboard component
|
|
4948
6263
|
function Dashboard() {
|
|
4949
6264
|
const [view, setView] = useState('dashboard');
|
|
@@ -5204,6 +6519,8 @@ export function createDashboardHTML() {
|
|
|
5204
6519
|
|
|
5205
6520
|
<ExecutionStatsCard traces={data.traces || []} />
|
|
5206
6521
|
|
|
6522
|
+
<TaskClassificationCard classification={data.classification} />
|
|
6523
|
+
|
|
5207
6524
|
<TracesCard traces={data.traces || []} onSelectTrace={handleSelectTrace} />
|
|
5208
6525
|
</section>
|
|
5209
6526
|
)}
|