'use server'; import { unstable_cache } from 'next/cache'; import { callAIProviderWithFallback, hasConfiguredAIProvider } from '@/lib/ai-provider'; import { getStockSentimentInsights } from './adanos.actions'; import { getBasicFinancials, getCompanyNews, getCompanyProfile, getQuote } from './finnhub.actions'; import { buildStockAnalysisPrompt, parseStockAnalysisResponse, type StockAIAnalysis, } from './stock-analysis.helpers'; import type { StockSentimentInsights } from './adanos.helpers'; export interface StockDetailInsights { sentimentInsights: StockSentimentInsights | null; aiAnalysis: StockAIAnalysis | null; } function normalizeHeadline(article: MarketNewsArticle) { return { headline: article.headline, summary: article.summary, source: article.source, datetime: article.datetime, }; } async function generateStockDetailInsights(normalizedSymbol: string): Promise { const sentimentPromise = getStockSentimentInsights(normalizedSymbol); const [quote, profile, basicFinancials, news, sentimentInsights] = await Promise.all([ getQuote(normalizedSymbol), getCompanyProfile(normalizedSymbol), getBasicFinancials(normalizedSymbol), getCompanyNews(normalizedSymbol), sentimentPromise, ]); if (!hasConfiguredAIProvider()) { return { sentimentInsights, aiAnalysis: null, }; } const hasAnalysisContext = Boolean(quote) || Boolean(profile) || Boolean(sentimentInsights) || Boolean(basicFinancials?.metric && Object.keys(basicFinancials.metric).length > 0) || news.length > 0; if (!hasAnalysisContext) { return { sentimentInsights, aiAnalysis: null, }; } try { const response = await callAIProviderWithFallback( buildStockAnalysisPrompt({ symbol: normalizedSymbol, quote, profile, basicFinancials, sentimentInsights, recentHeadlines: news.slice(0, 4).map(normalizeHeadline), }), ); return { sentimentInsights, aiAnalysis: parseStockAnalysisResponse( response, normalizedSymbol, profile?.name ?? sentimentInsights?.companyName ?? null, ), }; } catch (error) { console.error(`Failed to generate AI stock analysis for ${normalizedSymbol}`, error); return { sentimentInsights, aiAnalysis: null, }; } } export async function getStockDetailInsights(symbol: string): Promise { const normalizedSymbol = symbol.trim().toUpperCase(); if (!normalizedSymbol) { return { sentimentInsights: null, aiAnalysis: null, }; } return unstable_cache(() => generateStockDetailInsights(normalizedSymbol), ['stock-detail-insights', normalizedSymbol], { revalidate: 900, tags: [`stock-analysis:${normalizedSymbol}`], })(); } export async function getAIAnalysis( symbol: string, companyName?: string | null, ): Promise { const normalizedSymbol = symbol.trim().toUpperCase(); if (!normalizedSymbol || !hasConfiguredAIProvider()) { return null; } return unstable_cache( async () => { const [quote, profile, basicFinancials, news] = await Promise.all([ getQuote(normalizedSymbol), getCompanyProfile(normalizedSymbol), getBasicFinancials(normalizedSymbol), getCompanyNews(normalizedSymbol), ]); const hasAnalysisContext = Boolean(quote) || Boolean(profile) || Boolean(basicFinancials?.metric && Object.keys(basicFinancials.metric).length > 0) || news.length > 0; if (!hasAnalysisContext) { return null; } try { const response = await callAIProviderWithFallback( buildStockAnalysisPrompt({ symbol: normalizedSymbol, quote, profile, basicFinancials, sentimentInsights: null, recentHeadlines: news.slice(0, 4).map(normalizeHeadline), }), ); return parseStockAnalysisResponse( response, normalizedSymbol, companyName ?? profile?.name ?? null, ); } catch (error) { console.error(`Failed to generate AI stock analysis for ${normalizedSymbol}`, error); return null; } }, ['ai-stock-analysis', normalizedSymbol], { revalidate: 900, tags: [`stock-analysis:${normalizedSymbol}`], }, )(); }