diff --git a/src/components/home/HealthScoreModal/HealthScoreModal.tsx b/src/components/home/HealthScoreModal/HealthScoreModal.tsx index 2cf92e1..3c8f3a9 100644 --- a/src/components/home/HealthScoreModal/HealthScoreModal.tsx +++ b/src/components/home/HealthScoreModal/HealthScoreModal.tsx @@ -6,6 +6,7 @@ import React, { useEffect, useState } from 'react'; import { Icon } from '@iconify/react'; import { formatCurrency } from '../../../utils/format'; +import { getFinancialAdvice } from '../../../services/aiService'; import './HealthScoreModal.css'; interface HealthScoreModalProps { @@ -46,7 +47,8 @@ export const HealthScoreModal: React.FC = ({ }) => { const [showRules, setShowRules] = useState(false); const [animate, setAnimate] = useState(false); - // AI Advice state can be removed if strictly using tips, or kept as fallback + const [aiAdvice, setAiAdvice] = useState(''); + const [loadingAdvice, setLoadingAdvice] = useState(false); // --- Logic & Calculations --- @@ -54,15 +56,36 @@ export const HealthScoreModal: React.FC = ({ // const spendDiff = todaySpend - yesterdaySpend; // Unused now useEffect(() => { + let isMounted = true; if (isOpen) { setTimeout(() => setAnimate(true), 50); + + // Fetch AI Advice if not already fetched + if (!aiAdvice && !loadingAdvice) { // Only fetch if not already fetched or loading + setLoadingAdvice(true); + getFinancialAdvice({ + score, + totalAssets, + totalLiabilities, + metrics, + tips + }) + .then(advice => { + if (isMounted) setAiAdvice(advice); + }) + .catch(err => { + console.error("Failed to load AI advice", err); + }) + .finally(() => { + if (isMounted) setLoadingAdvice(false); + }); + } } else { setAnimate(false); setShowRules(false); // Reset view on close } - }, [isOpen]); - - const aiAdvice = ''; // Fallback placeholder if needed + return () => { isMounted = false; }; + }, [isOpen, aiAdvice, loadingAdvice, score, totalAssets, totalLiabilities, metrics, tips]); if (!isOpen) return null; @@ -79,13 +102,7 @@ export const HealthScoreModal: React.FC = ({ const level = getLevel(score); - // Dynamic Suggestion (Fallback) - const getStaticSuggestion = () => { - if (debtRatio > 50) return "负债率偏高(>50%)。建议优先偿还高息债务(如信用卡),避免产生不必要的利息支出。同时请审视非必要消费。"; - if (score < 60) return "建议建立强制储蓄计划,每月发工资后先存下一笔钱。同时,坚持每日记账能帮助你发现隐形浪费。"; - if (score >= 90) return "您的财务状况非常健康!目前的低负债率是很好的优势。建议考虑学习理财知识,让结余资金通过稳健投资实现增值。"; - return "财务状况良好。建议检查是否有闲置资金可以转入储蓄账户赚取收益,并尝试为自己设定一个年度储蓄目标。"; - }; + // --- Render Helpers --- @@ -238,12 +255,21 @@ export const HealthScoreModal: React.FC = ({ AI 智能建议
- {tips && tips.length > 0 ? ( - tips.map((tip, i) => ( -
{tip}
- )) - ) : ( -

{aiAdvice || getStaticSuggestion()}

+ {/* Static Tips */} + {tips && tips.length > 0 && tips.map((tip, i) => ( +
{tip}
+ ))} + + {/* AI Dynamic Advice */} + {loadingAdvice ? ( +
+ CFO 正在分析您的财务报表... +
+ ) : aiAdvice && ( +
+ + {aiAdvice} +
)}
diff --git a/src/services/aiService.ts b/src/services/aiService.ts index c5bb6d9..5ac4248 100644 --- a/src/services/aiService.ts +++ b/src/services/aiService.ts @@ -342,8 +342,12 @@ export async function getFinancialAdvice(context: { totalAssets: number; totalLiabilities: number; score: number; - todaySpend: number; - yesterdaySpend: number; + metrics: { + debtRatio: number; + survivalMonths: number; + budgetHealth: number; + }; + tips: string[]; }): Promise { // Generate a simple hash of the context to detect data changes const currentHash = JSON.stringify(context); @@ -360,25 +364,25 @@ export async function getFinancialAdvice(context: { // Construct a prompt for the AI const prompt = `System: 你是全能的首席财务官 (CFO) 兼个人财富导师。 -Context: 用户希望获得深度、犀利且具有前瞻性的财务洞察。不要说废话,直击痛点或爽点。 +Context: 用户希望获得深度、犀利且具有前瞻性的财务洞察。基于用户的健康分数据和系统预生成的 Tips,给予综合评价。 User Financial Data: -- 综合评分: ${context.score} (S级标准: 90+, 警戒线: 60) +- 综合评分: ${context.score} +- 财务结构: 负债率 ${(context.metrics.debtRatio * 100).toFixed(1)}%, 生存期 ${context.metrics.survivalMonths.toFixed(1)} 个月 - 净资产: ¥${(context.totalAssets - context.totalLiabilities).toFixed(2)} -- 资产/负债: ¥${context.totalAssets.toFixed(2)} / ¥${context.totalLiabilities.toFixed(2)} -- 负债杠杆: ${context.totalAssets > 0 ? ((context.totalLiabilities / context.totalAssets) * 100).toFixed(1) : 0}% -- 短期收支: 今日支出 ¥${context.todaySpend.toFixed(2)} (对比昨日: ¥${context.yesterdaySpend.toFixed(2)}) +- 预算状态: ${context.metrics.budgetHealth > 0 ? '有结余 (优秀)' : context.metrics.budgetHealth < 0 ? '超支 (警告)' : '正常'} +- 系统 Tips: ${context.tips.join('; ')} Instruction: -请根据上述数据,运用"第一性原理"分析用户的核心财务健康度。 -1. 如果负债率过高(>30%),给出一条关于"债务雪崩法"或"债务雪球法"的具体行动建议。 -2. 如果资产健康但支出波动大,提示"拿铁因子"风险。 -3. 如果状态完美,建议关注"被动收入"或"抗通胀"配置。 +请根据上述数据,生成一段简短有力的财务点评。 +1. 甚至可以引用系统 Tips 中的关键数据来加强说服力。 +2. 如果生存期 < 3个月,必须强调建立应急储备的重要性。 +3. 语气要像个严厉但负责任的导师。 Output Requirements: -- 限制字数: 120字以内。 +- 限制字数: 100字以内。 - 风格: 睿智、冷静、一针见血。 -- 格式: 纯文本,适当使用Emoji (💡, 🚀, 🛡️) 作为视觉锚点。`; +- 格式: 纯文本,不要 markdown 标题。`; try { // 2. Request from AI