diff --git a/app/(root)/stocks/[symbol]/page.tsx b/app/(root)/stocks/[symbol]/page.tsx
index 3b46ff8..4956ac2 100644
--- a/app/(root)/stocks/[symbol]/page.tsx
+++ b/app/(root)/stocks/[symbol]/page.tsx
@@ -14,7 +14,7 @@ import {
import { auth } from '@/lib/better-auth/auth';
import { headers } from 'next/headers';
import { isStockInWatchlist } from '@/lib/actions/watchlist.actions';
-import { getStockDetailInsights } from '@/lib/actions/stock-analysis.actions';
+import { getStockSentimentInsights } from '@/lib/actions/adanos.actions';
import { formatSymbolForTradingView } from '@/lib/utils';
export default async function StockDetails({ params }: StockDetailsPageProps) {
@@ -26,11 +26,10 @@ export default async function StockDetails({ params }: StockDetailsPageProps) {
headers: await headers()
});
const userId = session?.user?.id;
- const [isInWatchlist, stockInsights] = await Promise.all([
+ const [isInWatchlist, sentimentInsights] = await Promise.all([
userId ? isStockInWatchlist(userId, symbol) : Promise.resolve(false),
- getStockDetailInsights(symbol),
+ getStockSentimentInsights(symbol),
]);
- const { sentimentInsights, aiAnalysis } = stockInsights;
return (
@@ -65,13 +64,16 @@ export default async function StockDetails({ params }: StockDetailsPageProps) {
-
+
diff --git a/components/stocks/StockAIAnalysisCard.tsx b/components/stocks/StockAIAnalysisCard.tsx
index 0f9d01b..57cbfd1 100644
--- a/components/stocks/StockAIAnalysisCard.tsx
+++ b/components/stocks/StockAIAnalysisCard.tsx
@@ -1,7 +1,12 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { getAIAnalysis } from '@/lib/actions/stock-analysis.actions';
import type { StockAIAnalysis } from '@/lib/actions/stock-analysis.helpers';
interface StockAIAnalysisCardProps {
- analysis: StockAIAnalysis | null;
+ symbol: string;
+ companyName?: string | null;
}
function getStanceClasses(stance: StockAIAnalysis['stance']) {
@@ -30,17 +35,88 @@ function renderList(title: string, items: string[]) {
);
}
-export default function StockAIAnalysisCard({ analysis }: StockAIAnalysisCardProps) {
+function LoadingSkeleton() {
+ return (
+
+
+
+
+ {[0, 1, 2].map((i) => (
+
+ ))}
+
+
+
+
+ );
+}
+
+function ErrorState() {
+ return (
+
+ AI Stock Analysis
+ Professional research note unavailable
+
+ The stock dashboard data loaded, but an AI research note could not be generated right now.
+
+
+ );
+}
+
+export default function StockAIAnalysisCard({ symbol, companyName }: StockAIAnalysisCardProps) {
+ const [analysis, setAnalysis] = useState
(null);
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ let cancelled = false;
+
+ async function fetchAnalysis() {
+ try {
+ const result = await getAIAnalysis(symbol, companyName);
+ if (!cancelled) {
+ setAnalysis(result);
+ }
+ } catch {
+ if (!cancelled) {
+ setAnalysis(null);
+ }
+ } finally {
+ if (!cancelled) {
+ setIsLoading(false);
+ }
+ }
+ }
+
+ fetchAnalysis();
+
+ return () => {
+ cancelled = true;
+ };
+ }, [symbol, companyName]);
+
+ if (isLoading) {
+ return ;
+ }
+
if (!analysis) {
- return (
-
- AI Stock Analysis
- Professional research note unavailable
-
- The stock dashboard data loaded, but an AI research note could not be generated right now.
-
-
- );
+ return ;
}
return (
diff --git a/diagnostic/image.png b/diagnostic/image.png
new file mode 100644
index 0000000..85b0c7a
Binary files /dev/null and b/diagnostic/image.png differ
diff --git a/lib/actions/stock-analysis.actions.ts b/lib/actions/stock-analysis.actions.ts
index 2439b02..f234845 100644
--- a/lib/actions/stock-analysis.actions.ts
+++ b/lib/actions/stock-analysis.actions.ts
@@ -102,3 +102,62 @@ export async function getStockDetailInsights(symbol: string): 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}`],
+ },
+ )();
+}