feat: 添加智能记账AI服务,包括聊天、语音输入、交易确认和财务建议,并集成到首页展示。

This commit is contained in:
2026-01-29 09:26:20 +08:00
parent 8aa881bcc1
commit 5cbb884fa7
4 changed files with 34 additions and 9 deletions

View File

@@ -21,6 +21,7 @@ interface DailyInsightCardProps {
top3Categories?: { name: string; amount: number }[];
todayTransactionCount?: number;
last7DaysSpend?: number[]; // 最近7天每日支出
recentTransactions?: { date: string; categoryName: string; amount: number; type: string; note?: string }[];
}
export const DailyInsightCard: React.FC<DailyInsightCardProps> = ({
@@ -39,6 +40,7 @@ export const DailyInsightCard: React.FC<DailyInsightCardProps> = ({
top3Categories,
todayTransactionCount,
last7DaysSpend,
recentTransactions,
}) => {
const [aiData, setAiData] = useState<{ spending: string; budget: string; emoji?: string; tip?: string } | null>(null);
const [isAiLoading, setIsAiLoading] = useState(false);
@@ -74,6 +76,7 @@ export const DailyInsightCard: React.FC<DailyInsightCardProps> = ({
top3Categories,
todayTransactionCount,
last7DaysSpend,
recentTransactions,
});
setAiData(result);
} catch (e) {
@@ -85,7 +88,7 @@ export const DailyInsightCard: React.FC<DailyInsightCardProps> = ({
const timer = setTimeout(fetchAI, 500);
return () => clearTimeout(timer);
}, [todaySpend, yesterdaySpend, monthlyBudget, monthlySpent, topCategory, maxTransaction, lastWeekSpend, streakDays, budgetRemaining, daysRemaining, weeklyTotal, avgDailySpend, top3Categories, todayTransactionCount, last7DaysSpend]);
}, [todaySpend, yesterdaySpend, monthlyBudget, monthlySpent, topCategory, maxTransaction, lastWeekSpend, streakDays, budgetRemaining, daysRemaining, weeklyTotal, avgDailySpend, top3Categories, todayTransactionCount, last7DaysSpend, recentTransactions]);
const getSpendingInsight = (today: number, yesterday: number) => {
if (aiData) return { text: <span>{aiData.spending}</span>, type: 'ai' };

View File

@@ -30,20 +30,21 @@
.home-greeting {
display: flex;
flex-direction: column;
gap: 2px;
/* Tighter gap */
gap: 0.75rem;
/* Increased gap for better breathing room */
}
/* ... */
.greeting-text {
font-family: 'Outfit', sans-serif;
font-size: 1.5rem;
/* Slightly smaller for compactness */
font-size: 1.75rem;
/* Restored size */
font-weight: 700;
margin: 0;
margin: 0.25rem 0;
/* Added vertical margin */
color: var(--text-primary);
line-height: 1.2;
line-height: 1.3;
}
/* ... */

View File

@@ -207,6 +207,16 @@ function Home() {
};
}, [todayTransactions, weekTransactions, categories, monthlyBudgetSpentTotal]);
const recentTransactionsForAI = useMemo(() => {
return weekTransactions.map(t => ({
date: new Date(t.transactionDate).toLocaleDateString('zh-CN'),
categoryName: categories.find(c => c.id === t.categoryId)?.name || '未知',
amount: t.amount,
type: t.type,
note: t.note
}));
}, [weekTransactions, categories]);
const handleQuickTransaction = () => {
navigate('/transactions?action=new');
};
@@ -513,6 +523,7 @@ function Home() {
lastWeekSpend={lastWeekSpend}
streakDays={streakInfo?.currentStreak || 0}
budgetRemaining={monthlyBudgetTotal - monthlyBudgetSpentTotal}
recentTransactions={recentTransactionsForAI}
avgDailySpend={avgDailySpend}
top3Categories={top3Categories}
todayTransactionCount={todayTransactionCount}

View File

@@ -319,6 +319,7 @@ export interface DailyInsightContext {
// 分类分析
topCategory?: { name: string; amount: number };
top3Categories?: { name: string; amount: number }[]; // 本月前三分类
recentTransactions?: { date: string; categoryName: string; amount: number; type: string; note?: string }[]; // 最近7天交易详情
// 交易详情
maxTransaction?: { note: string; amount: number };
@@ -333,7 +334,9 @@ export interface DailyInsightContext {
*/
export async function getDailyInsight(context: DailyInsightContext): Promise<{ spending: string; budget: string; emoji?: string; tip?: string }> {
// Hash needs to include new fields
const currentHash = JSON.stringify(context);
// Simplify recentTransactions for hash to avoid order issues or too long string
const contextForHash = { ...context, recentTransactions: context.recentTransactions?.length };
const currentHash = JSON.stringify(contextForHash);
const NOW = Date.now();
const CACHE_TTL = 30 * 60 * 1000; // 30 Minutes
@@ -348,6 +351,12 @@ export async function getDailyInsight(context: DailyInsightContext): Promise<{ s
const weekday = new Date().toLocaleDateString('zh-CN', { weekday: 'long' });
const weekDiff = context.lastWeekSpend !== undefined ? (context.todaySpend - context.lastWeekSpend) : 0;
// Format recent transactions for AI (simplify to save tokens)
const formattedTransactions = context.recentTransactions
?.slice(0, 50) // Limit to 50 items
.map(t => `${t.date}: ${t.categoryName} ${t.type === 'expense' ? '-' : '+'}${t.amount.toFixed(1)}${t.note ? ` (${t.note})` : ''}`)
.join('\n');
// Prepare enriched context for the backend
// We format some fields to help the AI understand better (like percentages)
const enrichedContext = {
@@ -356,7 +365,8 @@ export async function getDailyInsight(context: DailyInsightContext): Promise<{ s
weekDiff,
date: new Date().toLocaleDateString('zh-CN'),
monthProgressPercent: `${(context.monthProgress * 100).toFixed(0)}%`,
budgetUsedPercent: `${(context.monthlySpent / (context.monthlyBudget || 1) * 100).toFixed(0)}%`
budgetUsedPercent: `${(context.monthlySpent / (context.monthlyBudget || 1) * 100).toFixed(0)}%`,
recentTransactionsSummary: formattedTransactions // Send as specific field
};
try {