vn6295337 Claude Opus 4.5 commited on
Commit
b3d4d5b
·
1 Parent(s): 2cf130c

UI improvements: icons, labels, status, and print layout

Browse files

- Match ProcessFlow MCP icons with MCPDataPanel (DollarSign, TrendingUp, Activity, MessageSquare)
- Add "Macro (US)" label in both ProcessFlow and MCPDataPanel
- Show MCP failure status (API unavailable, Partial data) instead of generic "No data"
- Update print layout to 8.27"x11" (most restrictive common size)
- Compact print styles for single-page fit

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

frontend/src/App.tsx CHANGED
@@ -607,7 +607,7 @@ Generated by Instant SWOT Agent`
607
 
608
  {/* MCP Source Data */}
609
  {metrics.length > 0 && (
610
- <MCPDataPanel metrics={metrics} rawData={analysisResult.raw_data} />
611
  )}
612
 
613
  {/* SWOT Cards */}
 
607
 
608
  {/* MCP Source Data */}
609
  {metrics.length > 0 && (
610
+ <MCPDataPanel metrics={metrics} rawData={analysisResult.raw_data} mcpStatus={mcpStatus} />
611
  )}
612
 
613
  {/* SWOT Cards */}
frontend/src/components/MCPDataPanel.tsx CHANGED
@@ -1,5 +1,5 @@
1
  import React from "react"
2
- import type { MetricEntry } from "@/lib/api"
3
  import type { MCPRawData } from "@/lib/types"
4
  import {
5
  DollarSign,
@@ -14,6 +14,7 @@ import {
14
  interface MCPDataPanelProps {
15
  metrics: MetricEntry[]
16
  rawData?: MCPRawData
 
17
  }
18
 
19
  // Format numbers for display
@@ -37,19 +38,33 @@ interface MCPRowProps {
37
  label: string
38
  color: string
39
  children: React.ReactNode
 
40
  }
41
 
42
- function MCPRow({ icon, label, color, children }: MCPRowProps) {
43
  const hasContent = React.Children.toArray(children).length > 0
 
 
 
 
 
 
 
 
 
44
 
45
  return (
46
- <div className={`flex items-center gap-2 py-1.5 px-3 border-b border-border last:border-b-0 ${!hasContent ? 'opacity-40' : ''}`}>
47
- <div className={`flex items-center gap-2 w-24 shrink-0 ${color}`}>
48
  {icon}
49
  <span className="text-xs font-medium">{label}</span>
50
  </div>
51
  <div className="flex-1 flex items-center gap-4 overflow-x-auto text-xs scrollbar-thin">
52
- {hasContent ? children : <span className="text-muted-foreground italic">No data</span>}
 
 
 
 
53
  </div>
54
  </div>
55
  )
@@ -91,7 +106,7 @@ function LinkItem({ title, url }: LinkItemProps) {
91
  )
92
  }
93
 
94
- export function MCPDataPanel({ metrics, rawData }: MCPDataPanelProps) {
95
  // Group metrics by source
96
  const groupedMetrics = React.useMemo(() => {
97
  const groups: Record<string, Array<{metric: string, value: string | number}>> = {
@@ -150,6 +165,7 @@ export function MCPDataPanel({ metrics, rawData }: MCPDataPanelProps) {
150
  icon={<DollarSign className="h-4 w-4" />}
151
  label="Financials"
152
  color="text-emerald-500"
 
153
  >
154
  {groupedMetrics.financials.map((m, i) => (
155
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
@@ -161,6 +177,7 @@ export function MCPDataPanel({ metrics, rawData }: MCPDataPanelProps) {
161
  icon={<TrendingUp className="h-4 w-4" />}
162
  label="Valuation"
163
  color="text-blue-500"
 
164
  >
165
  {groupedMetrics.valuation.map((m, i) => (
166
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
@@ -172,17 +189,19 @@ export function MCPDataPanel({ metrics, rawData }: MCPDataPanelProps) {
172
  icon={<Activity className="h-4 w-4" />}
173
  label="Volatility"
174
  color="text-yellow-500"
 
175
  >
176
  {groupedMetrics.volatility.map((m, i) => (
177
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
178
  ))}
179
  </MCPRow>
180
 
181
- {/* Macro */}
182
  <MCPRow
183
  icon={<Globe className="h-4 w-4" />}
184
- label="Macro"
185
  color="text-purple-500"
 
186
  >
187
  {groupedMetrics.macro.map((m, i) => (
188
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
@@ -194,6 +213,7 @@ export function MCPDataPanel({ metrics, rawData }: MCPDataPanelProps) {
194
  icon={<Newspaper className="h-4 w-4" />}
195
  label="News"
196
  color="text-orange-500"
 
197
  >
198
  {newsArticles.length > 0 ? (
199
  newsArticles.map((article, i) => (
@@ -211,6 +231,7 @@ export function MCPDataPanel({ metrics, rawData }: MCPDataPanelProps) {
211
  icon={<MessageSquare className="h-4 w-4" />}
212
  label="Sentiment"
213
  color="text-pink-500"
 
214
  >
215
  {groupedMetrics.sentiment.map((m, i) => (
216
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
 
1
  import React from "react"
2
+ import type { MetricEntry, MCPStatus } from "@/lib/api"
3
  import type { MCPRawData } from "@/lib/types"
4
  import {
5
  DollarSign,
 
14
  interface MCPDataPanelProps {
15
  metrics: MetricEntry[]
16
  rawData?: MCPRawData
17
+ mcpStatus?: MCPStatus
18
  }
19
 
20
  // Format numbers for display
 
38
  label: string
39
  color: string
40
  children: React.ReactNode
41
+ status?: 'idle' | 'executing' | 'completed' | 'partial' | 'failed'
42
  }
43
 
44
+ function MCPRow({ icon, label, color, children, status }: MCPRowProps) {
45
  const hasContent = React.Children.toArray(children).length > 0
46
+ const isFailed = status === 'failed'
47
+ const isPartial = status === 'partial'
48
+
49
+ // Determine what to show when no content
50
+ const getEmptyMessage = () => {
51
+ if (isFailed) return 'API unavailable'
52
+ if (isPartial) return 'Partial data'
53
+ return 'No data'
54
+ }
55
 
56
  return (
57
+ <div className={`flex items-center gap-2 py-1.5 px-3 border-b border-border last:border-b-0 ${!hasContent && !isFailed ? 'opacity-40' : ''}`}>
58
+ <div className={`flex items-center gap-2 w-28 shrink-0 ${isFailed ? 'text-red-400' : isPartial ? 'text-amber-400' : color}`}>
59
  {icon}
60
  <span className="text-xs font-medium">{label}</span>
61
  </div>
62
  <div className="flex-1 flex items-center gap-4 overflow-x-auto text-xs scrollbar-thin">
63
+ {hasContent ? children : (
64
+ <span className={`italic ${isFailed ? 'text-red-400' : isPartial ? 'text-amber-400' : 'text-muted-foreground'}`}>
65
+ {getEmptyMessage()}
66
+ </span>
67
+ )}
68
  </div>
69
  </div>
70
  )
 
106
  )
107
  }
108
 
109
+ export function MCPDataPanel({ metrics, rawData, mcpStatus }: MCPDataPanelProps) {
110
  // Group metrics by source
111
  const groupedMetrics = React.useMemo(() => {
112
  const groups: Record<string, Array<{metric: string, value: string | number}>> = {
 
165
  icon={<DollarSign className="h-4 w-4" />}
166
  label="Financials"
167
  color="text-emerald-500"
168
+ status={mcpStatus?.financials}
169
  >
170
  {groupedMetrics.financials.map((m, i) => (
171
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
 
177
  icon={<TrendingUp className="h-4 w-4" />}
178
  label="Valuation"
179
  color="text-blue-500"
180
+ status={mcpStatus?.valuation}
181
  >
182
  {groupedMetrics.valuation.map((m, i) => (
183
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
 
189
  icon={<Activity className="h-4 w-4" />}
190
  label="Volatility"
191
  color="text-yellow-500"
192
+ status={mcpStatus?.volatility}
193
  >
194
  {groupedMetrics.volatility.map((m, i) => (
195
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
196
  ))}
197
  </MCPRow>
198
 
199
+ {/* Macro (US) */}
200
  <MCPRow
201
  icon={<Globe className="h-4 w-4" />}
202
+ label="Macro (US)"
203
  color="text-purple-500"
204
+ status={mcpStatus?.macro}
205
  >
206
  {groupedMetrics.macro.map((m, i) => (
207
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
 
213
  icon={<Newspaper className="h-4 w-4" />}
214
  label="News"
215
  color="text-orange-500"
216
+ status={mcpStatus?.news}
217
  >
218
  {newsArticles.length > 0 ? (
219
  newsArticles.map((article, i) => (
 
231
  icon={<MessageSquare className="h-4 w-4" />}
232
  label="Sentiment"
233
  color="text-pink-500"
234
+ status={mcpStatus?.sentiment}
235
  >
236
  {groupedMetrics.sentiment.map((m, i) => (
237
  <DataItem key={i} label={m.metric} value={formatValue(m.value)} />
frontend/src/components/ProcessFlow.tsx CHANGED
@@ -14,10 +14,9 @@ import {
14
  GitBranch,
15
  TrendingUp,
16
  DollarSign,
17
- BarChart3,
18
  Globe,
19
  Newspaper,
20
- Heart,
21
  } from "lucide-react"
22
  import type { MCPStatus, LLMStatus } from "@/lib/api"
23
 
@@ -79,12 +78,12 @@ const NODES = {
79
  const MCP_START_X = NODES.researcher.x + NODE_SIZE / 2 + 40
80
  const MCP_GAP = 38
81
  const MCP_SERVERS = [
82
- { id: 'financials', label: 'Financials', icon: TrendingUp, x: MCP_START_X },
83
- { id: 'valuation', label: 'Valuation', icon: DollarSign, x: MCP_START_X + MCP_GAP },
84
- { id: 'volatility', label: 'Volatility', icon: BarChart3, x: MCP_START_X + MCP_GAP * 2 },
85
- { id: 'macro', label: 'Macro', icon: Globe, x: MCP_START_X + MCP_GAP * 3 },
86
  { id: 'news', label: 'News', icon: Newspaper, x: MCP_START_X + MCP_GAP * 4 },
87
- { id: 'sentiment', label: 'Sentiment', icon: Heart, x: MCP_START_X + MCP_GAP * 5 },
88
  ]
89
 
90
  const AGENTS_CENTER_X = (NODES.analyzer.x + NODES.editor.x) / 2
 
14
  GitBranch,
15
  TrendingUp,
16
  DollarSign,
17
+ Activity,
18
  Globe,
19
  Newspaper,
 
20
  } from "lucide-react"
21
  import type { MCPStatus, LLMStatus } from "@/lib/api"
22
 
 
78
  const MCP_START_X = NODES.researcher.x + NODE_SIZE / 2 + 40
79
  const MCP_GAP = 38
80
  const MCP_SERVERS = [
81
+ { id: 'financials', label: 'Financials', icon: DollarSign, x: MCP_START_X },
82
+ { id: 'valuation', label: 'Valuation', icon: TrendingUp, x: MCP_START_X + MCP_GAP },
83
+ { id: 'volatility', label: 'Volatility', icon: Activity, x: MCP_START_X + MCP_GAP * 2 },
84
+ { id: 'macro', label: 'Macro (US)', icon: Globe, x: MCP_START_X + MCP_GAP * 3 },
85
  { id: 'news', label: 'News', icon: Newspaper, x: MCP_START_X + MCP_GAP * 4 },
86
+ { id: 'sentiment', label: 'Sentiment', icon: MessageSquare, x: MCP_START_X + MCP_GAP * 5 },
87
  ]
88
 
89
  const AGENTS_CENTER_X = (NODES.analyzer.x + NODES.editor.x) / 2
frontend/src/index.css CHANGED
@@ -609,8 +609,8 @@
609
  header,
610
  footer,
611
  aside,
612
- .print\\:hidden,
613
- button:not(.print\\:block),
614
  [role="tablist"] {
615
  display: none !important;
616
  }
@@ -619,6 +619,8 @@
619
  main {
620
  width: 100% !important;
621
  max-width: 100% !important;
 
 
622
  }
623
 
624
  .container {
@@ -632,26 +634,55 @@
632
  opacity: 1 !important;
633
  }
634
 
635
- /* Clean card styles */
636
  .card {
637
  border: 1px solid #ddd !important;
638
  box-shadow: none !important;
639
  break-inside: avoid;
640
  page-break-inside: avoid;
 
 
 
 
 
641
  }
642
 
643
- /* Ensure text is black */
 
 
 
 
644
  body {
645
  color: black !important;
646
  background: white !important;
647
- font-size: 12pt;
648
- line-height: 1.5;
649
  }
650
 
651
- /* Page margins */
652
  @page {
653
- margin: 1cm;
654
- size: A4;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  }
656
 
657
  /* Prevent orphans and widows */
@@ -660,9 +691,23 @@
660
  widows: 3;
661
  }
662
 
663
- /* Add section labels for print */
664
- .text-strength::before { content: ""; }
665
- .text-weakness::before { content: ""; }
666
- .text-opportunity::before { content: ""; }
667
- .text-threat::before { content: ""; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
  }
 
609
  header,
610
  footer,
611
  aside,
612
+ .print\:hidden,
613
+ button:not(.print\:block),
614
  [role="tablist"] {
615
  display: none !important;
616
  }
 
619
  main {
620
  width: 100% !important;
621
  max-width: 100% !important;
622
+ padding: 0 !important;
623
+ margin: 0 !important;
624
  }
625
 
626
  .container {
 
634
  opacity: 1 !important;
635
  }
636
 
637
+ /* Compact card styles for single page fit */
638
  .card {
639
  border: 1px solid #ddd !important;
640
  box-shadow: none !important;
641
  break-inside: avoid;
642
  page-break-inside: avoid;
643
+ margin-bottom: 0.5rem !important;
644
+ }
645
+
646
+ .card-header {
647
+ padding: 0.5rem 0.75rem !important;
648
  }
649
 
650
+ .card-content {
651
+ padding: 0.5rem 0.75rem !important;
652
+ }
653
+
654
+ /* Ensure text is black - compact sizing */
655
  body {
656
  color: black !important;
657
  background: white !important;
658
+ font-size: 10pt;
659
+ line-height: 1.3;
660
  }
661
 
662
+ /* Page size: 8.27" x 11" (most restrictive common between A4 width and Letter height) */
663
  @page {
664
+ margin: 0.5in;
665
+ size: 8.27in 11in;
666
+ }
667
+
668
+ /* Compact SWOT grid */
669
+ .grid.md\:grid-cols-2 {
670
+ gap: 0.5rem !important;
671
+ }
672
+
673
+ /* Compact list items */
674
+ ul.space-y-2 {
675
+ gap: 0.25rem !important;
676
+ }
677
+
678
+ ul.space-y-2 li {
679
+ margin-bottom: 0.125rem !important;
680
+ }
681
+
682
+ /* Source data panel compact */
683
+ .divide-y > div {
684
+ padding-top: 0.25rem !important;
685
+ padding-bottom: 0.25rem !important;
686
  }
687
 
688
  /* Prevent orphans and widows */
 
691
  widows: 3;
692
  }
693
 
694
+ /* Hide process flow and metrics panel */
695
+ .h-\[260px\] {
696
+ display: none !important;
697
+ }
698
+
699
+ /* Compact headings */
700
+ h2 {
701
+ font-size: 14pt !important;
702
+ margin-bottom: 0.5rem !important;
703
+ }
704
+
705
+ h3 {
706
+ font-size: 11pt !important;
707
+ }
708
+
709
+ /* Quality evaluation compact */
710
+ .space-y-6 {
711
+ gap: 0.75rem !important;
712
+ }
713
  }
static/assets/{index-DRWwG9eL.css → index-CyMUTshU.css} RENAMED
The diff for this file is too large to render. See raw diff
 
static/assets/{index-DP1mfG7B.js → index-De5OIPDQ.js} RENAMED
The diff for this file is too large to render. See raw diff
 
static/index.html CHANGED
@@ -5,8 +5,8 @@
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>frontend</title>
8
- <script type="module" crossorigin src="/assets/index-DP1mfG7B.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-DRWwG9eL.css">
10
  </head>
11
  <body>
12
  <div id="root"></div>
 
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>frontend</title>
8
+ <script type="module" crossorigin src="/assets/index-De5OIPDQ.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-CyMUTshU.css">
10
  </head>
11
  <body>
12
  <div id="root"></div>