mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
b1c05bca83
Whether the AI Parsing model reads images is an admin setting now, and it lands with the two features that send a photo, so it has something to control. The setting is `vision: 'auto' | 'on' | 'off'` on the addon config. On Automatic a local Ollama is asked what the model can do (`/api/show`), and a cloud provider means no; Yes and No are applied as set. The old `multimodal` flag is not read: the admin screens wrote it false on every save, so a stored false meant nothing. The per-user toggle keeps its on/off meaning and is read by the extraction now. The lookup is server-side only: capped read, validated body, never throws, cached per server and model. GET /api/llm/capabilities answers it for the signed-in caller, and both photo pickers below ask it. The booking import takes JPG, PNG and WEBP when the model reads images. The photo goes as an image to every provider (image_url, an Anthropic image block, Ollama's native chat with `images`), capped at 1600px, and skips KDE Itinerary. A scanned PDF, which used to reach the model as empty text, has its first two pages drawn by pdf-parse and sent the same way. The rest is the import as it was: background job, review, the file attached to the booking, the linked cost. On a real qwen3.5:4b a train ticket came back as bare clock times until the router gave the model every type's field meanings, and its price as an object inside a string, which read as 4900; both are handled. Costs gets Scan receipt: a dialog laid out like the booking import's takes one photo, read on the import job queue, then reviewed in the existing expense editor pre-filled with merchant, total, currency, day and lines, the photo waiting in Receipts & Invoices to be uploaded on save. No table, no migration, no category guess, no document-type detection.
30 lines
1.4 KiB
TypeScript
30 lines
1.4 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { useBackgroundTasksStore, taskFoundSomething, type BackgroundImportTask } from './backgroundTasksStore'
|
|
|
|
const RECEIPT = { merchant: 'Café', date: null, total: 4, currency: 'EUR', items: [] }
|
|
|
|
beforeEach(() => useBackgroundTasksStore.setState({ tasks: [] }))
|
|
|
|
describe('backgroundTasksStore with a receipt scan', () => {
|
|
it('keeps what the scan read on the finished task', () => {
|
|
const s = useBackgroundTasksStore.getState()
|
|
s.addTask({ id: 'j1', tripId: '7', label: 'bill.jpg', total: 1, kind: 'costs' })
|
|
s.setDone('j1', '7', [], [], RECEIPT)
|
|
expect(useBackgroundTasksStore.getState().tasks[0]).toMatchObject({ status: 'done', kind: 'costs', receipt: RECEIPT, items: [] })
|
|
})
|
|
})
|
|
|
|
describe('taskFoundSomething', () => {
|
|
const task = (over: Partial<BackgroundImportTask>): BackgroundImportTask => ({ id: 'j', tripId: '7', label: 'x', status: 'done', done: 1, total: 1, ...over })
|
|
|
|
it('asks a receipt scan for its receipt', () => {
|
|
expect(taskFoundSomething(task({ kind: 'costs', items: [], receipt: RECEIPT }))).toBe(true)
|
|
expect(taskFoundSomething(task({ kind: 'costs', items: [], receipt: null }))).toBe(false)
|
|
})
|
|
|
|
it('asks a booking parse for its items', () => {
|
|
expect(taskFoundSomething(task({ kind: 'bookings', items: [{} as never] }))).toBe(true)
|
|
expect(taskFoundSomething(task({ items: [] }))).toBe(false)
|
|
})
|
|
})
|