집계만 /account/ai-comment 로 보내 코멘트 수신, 상단에 카드 표시(로딩·빈값 처리). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,10 @@ const realApi = {
|
||||
categoryStats({ type, year, month } = {}) {
|
||||
return http.get('/account/category-stats', { params: { type, year, month } })
|
||||
},
|
||||
// 통계 화면 집계 → AI 재무 코멘트(유료). 실패해도 화면을 막지 않도록 전역 배너 억제
|
||||
aiComment(payload) {
|
||||
return http.post('/account/ai-comment', payload, { timeout: 30000, suppressNetError: true })
|
||||
},
|
||||
netWorthTrend({ months, weeks, unit } = {}) {
|
||||
return http.get('/account/networth/trend', { params: { months, weeks, unit } })
|
||||
},
|
||||
@@ -262,7 +266,7 @@ const DEMO_WRITES = new Set([
|
||||
'createRecurring', 'updateRecurring', 'removeRecurring', 'runRecurrings',
|
||||
'createWallet', 'updateWallet', 'removeWallet', 'reorderWallets',
|
||||
'createCategory', 'updateCategory', 'removeCategory', 'reorderCategories', 'importCategories',
|
||||
'createTag', 'updateTag', 'removeTag', 'reorderTags', 'confirmEntry', 'ocrReceipt', 'parseReceiptAi', 'restore',
|
||||
'createTag', 'updateTag', 'removeTag', 'reorderTags', 'confirmEntry', 'ocrReceipt', 'parseReceiptAi', 'aiComment', 'restore',
|
||||
'createBudget', 'updateBudget', 'removeBudget', 'setExpectedIncome',
|
||||
'createHolding', 'updateHolding', 'removeHolding', 'addTrade', 'updateTrade', 'removeTrade',
|
||||
'refreshPrices', 'refreshAllPrices',
|
||||
|
||||
@@ -24,6 +24,8 @@ const catType = ref('EXPENSE') // 분류별 파이차트 구분
|
||||
const catData = ref([]) // [{category, total}]
|
||||
const categoryList = ref([]) // 분류 목록(대/소분류 매핑용)
|
||||
const trendData = ref([]) // [{month, netWorth}]
|
||||
const aiBullets = ref([]) // AI 재무 코멘트 문장들
|
||||
const aiLoading = ref(false)
|
||||
|
||||
const periodLabel = computed(() => `${year.value}년 ${String(month.value).padStart(2, '0')}월`)
|
||||
|
||||
@@ -264,6 +266,7 @@ async function load() {
|
||||
monthIncome.value = summary.totalIncome
|
||||
monthExpense.value = summary.totalExpense
|
||||
prevMonthExpense.value = prevSummary.totalExpense
|
||||
loadAiComment() // 집계가 준비된 뒤 AI 코멘트 생성(비동기, 화면 로딩 막지 않음)
|
||||
} catch (e) {
|
||||
error.value = e.response?.data?.message || '불러오지 못했습니다.'
|
||||
} finally {
|
||||
@@ -271,6 +274,29 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
// 이번 달 집계만 백엔드로 보내 AI 재무 코멘트를 받는다(거래 원문 미전송, 실패해도 무중단)
|
||||
async function loadAiComment() {
|
||||
aiLoading.value = true
|
||||
aiBullets.value = []
|
||||
try {
|
||||
const res = await accountApi.aiComment({
|
||||
year: year.value,
|
||||
month: month.value,
|
||||
income: monthIncome.value,
|
||||
expense: monthExpense.value,
|
||||
prevExpense: prevMonthExpense.value,
|
||||
budget: budgetTotal.value,
|
||||
spent: spentTotal.value,
|
||||
categories: (catData.value || []).slice(0, 8).map((c) => ({ category: c.category, total: c.total })),
|
||||
})
|
||||
aiBullets.value = res?.bullets || []
|
||||
} catch {
|
||||
aiBullets.value = []
|
||||
} finally {
|
||||
aiLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function setUnit(u) {
|
||||
unit.value = u
|
||||
loadBar()
|
||||
@@ -335,6 +361,15 @@ onMounted(async () => {
|
||||
<p v-if="loading" class="msg">불러오는 중...</p>
|
||||
|
||||
<template v-else>
|
||||
<!-- AI 재무 코멘트: 이번 달 집계 해석 -->
|
||||
<div v-if="aiLoading || aiBullets.length" class="ai-comment">
|
||||
<div class="ai-comment-head">✨ 이번 달 AI 코멘트</div>
|
||||
<p v-if="aiLoading" class="ai-comment-loading">집계를 분석하고 있어요…</p>
|
||||
<ul v-else class="ai-comment-list">
|
||||
<li v-for="(b, i) in aiBullets" :key="i">{{ b }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 상단: 예산 대비 지출(도넛) + 수입 대비 지출 -->
|
||||
<div class="top-grid">
|
||||
<div class="panel donut-panel">
|
||||
@@ -551,6 +586,36 @@ h2 {
|
||||
padding: 1rem 1.1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
/* AI 재무 코멘트 카드 */
|
||||
.ai-comment {
|
||||
border: 1px solid hsla(160, 100%, 37%, 0.45);
|
||||
border-radius: 12px;
|
||||
background: hsla(160, 100%, 37%, 0.07);
|
||||
padding: 0.9rem 1.1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.ai-comment-head {
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
color: hsla(160, 100%, 32%, 1);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.ai-comment-loading {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.6;
|
||||
margin: 0;
|
||||
}
|
||||
.ai-comment-list {
|
||||
margin: 0;
|
||||
padding-left: 1.1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.ai-comment-list li {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.panel-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user