feat: 添加 AI 分类功能,支持交易自动分类与标签生成
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user