vn6295337 Claude Opus 4.5 commited on
Commit
522e6f7
·
1 Parent(s): 19c947a

Add clickable links for news articles and sentiment sources

Browse files

- Fix news extraction to use 'results' field (Tavily API format)
- Add sentiment source links (Finnhub, Reddit) with scores
- Display news as clickable article links

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

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

frontend/src/components/MCPDataPanel.tsx CHANGED
@@ -182,10 +182,17 @@ export function MCPDataPanel({ metrics, rawData, mcpStatus, companyName, ticker,
182
  const newsArticles = React.useMemo(() => {
183
  if (!rawData) return []
184
 
185
- // Try to get articles from metrics.news.articles
186
  const newsData = rawData.metrics?.news || rawData.news
187
- if (newsData && 'articles' in newsData) {
188
- return (newsData.articles as Array<{title: string, url: string}>).slice(0, 4)
 
 
 
 
 
 
 
189
  }
190
 
191
  // Fallback: check if news is an array directly
@@ -196,6 +203,45 @@ export function MCPDataPanel({ metrics, rawData, mcpStatus, companyName, ticker,
196
  return []
197
  }, [rawData])
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  // Extract company profile info from raw_data if available
200
  const companyProfile = React.useMemo(() => {
201
  if (!rawData) return null
@@ -376,15 +422,39 @@ export function MCPDataPanel({ metrics, rawData, mcpStatus, companyName, ticker,
376
  color="text-pink-500"
377
  status={mcpStatus?.sentiment}
378
  >
379
- {groupedMetrics.sentiment.map((m, i) => (
380
- <DataItem
381
- key={i}
382
- label={m.metric}
383
- value={formatValue(m.value)}
384
- fiscalPeriod={m.fiscalPeriod}
385
- endDate={m.endDate}
386
- />
387
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  </MCPRow>
389
  </div>
390
  </div>
 
182
  const newsArticles = React.useMemo(() => {
183
  if (!rawData) return []
184
 
185
+ // Try to get articles from metrics.news (field name is "results" from Tavily API)
186
  const newsData = rawData.metrics?.news || rawData.news
187
+ if (newsData && typeof newsData === 'object') {
188
+ // Check "results" first (Tavily API format), then "articles" as fallback
189
+ const articles = (newsData as Record<string, unknown>).results || (newsData as Record<string, unknown>).articles
190
+ if (Array.isArray(articles)) {
191
+ return articles.slice(0, 4).map((a: Record<string, unknown>) => ({
192
+ title: a.title as string || a.content as string || 'News article',
193
+ url: a.url as string || a.link as string || '#'
194
+ }))
195
+ }
196
  }
197
 
198
  // Fallback: check if news is an array directly
 
203
  return []
204
  }, [rawData])
205
 
206
+ // Extract sentiment sources with links from raw_data
207
+ const sentimentSources = React.useMemo(() => {
208
+ if (!rawData) return []
209
+
210
+ const sentimentData = rawData.metrics?.sentiment || rawData.sentiment
211
+ if (!sentimentData || typeof sentimentData !== 'object') return []
212
+
213
+ const sources: Array<{name: string, score: number | null, url?: string}> = []
214
+ const metrics = (sentimentData as Record<string, unknown>).metrics as Record<string, unknown> | undefined
215
+
216
+ // Finnhub sentiment
217
+ if (metrics?.finnhub) {
218
+ const finnhub = metrics.finnhub as Record<string, unknown>
219
+ sources.push({
220
+ name: 'Finnhub',
221
+ score: finnhub.score as number || finnhub.sentiment_score as number || null,
222
+ url: 'https://finnhub.io'
223
+ })
224
+ }
225
+
226
+ // Reddit sentiment
227
+ if (metrics?.reddit) {
228
+ const reddit = metrics.reddit as Record<string, unknown>
229
+ sources.push({
230
+ name: 'Reddit',
231
+ score: reddit.score as number || null,
232
+ url: 'https://reddit.com'
233
+ })
234
+ }
235
+
236
+ // Composite score
237
+ const composite = (sentimentData as Record<string, unknown>).composite_score as number | undefined
238
+ if (composite !== undefined && sources.length === 0) {
239
+ sources.push({ name: 'Composite', score: composite })
240
+ }
241
+
242
+ return sources
243
+ }, [rawData])
244
+
245
  // Extract company profile info from raw_data if available
246
  const companyProfile = React.useMemo(() => {
247
  if (!rawData) return null
 
422
  color="text-pink-500"
423
  status={mcpStatus?.sentiment}
424
  >
425
+ {sentimentSources.length > 0 ? (
426
+ sentimentSources.map((s, i) => (
427
+ <span key={i} className="whitespace-nowrap">
428
+ {s.url ? (
429
+ <a
430
+ href={s.url}
431
+ target="_blank"
432
+ rel="noopener noreferrer"
433
+ className="text-pink-400 hover:text-pink-300 hover:underline"
434
+ >
435
+ {s.name}
436
+ </a>
437
+ ) : (
438
+ <span className="text-muted-foreground">{s.name}</span>
439
+ )}
440
+ {s.score !== null && (
441
+ <span className="text-foreground font-medium ml-1">
442
+ {s.score.toFixed(1)}
443
+ </span>
444
+ )}
445
+ </span>
446
+ ))
447
+ ) : groupedMetrics.sentiment.length > 0 ? (
448
+ groupedMetrics.sentiment.map((m, i) => (
449
+ <DataItem
450
+ key={i}
451
+ label={m.metric}
452
+ value={formatValue(m.value)}
453
+ fiscalPeriod={m.fiscalPeriod}
454
+ endDate={m.endDate}
455
+ />
456
+ ))
457
+ ) : null}
458
  </MCPRow>
459
  </div>
460
  </div>