@pol-studios/db 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/context.js +21 -12786
- package/dist/auth/context.js.map +1 -1
- package/dist/auth/guards.js +12 -7640
- package/dist/auth/guards.js.map +1 -1
- package/dist/auth/hooks.js +25 -10591
- package/dist/auth/hooks.js.map +1 -1
- package/dist/auth/index.js +43 -13008
- package/dist/auth/index.js.map +1 -1
- package/dist/canvas-75Y7XMF3.js +1541 -0
- package/dist/canvas-75Y7XMF3.js.map +1 -0
- package/dist/chunk-2IFGILT3.js +532 -0
- package/dist/chunk-2IFGILT3.js.map +1 -0
- package/dist/chunk-3M2U6TXH.js +928 -0
- package/dist/chunk-3M2U6TXH.js.map +1 -0
- package/dist/chunk-3PJTNH2L.js +2778 -0
- package/dist/chunk-3PJTNH2L.js.map +1 -0
- package/dist/chunk-5ZYAEGCJ.js +416 -0
- package/dist/chunk-5ZYAEGCJ.js.map +1 -0
- package/dist/chunk-7HG6G25H.js +710 -0
- package/dist/chunk-7HG6G25H.js.map +1 -0
- package/dist/chunk-7XT7K4QT.js +2687 -0
- package/dist/chunk-7XT7K4QT.js.map +1 -0
- package/dist/chunk-AWFMICFV.js +158 -0
- package/dist/chunk-AWFMICFV.js.map +1 -0
- package/dist/chunk-BRTW7CO5.js +1467 -0
- package/dist/chunk-BRTW7CO5.js.map +1 -0
- package/dist/chunk-EL45Z26M.js +4194 -0
- package/dist/chunk-EL45Z26M.js.map +1 -0
- package/dist/chunk-ERGF2FCE.js +903 -0
- package/dist/chunk-ERGF2FCE.js.map +1 -0
- package/dist/chunk-GK7B66LY.js +135 -0
- package/dist/chunk-GK7B66LY.js.map +1 -0
- package/dist/chunk-GQI6WJGI.js +172 -0
- package/dist/chunk-GQI6WJGI.js.map +1 -0
- package/dist/chunk-H6365JPC.js +1858 -0
- package/dist/chunk-H6365JPC.js.map +1 -0
- package/dist/chunk-J4ZVCXZ4.js +1 -0
- package/dist/chunk-J4ZVCXZ4.js.map +1 -0
- package/dist/chunk-JUVE3DWY.js +433 -0
- package/dist/chunk-JUVE3DWY.js.map +1 -0
- package/dist/chunk-O3K7R32P.js +7555 -0
- package/dist/chunk-O3K7R32P.js.map +1 -0
- package/dist/chunk-P4UZ7IXC.js +42 -0
- package/dist/chunk-P4UZ7IXC.js.map +1 -0
- package/dist/chunk-SEY5UO2T.js +89 -0
- package/dist/chunk-SEY5UO2T.js.map +1 -0
- package/dist/chunk-USJYMRUO.js +86 -0
- package/dist/chunk-USJYMRUO.js.map +1 -0
- package/dist/chunk-XX3IWSPM.js +189 -0
- package/dist/chunk-XX3IWSPM.js.map +1 -0
- package/dist/chunk-Y3INY2CS.js +14 -0
- package/dist/chunk-Y3INY2CS.js.map +1 -0
- package/dist/chunk-ZTSBF536.js +1927 -0
- package/dist/chunk-ZTSBF536.js.map +1 -0
- package/dist/client/index.js +13 -141
- package/dist/client/index.js.map +1 -1
- package/dist/dist-NDNRSNOG.js +521 -0
- package/dist/dist-NDNRSNOG.js.map +1 -0
- package/dist/gen/index.js +186 -1280
- package/dist/gen/index.js.map +1 -1
- package/dist/hooks/index.js +21 -8694
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.js +403 -47848
- package/dist/index.js.map +1 -1
- package/dist/index.native.js +400 -25048
- package/dist/index.native.js.map +1 -1
- package/dist/index.web.js +576 -43769
- package/dist/index.web.js.map +1 -1
- package/dist/mutation/index.js +44 -4675
- package/dist/mutation/index.js.map +1 -1
- package/dist/parser/index.js +45 -3697
- package/dist/parser/index.js.map +1 -1
- package/dist/pdf-3TIGQRLA.js +20336 -0
- package/dist/pdf-3TIGQRLA.js.map +1 -0
- package/dist/query/index.js +31 -13175
- package/dist/query/index.js.map +1 -1
- package/dist/realtime/index.js +45 -12431
- package/dist/realtime/index.js.map +1 -1
- package/dist/types/index.js +9 -0
- package/package.json +3 -3
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSupabaseUrl
|
|
3
|
+
} from "./chunk-Y3INY2CS.js";
|
|
4
|
+
import {
|
|
5
|
+
generateUUID,
|
|
6
|
+
useSupabase
|
|
7
|
+
} from "./chunk-AWFMICFV.js";
|
|
8
|
+
|
|
9
|
+
// src/useSupabaseFunction.ts
|
|
10
|
+
function useSupabaseFunction() {
|
|
11
|
+
const supabase = useSupabase();
|
|
12
|
+
async function downloadFunctionResponse(functionName, body, name) {
|
|
13
|
+
const { data, error } = await supabase.auth.getSession();
|
|
14
|
+
if (error || !data.session) {
|
|
15
|
+
console.error("Error retrieving session:", error);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const accessToken = data.session.access_token;
|
|
19
|
+
const response = await fetch(
|
|
20
|
+
`${getSupabaseUrl()}/functions/v1/${functionName}`,
|
|
21
|
+
{
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": body && "application/json",
|
|
25
|
+
Authorization: `Bearer ${accessToken}`
|
|
26
|
+
},
|
|
27
|
+
body: body && JSON.stringify(body)
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
console.error("Error downloading invoice:", response.statusText);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const contentDisposition = response.headers.get("Content-Disposition");
|
|
35
|
+
const mimeType = response.headers.get("Content-Type") || "application/octet-stream";
|
|
36
|
+
const filename = name ? name : (contentDisposition && contentDisposition.split("filename=")[1]) ?? "Unknown";
|
|
37
|
+
await downloadFile(response.body, filename, mimeType);
|
|
38
|
+
}
|
|
39
|
+
return { downloadFunctionResponse };
|
|
40
|
+
}
|
|
41
|
+
async function downloadFile(stream, filename, mimeType) {
|
|
42
|
+
if (!stream) return;
|
|
43
|
+
const reader = stream.getReader();
|
|
44
|
+
const a = document.createElement("a");
|
|
45
|
+
a.style.display = "none";
|
|
46
|
+
document.body.appendChild(a);
|
|
47
|
+
try {
|
|
48
|
+
const chunks = [];
|
|
49
|
+
while (true) {
|
|
50
|
+
const { done, value } = await reader.read();
|
|
51
|
+
if (done) break;
|
|
52
|
+
chunks.push(value);
|
|
53
|
+
}
|
|
54
|
+
const blob = new Blob(chunks, { type: mimeType });
|
|
55
|
+
const url = window.URL.createObjectURL(blob);
|
|
56
|
+
a.href = url;
|
|
57
|
+
a.download = filename;
|
|
58
|
+
a.click();
|
|
59
|
+
window.URL.revokeObjectURL(url);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("Error downloading file:", error);
|
|
62
|
+
} finally {
|
|
63
|
+
document.body.removeChild(a);
|
|
64
|
+
reader.releaseLock();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/useReceiptAI.ts
|
|
69
|
+
import { useState, useCallback } from "react";
|
|
70
|
+
var pdfjsLib = null;
|
|
71
|
+
async function getPdfJs() {
|
|
72
|
+
if (!pdfjsLib) {
|
|
73
|
+
pdfjsLib = await import("./pdf-3TIGQRLA.js");
|
|
74
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;
|
|
75
|
+
}
|
|
76
|
+
return pdfjsLib;
|
|
77
|
+
}
|
|
78
|
+
var receiptSchema = {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
merchantName: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "The merchant or vendor name from the receipt"
|
|
84
|
+
},
|
|
85
|
+
transactionDate: {
|
|
86
|
+
type: ["string", "null"],
|
|
87
|
+
description: "Transaction date in YYYY-MM-DD format, or null if not found"
|
|
88
|
+
},
|
|
89
|
+
currency: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "Currency code (USD, EUR, GBP, CAD, etc.) or symbol ($, \u20AC, \xA3)"
|
|
92
|
+
},
|
|
93
|
+
subtotal: {
|
|
94
|
+
type: ["number", "null"],
|
|
95
|
+
description: "Subtotal before tax, or null if not found"
|
|
96
|
+
},
|
|
97
|
+
tax: {
|
|
98
|
+
type: ["number", "null"],
|
|
99
|
+
description: "Tax amount, or null if not found"
|
|
100
|
+
},
|
|
101
|
+
total: {
|
|
102
|
+
type: "number",
|
|
103
|
+
description: "Total amount including tax"
|
|
104
|
+
},
|
|
105
|
+
lineItems: {
|
|
106
|
+
type: "array",
|
|
107
|
+
description: "Individual items on the receipt",
|
|
108
|
+
items: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
description: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "Item description"
|
|
114
|
+
},
|
|
115
|
+
amount: {
|
|
116
|
+
type: "number",
|
|
117
|
+
description: "Item amount/price"
|
|
118
|
+
},
|
|
119
|
+
quantity: {
|
|
120
|
+
type: ["number", "null"],
|
|
121
|
+
description: "Quantity if shown"
|
|
122
|
+
},
|
|
123
|
+
suggestedCategory: {
|
|
124
|
+
type: ["string", "null"],
|
|
125
|
+
description: "Suggested expense category (e.g., 'Meals', 'Transportation', 'Supplies', 'Equipment', 'Lodging', 'Utilities', 'Entertainment', 'Office Supplies')"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
required: ["description", "amount", "quantity", "suggestedCategory"],
|
|
129
|
+
additionalProperties: false
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
confidence: {
|
|
133
|
+
type: "number",
|
|
134
|
+
description: "Confidence score from 0 to 1 for the extraction quality"
|
|
135
|
+
},
|
|
136
|
+
notes: {
|
|
137
|
+
type: ["string", "null"],
|
|
138
|
+
description: "Any additional notes or context extracted from the receipt"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
required: ["merchantName", "transactionDate", "currency", "subtotal", "tax", "total", "lineItems", "confidence", "notes"],
|
|
142
|
+
additionalProperties: false
|
|
143
|
+
};
|
|
144
|
+
function generateId() {
|
|
145
|
+
return generateUUID();
|
|
146
|
+
}
|
|
147
|
+
function useReceiptAI(options = {}) {
|
|
148
|
+
const supabase = useSupabase();
|
|
149
|
+
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
|
150
|
+
const [error, setError] = useState(null);
|
|
151
|
+
const [extractedData, setExtractedData] = useState(
|
|
152
|
+
null
|
|
153
|
+
);
|
|
154
|
+
const [batchProgress, setBatchProgress] = useState(null);
|
|
155
|
+
const analyzeReceipt = useCallback(
|
|
156
|
+
async (file) => {
|
|
157
|
+
setIsAnalyzing(true);
|
|
158
|
+
setError(null);
|
|
159
|
+
try {
|
|
160
|
+
const base64 = await fileToBase64(file);
|
|
161
|
+
const context = {};
|
|
162
|
+
if (options.recentExpenses && options.recentExpenses.length > 0) {
|
|
163
|
+
context.recentExpensePatterns = options.recentExpenses.slice(0, 10);
|
|
164
|
+
context.hint = "Use recent expense patterns to help match merchant names and suggest categories based on user's history";
|
|
165
|
+
}
|
|
166
|
+
if (options.taxCategories && options.taxCategories.length > 0) {
|
|
167
|
+
context.availableCategories = options.taxCategories.map(
|
|
168
|
+
(c) => c.title
|
|
169
|
+
);
|
|
170
|
+
context.categoryHint = "When suggesting categories for line items, try to match one of the available categories";
|
|
171
|
+
}
|
|
172
|
+
const response = await supabase.functions.invoke("ai", {
|
|
173
|
+
body: {
|
|
174
|
+
prompt: `Extract receipt data. Use official merchant names for known chains. Match categories from availableCategories when possible.`,
|
|
175
|
+
context,
|
|
176
|
+
files: [
|
|
177
|
+
{
|
|
178
|
+
source: "base64",
|
|
179
|
+
data: base64,
|
|
180
|
+
// PDFs and images are all converted to JPEG
|
|
181
|
+
mimeType: file.type === "application/pdf" || file.type.startsWith("image/") ? "image/jpeg" : file.type,
|
|
182
|
+
fileName: file.name
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
outputSchema: {
|
|
186
|
+
name: "receipt_extraction",
|
|
187
|
+
description: "Structured data extracted from a receipt image or document",
|
|
188
|
+
schema: receiptSchema
|
|
189
|
+
},
|
|
190
|
+
options: {
|
|
191
|
+
maxTokens: 5e3
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
if (response.error) {
|
|
196
|
+
throw new Error(`AI function error: ${response.error.message}`);
|
|
197
|
+
}
|
|
198
|
+
if (!response.data?.success) {
|
|
199
|
+
throw new Error(response.data?.error || "Failed to analyze receipt");
|
|
200
|
+
}
|
|
201
|
+
const extracted = response.data.data;
|
|
202
|
+
setExtractedData(extracted);
|
|
203
|
+
return extracted;
|
|
204
|
+
} catch (err) {
|
|
205
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
206
|
+
setError(message);
|
|
207
|
+
console.error("Receipt analysis error:", err);
|
|
208
|
+
return null;
|
|
209
|
+
} finally {
|
|
210
|
+
setIsAnalyzing(false);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
[supabase, options.recentExpenses, options.taxCategories]
|
|
214
|
+
);
|
|
215
|
+
const reset = useCallback(() => {
|
|
216
|
+
setExtractedData(null);
|
|
217
|
+
setError(null);
|
|
218
|
+
setBatchProgress(null);
|
|
219
|
+
}, []);
|
|
220
|
+
const analyzeReceiptInternal = useCallback(
|
|
221
|
+
async (file) => {
|
|
222
|
+
try {
|
|
223
|
+
const base64 = await fileToBase64(file);
|
|
224
|
+
const context = {};
|
|
225
|
+
if (options.recentExpenses && options.recentExpenses.length > 0) {
|
|
226
|
+
context.recentExpensePatterns = options.recentExpenses.slice(0, 10);
|
|
227
|
+
context.hint = "Use recent expense patterns to help match merchant names and suggest categories based on user's history";
|
|
228
|
+
}
|
|
229
|
+
if (options.taxCategories && options.taxCategories.length > 0) {
|
|
230
|
+
context.availableCategories = options.taxCategories.map(
|
|
231
|
+
(c) => c.title
|
|
232
|
+
);
|
|
233
|
+
context.categoryHint = "When suggesting categories for line items, try to match one of the available categories";
|
|
234
|
+
}
|
|
235
|
+
const response = await supabase.functions.invoke("ai", {
|
|
236
|
+
body: {
|
|
237
|
+
prompt: `Extract receipt data. Use official merchant names for known chains. Match categories from availableCategories when possible.`,
|
|
238
|
+
context,
|
|
239
|
+
files: [
|
|
240
|
+
{
|
|
241
|
+
source: "base64",
|
|
242
|
+
data: base64,
|
|
243
|
+
// PDFs and images are all converted to JPEG
|
|
244
|
+
mimeType: file.type === "application/pdf" || file.type.startsWith("image/") ? "image/jpeg" : file.type,
|
|
245
|
+
fileName: file.name
|
|
246
|
+
}
|
|
247
|
+
],
|
|
248
|
+
outputSchema: {
|
|
249
|
+
name: "receipt_extraction",
|
|
250
|
+
description: "Structured data extracted from a receipt image or document",
|
|
251
|
+
schema: receiptSchema
|
|
252
|
+
},
|
|
253
|
+
options: {
|
|
254
|
+
maxTokens: 5e3
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
if (response.error) {
|
|
259
|
+
throw new Error(`AI function error: ${response.error.message}`);
|
|
260
|
+
}
|
|
261
|
+
if (!response.data?.success) {
|
|
262
|
+
throw new Error(response.data?.error || "Failed to analyze receipt");
|
|
263
|
+
}
|
|
264
|
+
return response.data.data;
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.error("Receipt analysis error:", err);
|
|
267
|
+
throw err;
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
[supabase, options.recentExpenses, options.taxCategories]
|
|
271
|
+
);
|
|
272
|
+
const analyzeMultipleReceipts = useCallback(
|
|
273
|
+
async (files, onProgress, onItemComplete) => {
|
|
274
|
+
const CONCURRENCY_LIMIT = 3;
|
|
275
|
+
const results = [];
|
|
276
|
+
const progress = {
|
|
277
|
+
total: files.length,
|
|
278
|
+
completed: 0,
|
|
279
|
+
processing: []
|
|
280
|
+
};
|
|
281
|
+
setBatchProgress(progress);
|
|
282
|
+
onProgress?.(progress);
|
|
283
|
+
const pendingItems = files.map((file) => ({
|
|
284
|
+
id: generateId(),
|
|
285
|
+
file,
|
|
286
|
+
status: "processing",
|
|
287
|
+
extractedData: null,
|
|
288
|
+
error: null
|
|
289
|
+
}));
|
|
290
|
+
const queue = [...pendingItems];
|
|
291
|
+
const inProgress = [];
|
|
292
|
+
const processNext = async () => {
|
|
293
|
+
if (queue.length === 0) return;
|
|
294
|
+
const pending = queue.shift();
|
|
295
|
+
progress.processing.push(pending.file.name);
|
|
296
|
+
setBatchProgress({ ...progress });
|
|
297
|
+
onProgress?.({ ...progress });
|
|
298
|
+
try {
|
|
299
|
+
const extracted = await analyzeReceiptInternal(pending.file);
|
|
300
|
+
pending.extractedData = extracted;
|
|
301
|
+
pending.status = "ready";
|
|
302
|
+
} catch (err) {
|
|
303
|
+
pending.error = err instanceof Error ? err.message : "Unknown error";
|
|
304
|
+
pending.status = "error";
|
|
305
|
+
}
|
|
306
|
+
progress.completed++;
|
|
307
|
+
progress.processing = progress.processing.filter(
|
|
308
|
+
(name) => name !== pending.file.name
|
|
309
|
+
);
|
|
310
|
+
setBatchProgress({ ...progress });
|
|
311
|
+
onProgress?.({ ...progress });
|
|
312
|
+
onItemComplete?.(pending);
|
|
313
|
+
results.push(pending);
|
|
314
|
+
if (queue.length > 0) {
|
|
315
|
+
await processNext();
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
for (let i = 0; i < Math.min(CONCURRENCY_LIMIT, files.length); i++) {
|
|
319
|
+
inProgress.push(processNext());
|
|
320
|
+
}
|
|
321
|
+
await Promise.all(inProgress);
|
|
322
|
+
setBatchProgress(null);
|
|
323
|
+
return pendingItems;
|
|
324
|
+
},
|
|
325
|
+
[analyzeReceiptInternal]
|
|
326
|
+
);
|
|
327
|
+
return {
|
|
328
|
+
analyzeReceipt,
|
|
329
|
+
analyzeMultipleReceipts,
|
|
330
|
+
isAnalyzing,
|
|
331
|
+
batchProgress,
|
|
332
|
+
error,
|
|
333
|
+
extractedData,
|
|
334
|
+
reset
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
async function pdfToImage(file, maxDimension = 1200) {
|
|
338
|
+
const pdfjs = await getPdfJs();
|
|
339
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
340
|
+
const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;
|
|
341
|
+
const page = await pdf.getPage(1);
|
|
342
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
343
|
+
const scale = Math.min(maxDimension / viewport.width, maxDimension / viewport.height, 1.5);
|
|
344
|
+
const scaledViewport = page.getViewport({ scale });
|
|
345
|
+
const canvas = document.createElement("canvas");
|
|
346
|
+
canvas.width = scaledViewport.width;
|
|
347
|
+
canvas.height = scaledViewport.height;
|
|
348
|
+
const ctx = canvas.getContext("2d");
|
|
349
|
+
if (!ctx) {
|
|
350
|
+
throw new Error("Could not get canvas context");
|
|
351
|
+
}
|
|
352
|
+
ctx.fillStyle = "#ffffff";
|
|
353
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
354
|
+
await page.render({
|
|
355
|
+
canvasContext: ctx,
|
|
356
|
+
viewport: scaledViewport
|
|
357
|
+
}).promise;
|
|
358
|
+
const dataUrl = canvas.toDataURL("image/jpeg", 0.9);
|
|
359
|
+
const base64 = dataUrl.split(",")[1];
|
|
360
|
+
console.log(`\u{1F4C4} PDF rendered: ${Math.round(scaledViewport.width)}x${Math.round(scaledViewport.height)} (scale: ${scale.toFixed(2)}, ~${Math.round(base64.length / 1024)}KB)`);
|
|
361
|
+
return base64;
|
|
362
|
+
}
|
|
363
|
+
async function fileToBase64(file, maxDimension = 1024) {
|
|
364
|
+
if (file.type === "application/pdf") {
|
|
365
|
+
return pdfToImage(file, 1200);
|
|
366
|
+
}
|
|
367
|
+
if (!file.type.startsWith("image/")) {
|
|
368
|
+
return new Promise((resolve, reject) => {
|
|
369
|
+
const reader = new FileReader();
|
|
370
|
+
reader.onload = () => {
|
|
371
|
+
const result = reader.result;
|
|
372
|
+
const base64 = result.split(",")[1];
|
|
373
|
+
resolve(base64);
|
|
374
|
+
};
|
|
375
|
+
reader.onerror = reject;
|
|
376
|
+
reader.readAsDataURL(file);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
return new Promise((resolve, reject) => {
|
|
380
|
+
const img = new Image();
|
|
381
|
+
img.onload = () => {
|
|
382
|
+
let { width, height } = img;
|
|
383
|
+
if (width > maxDimension || height > maxDimension) {
|
|
384
|
+
if (width > height) {
|
|
385
|
+
height = Math.round(height * maxDimension / width);
|
|
386
|
+
width = maxDimension;
|
|
387
|
+
} else {
|
|
388
|
+
width = Math.round(width * maxDimension / height);
|
|
389
|
+
height = maxDimension;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const canvas = document.createElement("canvas");
|
|
393
|
+
canvas.width = width;
|
|
394
|
+
canvas.height = height;
|
|
395
|
+
const ctx = canvas.getContext("2d");
|
|
396
|
+
if (!ctx) {
|
|
397
|
+
reject(new Error("Could not get canvas context"));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
401
|
+
const dataUrl = canvas.toDataURL("image/jpeg", 0.85);
|
|
402
|
+
const base64 = dataUrl.split(",")[1];
|
|
403
|
+
console.log(`\u{1F4F7} Image resized: ${img.width}x${img.height} \u2192 ${width}x${height}`);
|
|
404
|
+
resolve(base64);
|
|
405
|
+
};
|
|
406
|
+
img.onerror = reject;
|
|
407
|
+
img.src = URL.createObjectURL(file);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
var useReceiptAI_default = useReceiptAI;
|
|
411
|
+
|
|
412
|
+
export {
|
|
413
|
+
useSupabaseFunction,
|
|
414
|
+
useReceiptAI_default
|
|
415
|
+
};
|
|
416
|
+
//# sourceMappingURL=chunk-5ZYAEGCJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useSupabaseFunction.ts","../src/useReceiptAI.ts"],"sourcesContent":["import { getSupabaseUrl } from \"./config\";\nimport useSupabase, { typedSupabase } from \"./useSupabase\";\n\nexport default function useSupabaseFunction() {\n const supabase = useSupabase();\n async function downloadFunctionResponse(\n functionName: string,\n body?: any,\n name?: string,\n ) {\n const { data, error } = await supabase.auth.getSession();\n if (error || !data.session) {\n console.error(\"Error retrieving session:\", error);\n return;\n }\n\n const accessToken = data.session.access_token;\n\n const response = await fetch(\n `${getSupabaseUrl()}/functions/v1/${functionName}`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": body && \"application/json\",\n Authorization: `Bearer ${accessToken}`,\n },\n body: body && JSON.stringify(body),\n },\n );\n\n if (!response.ok) {\n console.error(\"Error downloading invoice:\", response.statusText);\n return;\n }\n\n const contentDisposition = response.headers.get(\"Content-Disposition\");\n const mimeType = response.headers.get(\"Content-Type\") ||\n \"application/octet-stream\";\n const filename = name\n ? name\n : (contentDisposition && contentDisposition.split(\"filename=\")[1]) ??\n \"Unknown\";\n\n await downloadFile(response.body as any, filename, mimeType);\n }\n return { downloadFunctionResponse };\n}\n\nasync function downloadFile(\n stream: ReadableStream<Uint8Array> | null,\n filename: string,\n mimeType: string,\n) {\n if (!stream) return;\n const reader = stream.getReader();\n const a = document.createElement(\"a\");\n a.style.display = \"none\";\n document.body.appendChild(a);\n\n try {\n const chunks = [];\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n const blob = new Blob(chunks, { type: mimeType });\n const url = window.URL.createObjectURL(blob);\n a.href = url;\n a.download = filename;\n a.click();\n window.URL.revokeObjectURL(url);\n } catch (error) {\n console.error(\"Error downloading file:\", error);\n } finally {\n document.body.removeChild(a);\n reader.releaseLock();\n }\n}\n\n// Invoke Supabase Edge Function with response streaming\n","import { useState, useCallback } from \"react\";\nimport useSupabase from \"./useSupabase\";\nimport { generateUUID } from \"./utils/uuid\";\nimport { Tables } from \"./database.types\";\n\n// Lazy-load PDF.js to avoid top-level side effects that break React Native\nlet pdfjsLib: typeof import(\"pdfjs-dist\") | null = null;\n\nasync function getPdfJs() {\n if (!pdfjsLib) {\n pdfjsLib = await import(\"pdfjs-dist\");\n pdfjsLib.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;\n }\n return pdfjsLib;\n}\n\n// Schema for receipt extraction\nconst receiptSchema = {\n type: \"object\",\n properties: {\n merchantName: {\n type: \"string\",\n description: \"The merchant or vendor name from the receipt\",\n },\n transactionDate: {\n type: [\"string\", \"null\"],\n description: \"Transaction date in YYYY-MM-DD format, or null if not found\",\n },\n currency: {\n type: \"string\",\n description:\n \"Currency code (USD, EUR, GBP, CAD, etc.) or symbol ($, €, £)\",\n },\n subtotal: {\n type: [\"number\", \"null\"],\n description: \"Subtotal before tax, or null if not found\",\n },\n tax: {\n type: [\"number\", \"null\"],\n description: \"Tax amount, or null if not found\",\n },\n total: {\n type: \"number\",\n description: \"Total amount including tax\",\n },\n lineItems: {\n type: \"array\",\n description: \"Individual items on the receipt\",\n items: {\n type: \"object\",\n properties: {\n description: {\n type: \"string\",\n description: \"Item description\",\n },\n amount: {\n type: \"number\",\n description: \"Item amount/price\",\n },\n quantity: {\n type: [\"number\", \"null\"],\n description: \"Quantity if shown\",\n },\n suggestedCategory: {\n type: [\"string\", \"null\"],\n description:\n \"Suggested expense category (e.g., 'Meals', 'Transportation', 'Supplies', 'Equipment', 'Lodging', 'Utilities', 'Entertainment', 'Office Supplies')\",\n },\n },\n required: [\"description\", \"amount\", \"quantity\", \"suggestedCategory\"],\n additionalProperties: false,\n },\n },\n confidence: {\n type: \"number\",\n description: \"Confidence score from 0 to 1 for the extraction quality\",\n },\n notes: {\n type: [\"string\", \"null\"],\n description:\n \"Any additional notes or context extracted from the receipt\",\n },\n },\n required: [\"merchantName\", \"transactionDate\", \"currency\", \"subtotal\", \"tax\", \"total\", \"lineItems\", \"confidence\", \"notes\"],\n additionalProperties: false,\n};\n\nexport interface ExtractedReceipt {\n merchantName: string;\n transactionDate: string | null;\n currency: string;\n subtotal: number | null;\n tax: number | null;\n total: number;\n lineItems: Array<{\n description: string;\n amount: number;\n quantity: number | null;\n suggestedCategory: string | null;\n }>;\n confidence: number;\n notes: string | null;\n}\n\n// Types for batch processing\nexport interface PendingExpense {\n id: string; // temporary UUID for tracking\n file: File;\n status: \"processing\" | \"ready\" | \"error\" | \"confirming\";\n extractedData: ExtractedReceipt | null;\n error: string | null;\n duplicateWarning?: {\n existingExpenseId: number;\n reason: string;\n };\n}\n\nexport interface BatchAnalysisProgress {\n total: number;\n completed: number;\n processing: string[]; // file names currently processing\n}\n\ninterface AIResponse<T> {\n success: boolean;\n data?: T;\n model?: string;\n usage?: {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n };\n error?: string;\n errorCode?: string;\n}\n\ninterface RecentExpenseContext {\n merchantName: string;\n category: string | null;\n amount: number;\n}\n\ninterface UseReceiptAIOptions {\n recentExpenses?: RecentExpenseContext[];\n taxCategories?: Tables<\"TaxCategory\">[];\n}\n\n// Helper to generate UUID for pending expenses\nfunction generateId(): string {\n return generateUUID();\n}\n\nexport function useReceiptAI(options: UseReceiptAIOptions = {}) {\n const supabase = useSupabase();\n const [isAnalyzing, setIsAnalyzing] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [extractedData, setExtractedData] = useState<ExtractedReceipt | null>(\n null\n );\n // Batch processing state\n const [batchProgress, setBatchProgress] = useState<BatchAnalysisProgress | null>(null);\n\n const analyzeReceipt = useCallback(\n async (file: File): Promise<ExtractedReceipt | null> => {\n setIsAnalyzing(true);\n setError(null);\n\n try {\n // Convert file to base64\n const base64 = await fileToBase64(file);\n\n // Build context from recent expenses\n const context: Record<string, unknown> = {};\n\n if (options.recentExpenses && options.recentExpenses.length > 0) {\n context.recentExpensePatterns = options.recentExpenses.slice(0, 10);\n context.hint =\n \"Use recent expense patterns to help match merchant names and suggest categories based on user's history\";\n }\n\n if (options.taxCategories && options.taxCategories.length > 0) {\n context.availableCategories = options.taxCategories.map(\n (c) => c.title\n );\n context.categoryHint =\n \"When suggesting categories for line items, try to match one of the available categories\";\n }\n\n // Call the AI function\n const response = await supabase.functions.invoke<\n AIResponse<ExtractedReceipt>\n >(\"ai\", {\n body: {\n prompt: `Extract receipt data. Use official merchant names for known chains. Match categories from availableCategories when possible.`,\n context,\n files: [\n {\n source: \"base64\",\n data: base64,\n // PDFs and images are all converted to JPEG\n mimeType: file.type === \"application/pdf\" || file.type.startsWith(\"image/\") ? \"image/jpeg\" : file.type,\n fileName: file.name,\n },\n ],\n outputSchema: {\n name: \"receipt_extraction\",\n description:\n \"Structured data extracted from a receipt image or document\",\n schema: receiptSchema,\n },\n options:{\n maxTokens: 5000,\n }\n },\n });\n\n if (response.error) {\n throw new Error(`AI function error: ${response.error.message}`);\n }\n\n if (!response.data?.success) {\n throw new Error(response.data?.error || \"Failed to analyze receipt\");\n }\n\n const extracted = response.data.data!;\n setExtractedData(extracted);\n return extracted;\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Unknown error\";\n setError(message);\n console.error(\"Receipt analysis error:\", err);\n return null;\n } finally {\n setIsAnalyzing(false);\n }\n },\n [supabase, options.recentExpenses, options.taxCategories]\n );\n\n const reset = useCallback(() => {\n setExtractedData(null);\n setError(null);\n setBatchProgress(null);\n }, []);\n\n // Analyze a single file without updating shared state (for batch processing)\n const analyzeReceiptInternal = useCallback(\n async (file: File): Promise<ExtractedReceipt | null> => {\n try {\n const base64 = await fileToBase64(file);\n\n const context: Record<string, unknown> = {};\n\n if (options.recentExpenses && options.recentExpenses.length > 0) {\n context.recentExpensePatterns = options.recentExpenses.slice(0, 10);\n context.hint =\n \"Use recent expense patterns to help match merchant names and suggest categories based on user's history\";\n }\n\n if (options.taxCategories && options.taxCategories.length > 0) {\n context.availableCategories = options.taxCategories.map(\n (c) => c.title\n );\n context.categoryHint =\n \"When suggesting categories for line items, try to match one of the available categories\";\n }\n\n const response = await supabase.functions.invoke<\n AIResponse<ExtractedReceipt>\n >(\"ai\", {\n body: {\n prompt: `Extract receipt data. Use official merchant names for known chains. Match categories from availableCategories when possible.`,\n context,\n files: [\n {\n source: \"base64\",\n data: base64,\n // PDFs and images are all converted to JPEG\n mimeType: file.type === \"application/pdf\" || file.type.startsWith(\"image/\") ? \"image/jpeg\" : file.type,\n fileName: file.name,\n },\n ],\n outputSchema: {\n name: \"receipt_extraction\",\n description:\n \"Structured data extracted from a receipt image or document\",\n schema: receiptSchema,\n },\n options: {\n maxTokens: 5000,\n },\n },\n });\n\n if (response.error) {\n throw new Error(`AI function error: ${response.error.message}`);\n }\n\n if (!response.data?.success) {\n throw new Error(response.data?.error || \"Failed to analyze receipt\");\n }\n\n return response.data.data!;\n } catch (err) {\n console.error(\"Receipt analysis error:\", err);\n throw err;\n }\n },\n [supabase, options.recentExpenses, options.taxCategories]\n );\n\n // Batch analyze multiple receipts with concurrency control\n const analyzeMultipleReceipts = useCallback(\n async (\n files: File[],\n onProgress?: (progress: BatchAnalysisProgress) => void,\n onItemComplete?: (item: PendingExpense) => void\n ): Promise<PendingExpense[]> => {\n const CONCURRENCY_LIMIT = 3;\n const results: PendingExpense[] = [];\n const progress: BatchAnalysisProgress = {\n total: files.length,\n completed: 0,\n processing: [],\n };\n\n setBatchProgress(progress);\n onProgress?.(progress);\n\n // Initialize all pending expenses\n const pendingItems: PendingExpense[] = files.map((file) => ({\n id: generateId(),\n file,\n status: \"processing\" as const,\n extractedData: null,\n error: null,\n }));\n\n // Process files with concurrency limit\n const queue = [...pendingItems];\n const inProgress: Promise<void>[] = [];\n\n const processNext = async (): Promise<void> => {\n if (queue.length === 0) return;\n\n const pending = queue.shift()!;\n progress.processing.push(pending.file.name);\n setBatchProgress({ ...progress });\n onProgress?.({ ...progress });\n\n try {\n const extracted = await analyzeReceiptInternal(pending.file);\n pending.extractedData = extracted;\n pending.status = \"ready\";\n } catch (err) {\n pending.error = err instanceof Error ? err.message : \"Unknown error\";\n pending.status = \"error\";\n }\n\n // Update progress\n progress.completed++;\n progress.processing = progress.processing.filter(\n (name) => name !== pending.file.name\n );\n setBatchProgress({ ...progress });\n onProgress?.({ ...progress });\n\n // Notify caller immediately when this item completes\n onItemComplete?.(pending);\n\n results.push(pending);\n\n // Process next in queue\n if (queue.length > 0) {\n await processNext();\n }\n };\n\n // Start initial batch of concurrent processes\n for (let i = 0; i < Math.min(CONCURRENCY_LIMIT, files.length); i++) {\n inProgress.push(processNext());\n }\n\n // Wait for all to complete\n await Promise.all(inProgress);\n\n setBatchProgress(null);\n return pendingItems;\n },\n [analyzeReceiptInternal]\n );\n\n return {\n analyzeReceipt,\n analyzeMultipleReceipts,\n isAnalyzing,\n batchProgress,\n error,\n extractedData,\n reset,\n };\n}\n\n// Helper to render PDF to image\nasync function pdfToImage(file: File, maxDimension = 1200): Promise<string> {\n const pdfjs = await getPdfJs();\n const arrayBuffer = await file.arrayBuffer();\n const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise;\n\n // Get first page (receipts are typically single page)\n const page = await pdf.getPage(1);\n\n // Calculate scale to fit within maxDimension while maintaining aspect ratio\n const viewport = page.getViewport({ scale: 1 });\n const scale = Math.min(maxDimension / viewport.width, maxDimension / viewport.height, 1.5);\n const scaledViewport = page.getViewport({ scale });\n\n // Create canvas and render\n const canvas = document.createElement(\"canvas\");\n canvas.width = scaledViewport.width;\n canvas.height = scaledViewport.height;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n throw new Error(\"Could not get canvas context\");\n }\n\n // White background for PDFs\n ctx.fillStyle = \"#ffffff\";\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n await page.render({\n canvasContext: ctx,\n viewport: scaledViewport,\n }).promise;\n\n // Convert to base64 JPEG (smaller file size than PNG)\n const dataUrl = canvas.toDataURL(\"image/jpeg\", 0.9);\n const base64 = dataUrl.split(\",\")[1];\n\n console.log(`📄 PDF rendered: ${Math.round(scaledViewport.width)}x${Math.round(scaledViewport.height)} (scale: ${scale.toFixed(2)}, ~${Math.round(base64.length / 1024)}KB)`);\n return base64;\n}\n\n// Helper to resize image and convert to base64\nasync function fileToBase64(file: File, maxDimension = 1024): Promise<string> {\n // For PDFs, render to image first\n if (file.type === \"application/pdf\") {\n return pdfToImage(file, 1200);\n }\n\n // For non-image files (other than PDF), just convert directly\n if (!file.type.startsWith(\"image/\")) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n const result = reader.result as string;\n const base64 = result.split(\",\")[1];\n resolve(base64);\n };\n reader.onerror = reject;\n reader.readAsDataURL(file);\n });\n }\n\n // For images, resize first\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => {\n // Calculate new dimensions\n let { width, height } = img;\n if (width > maxDimension || height > maxDimension) {\n if (width > height) {\n height = Math.round((height * maxDimension) / width);\n width = maxDimension;\n } else {\n width = Math.round((width * maxDimension) / height);\n height = maxDimension;\n }\n }\n\n // Draw to canvas\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n reject(new Error(\"Could not get canvas context\"));\n return;\n }\n ctx.drawImage(img, 0, 0, width, height);\n\n // Convert to base64 (JPEG at 85% quality for smaller size)\n const dataUrl = canvas.toDataURL(\"image/jpeg\", 0.85);\n const base64 = dataUrl.split(\",\")[1];\n\n console.log(`📷 Image resized: ${img.width}x${img.height} → ${width}x${height}`);\n resolve(base64);\n };\n img.onerror = reject;\n img.src = URL.createObjectURL(file);\n });\n}\n\nexport default useReceiptAI;\n"],"mappings":"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","names":[]}
|