feat: 添加 AI 分类功能,支持交易自动分类与标签生成

This commit is contained in:
2026-03-12 10:59:44 +08:00
parent 8cbe01dd9c
commit 6a00875246
12 changed files with 594 additions and 27 deletions

View File

@@ -1,4 +1,4 @@
import { Capacitor } from '@capacitor/core'
import { Capacitor } from '@capacitor/core'
import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite'
import { defineCustomElements as defineJeep } from 'jeep-sqlite/loader'
@@ -12,9 +12,35 @@ const TRANSACTION_TABLE_SQL = `
category TEXT DEFAULT 'Uncategorized',
date TEXT NOT NULL,
note TEXT,
sync_status INTEGER DEFAULT 0
sync_status INTEGER DEFAULT 0,
ai_category TEXT,
ai_tags TEXT,
ai_confidence REAL,
ai_reason TEXT,
ai_status TEXT DEFAULT 'idle',
ai_model TEXT,
ai_normalized_merchant TEXT
);
`
const MERCHANT_PROFILE_TABLE_SQL = `
CREATE TABLE IF NOT EXISTS merchant_profiles (
merchant_key TEXT PRIMARY KEY NOT NULL,
normalized_merchant TEXT NOT NULL,
category TEXT,
tags TEXT,
hit_count INTEGER DEFAULT 0,
updated_at TEXT NOT NULL
);
`
const TRANSACTION_REQUIRED_COLUMNS = {
ai_category: 'TEXT',
ai_tags: 'TEXT',
ai_confidence: 'REAL',
ai_reason: 'TEXT',
ai_status: "TEXT DEFAULT 'idle'",
ai_model: 'TEXT',
ai_normalized_merchant: 'TEXT',
}
let sqliteConnection
let db
@@ -37,7 +63,7 @@ const ensureStoreReady = async (jeepEl, retries = 5) => {
}
await waitFor(200 * (attempt + 1))
}
throw new Error('jeep-sqlite IndexedDB store 未初始化成功')
throw new Error('jeep-sqlite IndexedDB store failed to initialize')
}
const ensureJeepElement = async () => {
@@ -66,6 +92,17 @@ const ensureJeepElement = async () => {
await CapacitorSQLite.initWebStore()
}
const ensureTableColumns = async (tableName, requiredColumns) => {
const result = await db.query(`PRAGMA table_info(${tableName});`)
const existingColumns = new Set((result?.values || []).map((row) => row.name))
for (const [columnName, definition] of Object.entries(requiredColumns)) {
if (!existingColumns.has(columnName)) {
await db.execute(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${definition};`)
}
}
}
const prepareConnection = async () => {
if (!sqliteConnection) {
sqliteConnection = new SQLiteConnection(CapacitorSQLite)
@@ -89,6 +126,8 @@ const prepareConnection = async () => {
await db.open()
await db.execute(TRANSACTION_TABLE_SQL)
await db.execute(MERCHANT_PROFILE_TABLE_SQL)
await ensureTableColumns('transactions', TRANSACTION_REQUIRED_COLUMNS)
initialized = true
}
@@ -112,7 +151,7 @@ export const saveDbToStore = async () => {
try {
await sqliteConnection.saveToStore(DB_NAME)
} catch (error) {
console.warn('[sqlite] saveToStore 执行失败,数据将保留在内存中', error)
console.warn('[sqlite] saveToStore failed, data remains in memory until next sync', error)
}
}