diff --git a/components/extensions/general/InvoiceInboxWorkspace.tsx b/components/extensions/general/InvoiceInboxWorkspace.tsx index 25b91bdd..aa0d1bec 100644 --- a/components/extensions/general/InvoiceInboxWorkspace.tsx +++ b/components/extensions/general/InvoiceInboxWorkspace.tsx @@ -452,10 +452,18 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) { onAttach={() => setAttachOpen(true)} isDeleting={isDeleting} onFieldsUpdated={(nextData) => { - setSelected((prev) => (prev ? { ...prev, extracted_data: nextData } : prev)) + // Guard against stale closure: if the user navigated to a + // different item between sending the PATCH and the response + // arriving, the captured `selected` is no longer the + // currently-selected one. Without the id check we'd write + // item A's payload onto item B's row. + const targetId = selected.id + setSelected((prev) => + prev?.id === targetId ? { ...prev, extracted_data: nextData } : prev + ) setItems((prev) => prev.map((it) => - it.id === selected.id ? { ...it, extracted_data: nextData } : it + it.id === targetId ? { ...it, extracted_data: nextData } : it ) ) }} diff --git a/extensions/general/invoice-inbox/__tests__/extract-invoice-fields.test.ts b/extensions/general/invoice-inbox/__tests__/extract-invoice-fields.test.ts index 3e3b043d..5e70a12f 100644 --- a/extensions/general/invoice-inbox/__tests__/extract-invoice-fields.test.ts +++ b/extensions/general/invoice-inbox/__tests__/extract-invoice-fields.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { describe, it, expect, vi, beforeEach, afterAll } from 'vitest' import { extractInvoiceFields } from '@/extensions/general/invoice-inbox/lib/extract-invoice-fields' // Mock the Bedrock SDK so tests drive the JSON parser without @@ -190,6 +190,3 @@ describe('extractInvoiceFields', () => { else delete process.env.AWS_SECRET_ACCESS_KEY }) }) - -// vitest doesn't auto-import afterAll -import { afterAll } from 'vitest' diff --git a/extensions/general/invoice-inbox/lib/extract-invoice-fields.ts b/extensions/general/invoice-inbox/lib/extract-invoice-fields.ts index d63ed50c..db51bfb1 100644 --- a/extensions/general/invoice-inbox/lib/extract-invoice-fields.ts +++ b/extensions/general/invoice-inbox/lib/extract-invoice-fields.ts @@ -17,12 +17,18 @@ import { createLogger } from '@/lib/logger' const log = createLogger('invoice-inbox-extract') -const MODEL = 'eu.anthropic.claude-sonnet-4-6' -// 4096 covers a 10-15 line invoice plus VAT breakdown comfortably. The JSON -// skeleton alone is ~200 tokens; 1500 left only ~1300 for content and -// silently truncated complex documents (response cut mid-JSON → -// JSON.parse throws → row lands with all-null fields). -const MAX_TOKENS = 4096 +// Both overridable via env vars so ops can swap models / raise token caps +// without a code deploy. Defaults match what's expected to be set in +// production (eu.anthropic.claude-sonnet-4-6 in eu-north-1, 8192 tokens — +// enough headroom for invoices with 20+ line items). +const MODEL = process.env.BEDROCK_MODEL_ID || 'eu.anthropic.claude-sonnet-4-6' +const MAX_TOKENS = (() => { + const parsed = Number(process.env.BEDROCK_MAX_TOKENS) + // Use the env value only if it's a positive number — `||` would also + // fall back on a deliberate `0`, masking what is really an invalid + // configuration rather than the intent to disable. + return Number.isFinite(parsed) && parsed > 0 ? parsed : 8192 +})() // Bedrock supports these document/image media types directly. HEIC/HEIF // are not on the list, so we skip AI for those — the inbox row still