feat:新增ai分析,新增储值账户,优化通知规则
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import { buildTransactionEnrichmentMessages } from '../../lib/aiPrompt.js'
|
||||
import {
|
||||
buildFinanceChatMessages,
|
||||
buildTransactionEnrichmentMessages,
|
||||
} from '../../lib/aiPrompt.js'
|
||||
import { useSettingsStore } from '../../stores/settings.js'
|
||||
import {
|
||||
applyAiEnrichment,
|
||||
fetchRecentTransactionsForAi,
|
||||
markTransactionAiFailed,
|
||||
markTransactionAiRunning,
|
||||
} from '../transactionService.js'
|
||||
import { DeepSeekProvider } from './deepseekProvider.js'
|
||||
|
||||
@@ -24,11 +28,13 @@ const createProvider = (settingsStore) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const isAiReady = (settingsStore = useSettingsStore()) =>
|
||||
!!settingsStore.aiAutoCategoryEnabled && !!settingsStore.aiApiKey
|
||||
export const hasAiAccess = (settingsStore = useSettingsStore()) => !!settingsStore.aiApiKey
|
||||
|
||||
export const maybeEnrichTransactionWithAi = async (transaction) => {
|
||||
if (!transaction?.id) return transaction
|
||||
export const isAiReady = (settingsStore = useSettingsStore()) =>
|
||||
!!settingsStore.aiAutoCategoryEnabled && hasAiAccess(settingsStore)
|
||||
|
||||
export const maybeEnrichTransactionWithAi = async (transaction, options = {}) => {
|
||||
if (!transaction?.id || transaction.entryType === 'transfer') return transaction
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
if (!isAiReady(settingsStore)) {
|
||||
@@ -36,11 +42,16 @@ export const maybeEnrichTransactionWithAi = async (transaction) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const running = await markTransactionAiRunning(transaction.id, settingsStore.aiModel)
|
||||
if (running) {
|
||||
options.onProgress?.(running)
|
||||
}
|
||||
|
||||
const provider = createProvider(settingsStore)
|
||||
const similarTransactions = await fetchRecentTransactionsForAi(6, transaction.id)
|
||||
const messages = buildTransactionEnrichmentMessages({
|
||||
transaction,
|
||||
similarTransactions,
|
||||
similarTransactions: similarTransactions.filter((item) => item.entryType !== 'transfer'),
|
||||
})
|
||||
const result = await provider.enrichTransaction({ messages })
|
||||
const threshold = clampThreshold(settingsStore.aiAutoApplyThreshold)
|
||||
@@ -61,3 +72,19 @@ export const maybeEnrichTransactionWithAi = async (transaction) => {
|
||||
return failed || transaction
|
||||
}
|
||||
}
|
||||
|
||||
export const askFinanceAssistant = async ({ question, transactions = [], conversation = [] }) => {
|
||||
const settingsStore = useSettingsStore()
|
||||
if (!hasAiAccess(settingsStore)) {
|
||||
throw new Error('请先在设置页填写 DeepSeek API Key')
|
||||
}
|
||||
|
||||
const provider = createProvider(settingsStore)
|
||||
const messages = buildFinanceChatMessages({
|
||||
question,
|
||||
transactions,
|
||||
conversation,
|
||||
})
|
||||
|
||||
return provider.chat({ messages })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { normalizeEnrichmentResult } from '../../lib/aiPrompt.js'
|
||||
import { AiProvider } from './aiProvider.js'
|
||||
|
||||
const extractMessageContent = (data) => {
|
||||
const content = data?.choices?.[0]?.message?.content
|
||||
if (Array.isArray(content)) {
|
||||
return content
|
||||
.map((item) => (typeof item?.text === 'string' ? item.text : ''))
|
||||
.join('')
|
||||
.trim()
|
||||
}
|
||||
return typeof content === 'string' ? content.trim() : ''
|
||||
}
|
||||
|
||||
export class DeepSeekProvider extends AiProvider {
|
||||
constructor({ apiKey, baseUrl = 'https://api.deepseek.com', model = 'deepseek-chat' }) {
|
||||
super()
|
||||
@@ -9,7 +20,7 @@ export class DeepSeekProvider extends AiProvider {
|
||||
this.model = model
|
||||
}
|
||||
|
||||
async enrichTransaction({ messages }) {
|
||||
async createCompletion(payload) {
|
||||
if (!this.apiKey) {
|
||||
throw new Error('DeepSeek API key is required')
|
||||
}
|
||||
@@ -22,9 +33,7 @@ export class DeepSeekProvider extends AiProvider {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
temperature: 0.2,
|
||||
response_format: { type: 'json_object' },
|
||||
messages,
|
||||
...payload,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -33,8 +42,17 @@ export class DeepSeekProvider extends AiProvider {
|
||||
throw new Error(`DeepSeek request failed: ${response.status} ${detail}`.trim())
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const content = data?.choices?.[0]?.message?.content
|
||||
return response.json()
|
||||
}
|
||||
|
||||
async enrichTransaction({ messages }) {
|
||||
const data = await this.createCompletion({
|
||||
temperature: 0.2,
|
||||
response_format: { type: 'json_object' },
|
||||
messages,
|
||||
})
|
||||
|
||||
const content = extractMessageContent(data)
|
||||
if (!content) {
|
||||
throw new Error('DeepSeek returned an empty response')
|
||||
}
|
||||
@@ -52,4 +70,22 @@ export class DeepSeekProvider extends AiProvider {
|
||||
model: data?.model || this.model,
|
||||
}
|
||||
}
|
||||
|
||||
async chat({ messages, temperature = 0.6, maxTokens = 1200 }) {
|
||||
const data = await this.createCompletion({
|
||||
temperature,
|
||||
max_tokens: maxTokens,
|
||||
messages,
|
||||
})
|
||||
|
||||
const content = extractMessageContent(data)
|
||||
if (!content) {
|
||||
throw new Error('DeepSeek returned an empty response')
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
model: data?.model || this.model,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user