@pol-studios/db 1.0.9 → 1.0.11
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/DataLayerContext-CL6alnkb.d.ts +755 -0
- package/dist/UserMetadataContext-B8gVWGMl.d.ts +35 -0
- package/dist/UserMetadataContext-DntmpK41.d.ts +33 -0
- package/dist/auth/context.d.ts +3 -2
- package/dist/auth/context.js +22 -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.d.ts +3 -3
- package/dist/auth/hooks.js +26 -10591
- package/dist/auth/hooks.js.map +1 -1
- package/dist/auth/index.d.ts +3 -2
- package/dist/auth/index.js +45 -13008
- package/dist/auth/index.js.map +1 -1
- package/dist/{canvas-UVNDA54X.node → canvas-C4TBBDUL.node} +0 -0
- package/dist/canvas-ZQNCL7JL.js +1541 -0
- package/dist/canvas-ZQNCL7JL.js.map +1 -0
- package/dist/chunk-3PJTNH2L.js +2778 -0
- package/dist/chunk-3PJTNH2L.js.map +1 -0
- package/dist/chunk-5EFDS7SR.js +205 -0
- package/dist/chunk-5EFDS7SR.js.map +1 -0
- package/dist/chunk-7SCJNYTE.js +1459 -0
- package/dist/chunk-7SCJNYTE.js.map +1 -0
- package/dist/chunk-DJ6VLEAL.js +247 -0
- package/dist/chunk-DJ6VLEAL.js.map +1 -0
- package/dist/chunk-GC3TBUWE.js +14 -0
- package/dist/chunk-GC3TBUWE.js.map +1 -0
- package/dist/chunk-H3LNH2NT.js +611 -0
- package/dist/chunk-H3LNH2NT.js.map +1 -0
- package/dist/chunk-H6365JPC.js +1858 -0
- package/dist/chunk-H6365JPC.js.map +1 -0
- package/dist/chunk-HAWJTZCK.js +86 -0
- package/dist/chunk-HAWJTZCK.js.map +1 -0
- package/dist/chunk-J4ZVCXZ4.js +1 -0
- package/dist/chunk-J4ZVCXZ4.js.map +1 -0
- package/dist/chunk-JAATANS3.js +429 -0
- package/dist/chunk-JAATANS3.js.map +1 -0
- package/dist/chunk-LNJ3WF7V.js +470 -0
- package/dist/chunk-LNJ3WF7V.js.map +1 -0
- package/dist/chunk-N26IEHZT.js +79 -0
- package/dist/chunk-N26IEHZT.js.map +1 -0
- package/dist/chunk-NSIAAYW3.js +1 -0
- package/dist/chunk-NSIAAYW3.js.map +1 -0
- package/dist/chunk-NZON56CB.js +3864 -0
- package/dist/chunk-NZON56CB.js.map +1 -0
- package/dist/chunk-OQ7U6EQ3.js +7550 -0
- package/dist/chunk-OQ7U6EQ3.js.map +1 -0
- package/dist/chunk-P4UZ7IXC.js +42 -0
- package/dist/chunk-P4UZ7IXC.js.map +1 -0
- package/dist/chunk-SM73S2DY.js +11 -0
- package/dist/chunk-SM73S2DY.js.map +1 -0
- package/dist/chunk-TKWR5AAY.js +415 -0
- package/dist/chunk-TKWR5AAY.js.map +1 -0
- package/dist/chunk-U5UNPBKB.js +501 -0
- package/dist/chunk-U5UNPBKB.js.map +1 -0
- package/dist/chunk-WGDJ4IXR.js +921 -0
- package/dist/chunk-WGDJ4IXR.js.map +1 -0
- package/dist/chunk-WVF7RUW5.js +186 -0
- package/dist/chunk-WVF7RUW5.js.map +1 -0
- package/dist/chunk-X3HZLNBV.js +2129 -0
- package/dist/chunk-X3HZLNBV.js.map +1 -0
- package/dist/chunk-XU3SBFAG.js +5205 -0
- package/dist/chunk-XU3SBFAG.js.map +1 -0
- package/dist/chunk-ZVBHWU7O.js +1412 -0
- package/dist/chunk-ZVBHWU7O.js.map +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +54 -163
- package/dist/client/index.js.map +1 -1
- package/dist/core/index.d.ts +19 -0
- package/dist/{index-BFu5_dS8.d.ts → database.types-ChFCG-4M.d.ts} +1 -177
- package/dist/dist-NDNRSNOG.js +521 -0
- package/dist/dist-NDNRSNOG.js.map +1 -0
- package/dist/gen/index.js +188 -1280
- package/dist/gen/index.js.map +1 -1
- package/dist/hooks/index.d.ts +10 -3
- package/dist/hooks/index.js +20 -8695
- package/dist/hooks/index.js.map +1 -1
- package/dist/index-CQLyNG6A.d.ts +433 -0
- package/dist/index.d.ts +12 -8
- package/dist/index.js +447 -47848
- package/dist/index.js.map +1 -1
- package/dist/index.native.d.ts +373 -33
- package/dist/index.native.js +432 -25048
- package/dist/index.native.js.map +1 -1
- package/dist/index.web.d.ts +10 -7
- package/dist/index.web.js +585 -43773
- package/dist/index.web.js.map +1 -1
- package/dist/mutation/index.d.ts +2 -2
- package/dist/mutation/index.js +331 -4777
- 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-PHXP7RHD.js +20336 -0
- package/dist/pdf-PHXP7RHD.js.map +1 -0
- package/dist/powersync-bridge/index.d.ts +284 -0
- package/dist/powersync-bridge/index.js +22 -0
- package/dist/powersync-bridge/index.js.map +1 -0
- package/dist/query/index.js +31 -13175
- package/dist/query/index.js.map +1 -1
- package/dist/realtime/index.js +279 -12541
- package/dist/realtime/index.js.map +1 -1
- package/dist/{UserMetadataContext-BYYqA6LI.d.ts → setupAuthContext-Kv-THH-h.d.ts} +1 -29
- package/dist/types/index.d.ts +5 -1
- package/dist/types/index.js +14 -0
- package/dist/{useBatchUpsert-CSQVX7w8.d.ts → useBatchUpsert-9OYjibLh.d.ts} +1 -1
- package/dist/{useDbCount-RGCuHmHp.d.ts → useDbCount-BG356T9i.d.ts} +3 -719
- package/dist/{useReceiptAI-Bn0czE7C.d.ts → useReceiptAI-6HkRpRml.d.ts} +1 -1
- package/dist/{useResolveFeedback-CpZPP8Pw.d.ts → useResolveFeedback-BWmatBlE.d.ts} +26 -45
- package/dist/{useSupabase-pPhUZHcl.d.ts → useSupabase-DvWVuHHE.d.ts} +2 -1
- package/dist/with-auth/index.d.ts +704 -0
- package/dist/with-auth/index.js +1221 -0
- package/dist/with-auth/index.js.map +1 -0
- package/package.json +25 -10
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isUsable
|
|
3
|
+
} from "./chunk-OQ7U6EQ3.js";
|
|
4
|
+
|
|
5
|
+
// src/auth/guards/hasAccess.ts
|
|
6
|
+
function hasAccess(accessKeys, key, options) {
|
|
7
|
+
if (options?.isArchived || options?.isSuspended) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
if (isUsable(accessKeys) === false) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
if (accessKeys.includes("owner")) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (accessKeys.includes(key)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (isUsable(key) === false) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
function hasAnyAccess(accessKeys, keys, options) {
|
|
25
|
+
return keys.some((key) => hasAccess(accessKeys, key, options));
|
|
26
|
+
}
|
|
27
|
+
function hasAllAccess(accessKeys, keys, options) {
|
|
28
|
+
return keys.every((key) => hasAccess(accessKeys, key, options));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/auth/guards/hasEntityAccess.ts
|
|
32
|
+
function hasEntityAccess(permission, action, options) {
|
|
33
|
+
if (options?.isArchived || options?.isSuspended) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const accessKeys = options?.accessKeys;
|
|
37
|
+
if (isUsable(accessKeys) && accessKeys.includes("owner")) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (!permission) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (permission.isLoading) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
switch (action) {
|
|
47
|
+
case "view":
|
|
48
|
+
return permission.canView;
|
|
49
|
+
case "adminView":
|
|
50
|
+
return permission.canAdminView;
|
|
51
|
+
case "edit":
|
|
52
|
+
return permission.canEdit;
|
|
53
|
+
case "create":
|
|
54
|
+
return permission.canCreate;
|
|
55
|
+
case "delete":
|
|
56
|
+
return permission.canDelete;
|
|
57
|
+
case "share":
|
|
58
|
+
return permission.canShare;
|
|
59
|
+
default:
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function hasAnyEntityAccess(permission, actions, options) {
|
|
64
|
+
return actions.some((action) => hasEntityAccess(permission, action, options));
|
|
65
|
+
}
|
|
66
|
+
function hasAllEntityAccess(permission, actions, options) {
|
|
67
|
+
return actions.every((action) => hasEntityAccess(permission, action, options));
|
|
68
|
+
}
|
|
69
|
+
function isEntityAccessDenied(permission) {
|
|
70
|
+
return permission?.isDenied === true;
|
|
71
|
+
}
|
|
72
|
+
function isPermissionLoaded(permission) {
|
|
73
|
+
return permission !== null && permission !== void 0 && !permission.isLoading;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
hasAccess,
|
|
78
|
+
hasAnyAccess,
|
|
79
|
+
hasAllAccess,
|
|
80
|
+
hasEntityAccess,
|
|
81
|
+
hasAnyEntityAccess,
|
|
82
|
+
hasAllEntityAccess,
|
|
83
|
+
isEntityAccessDenied,
|
|
84
|
+
isPermissionLoaded
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=chunk-HAWJTZCK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/guards/hasAccess.ts","../src/auth/guards/hasEntityAccess.ts"],"sourcesContent":["import { isUsable } from \"@pol-studios/utils\";\n\n/**\n * Check if a user has access to a specific permission key.\n * Owners bypass all permission checks.\n *\n * @param accessKeys - Array of access keys the user has\n * @param key - The access key to check for\n * @param options - Additional options for the check\n * @param options.isArchived - Whether the user is archived\n * @param options.isSuspended - Whether the user is suspended\n * @returns boolean - true if user has the specified access\n *\n * @example\n * ```ts\n * // Basic usage\n * const canAccess = hasAccess(['admin', 'viewer'], 'admin'); // true\n * const cannotAccess = hasAccess(['viewer'], 'admin'); // false\n *\n * // Owner bypasses all checks\n * const ownerAccess = hasAccess(['owner'], 'any-key'); // true\n *\n * // Empty key always returns true (no permission required)\n * const noKeyRequired = hasAccess(['viewer'], ''); // true\n *\n * // Archived/suspended users have no access\n * const archivedUser = hasAccess(['admin'], 'admin', { isArchived: true }); // false\n * ```\n */\nexport function hasAccess(accessKeys: string[] | undefined | null, key: string, options?: {\n isArchived?: boolean;\n isSuspended?: boolean;\n}): boolean {\n // Archived/suspended users have no access\n if (options?.isArchived || options?.isSuspended) {\n return false;\n }\n\n // No access keys means no access\n if (isUsable(accessKeys) === false) {\n return false;\n }\n\n // Owners bypass all permission checks\n if (accessKeys.includes(\"owner\")) {\n return true;\n }\n\n // Check if the specific key is present\n if (accessKeys.includes(key)) {\n return true;\n }\n\n // Empty/undefined key means no specific permission required\n if (isUsable(key) === false) {\n return true;\n }\n return false;\n}\n\n/**\n * Check if a user has any of the specified access keys.\n *\n * @param accessKeys - Array of access keys the user has\n * @param keys - Array of access keys to check for (OR logic)\n * @param options - Additional options for the check\n * @returns boolean - true if user has any of the specified keys\n *\n * @example\n * ```ts\n * const hasAny = hasAnyAccess(['viewer'], ['admin', 'editor', 'viewer']); // true\n * const hasNone = hasAnyAccess(['viewer'], ['admin', 'editor']); // false\n * ```\n */\nexport function hasAnyAccess(accessKeys: string[] | undefined | null, keys: string[], options?: {\n isArchived?: boolean;\n isSuspended?: boolean;\n}): boolean {\n // Check each key, return true if any match\n return keys.some(key => hasAccess(accessKeys, key, options));\n}\n\n/**\n * Check if a user has all of the specified access keys.\n *\n * @param accessKeys - Array of access keys the user has\n * @param keys - Array of access keys to check for (AND logic)\n * @param options - Additional options for the check\n * @returns boolean - true if user has all of the specified keys\n *\n * @example\n * ```ts\n * const hasAll = hasAllAccess(['admin', 'editor'], ['admin', 'editor']); // true\n * const hasPartial = hasAllAccess(['admin'], ['admin', 'editor']); // false\n * ```\n */\nexport function hasAllAccess(accessKeys: string[] | undefined | null, keys: string[], options?: {\n isArchived?: boolean;\n isSuspended?: boolean;\n}): boolean {\n // Check each key, return true only if all match\n return keys.every(key => hasAccess(accessKeys, key, options));\n}","import { isUsable } from \"@pol-studios/utils\";\nimport { EntityType, EntityAction, EntityPermissionCheck } from \"../types/EntityPermissions\";\n\n/**\n * Check if a user has entity-level access based on permission check result.\n * Owners (users with 'owner' access key) bypass entity permissions entirely.\n *\n * @param permission - The permission check result from getPermission/usePermission\n * @param action - The action to check for\n * @param options - Additional options for the check\n * @param options.accessKeys - User's access keys (for owner check)\n * @param options.isArchived - Whether the user is archived\n * @param options.isSuspended - Whether the user is suspended\n * @returns boolean - true if user has permission for the action\n *\n * @example\n * ```ts\n * // Basic usage\n * const permission = await getPermission('Project', 123);\n * const canEdit = hasEntityAccess(permission, 'edit');\n *\n * // With owner bypass\n * const canEditAsOwner = hasEntityAccess(permission, 'edit', {\n * accessKeys: ['owner'],\n * }); // true, owners bypass all checks\n *\n * // Check loading state\n * if (permission.isLoading) {\n * return false; // Wait for permission to load\n * }\n * ```\n */\nexport function hasEntityAccess(permission: EntityPermissionCheck | null | undefined, action: EntityAction, options?: {\n accessKeys?: string[] | null;\n isArchived?: boolean;\n isSuspended?: boolean;\n}): boolean {\n // Archived/suspended users have no entity access\n if (options?.isArchived || options?.isSuspended) {\n return false;\n }\n\n // Owners bypass all entity permission checks\n const accessKeys = options?.accessKeys;\n if (isUsable(accessKeys) && accessKeys.includes(\"owner\")) {\n return true;\n }\n\n // No permission data means no access\n if (!permission) {\n return false;\n }\n\n // Still loading means no access (yet)\n if (permission.isLoading) {\n return false;\n }\n\n // Check the specific action\n switch (action) {\n case \"view\":\n return permission.canView;\n case \"adminView\":\n return permission.canAdminView;\n case \"edit\":\n return permission.canEdit;\n case \"create\":\n return permission.canCreate;\n case \"delete\":\n return permission.canDelete;\n case \"share\":\n return permission.canShare;\n default:\n return false;\n }\n}\n\n/**\n * Check if a user has any of the specified entity actions.\n *\n * @param permission - The permission check result\n * @param actions - Array of actions to check for (OR logic)\n * @param options - Additional options for the check\n * @returns boolean - true if user has permission for any of the actions\n *\n * @example\n * ```ts\n * const canEditOrDelete = hasAnyEntityAccess(permission, ['edit', 'delete']);\n * ```\n */\nexport function hasAnyEntityAccess(permission: EntityPermissionCheck | null | undefined, actions: EntityAction[], options?: {\n accessKeys?: string[] | null;\n isArchived?: boolean;\n isSuspended?: boolean;\n}): boolean {\n return actions.some(action => hasEntityAccess(permission, action, options));\n}\n\n/**\n * Check if a user has all of the specified entity actions.\n *\n * @param permission - The permission check result\n * @param actions - Array of actions to check for (AND logic)\n * @param options - Additional options for the check\n * @returns boolean - true if user has permission for all of the actions\n *\n * @example\n * ```ts\n * // Check if user can edit AND delete (admin-like permissions)\n * const isAdmin = hasAllEntityAccess(permission, ['edit', 'delete', 'share']);\n * ```\n */\nexport function hasAllEntityAccess(permission: EntityPermissionCheck | null | undefined, actions: EntityAction[], options?: {\n accessKeys?: string[] | null;\n isArchived?: boolean;\n isSuspended?: boolean;\n}): boolean {\n return actions.every(action => hasEntityAccess(permission, action, options));\n}\n\n/**\n * Check if entity access was explicitly denied.\n * Useful for distinguishing between \"no permission\" and \"explicitly blocked\".\n *\n * @param permission - The permission check result\n * @returns boolean - true if access was explicitly denied\n *\n * @example\n * ```ts\n * const permission = await getPermission('Project', 123);\n * if (isEntityAccessDenied(permission)) {\n * showBlockedMessage('You have been explicitly denied access to this project');\n * }\n * ```\n */\nexport function isEntityAccessDenied(permission: EntityPermissionCheck | null | undefined): boolean {\n return permission?.isDenied === true;\n}\n\n/**\n * Type guard to check if permission data is loaded and available.\n *\n * @param permission - The permission check result\n * @returns boolean - true if permission data is loaded\n *\n * @example\n * ```ts\n * const permission = usePermission('Project', projectId);\n *\n * if (!isPermissionLoaded(permission)) {\n * return <Spinner />;\n * }\n *\n * // TypeScript knows permission is fully loaded here\n * return permission.canEdit ? <Editor /> : <Viewer />;\n * ```\n */\nexport function isPermissionLoaded(permission: EntityPermissionCheck | null | undefined): permission is EntityPermissionCheck & {\n isLoading: false;\n} {\n return permission !== null && permission !== undefined && !permission.isLoading;\n}"],"mappings":";;;;;AA6BO,SAAS,UAAU,YAAyC,KAAa,SAGpE;AAEV,MAAI,SAAS,cAAc,SAAS,aAAa;AAC/C,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,UAAU,MAAM,OAAO;AAClC,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,OAAO,GAAG;AAChC,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,GAAG,GAAG;AAC5B,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,GAAG,MAAM,OAAO;AAC3B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAgBO,SAAS,aAAa,YAAyC,MAAgB,SAG1E;AAEV,SAAO,KAAK,KAAK,SAAO,UAAU,YAAY,KAAK,OAAO,CAAC;AAC7D;AAgBO,SAAS,aAAa,YAAyC,MAAgB,SAG1E;AAEV,SAAO,KAAK,MAAM,SAAO,UAAU,YAAY,KAAK,OAAO,CAAC;AAC9D;;;ACtEO,SAAS,gBAAgB,YAAsD,QAAsB,SAIhG;AAEV,MAAI,SAAS,cAAc,SAAS,aAAa;AAC/C,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,SAAS;AAC5B,MAAI,SAAS,UAAU,KAAK,WAAW,SAAS,OAAO,GAAG;AACxD,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,WAAW;AACxB,WAAO;AAAA,EACT;AAGA,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,WAAW;AAAA,IACpB,KAAK;AACH,aAAO,WAAW;AAAA,IACpB,KAAK;AACH,aAAO,WAAW;AAAA,IACpB,KAAK;AACH,aAAO,WAAW;AAAA,IACpB,KAAK;AACH,aAAO,WAAW;AAAA,IACpB,KAAK;AACH,aAAO,WAAW;AAAA,IACpB;AACE,aAAO;AAAA,EACX;AACF;AAeO,SAAS,mBAAmB,YAAsD,SAAyB,SAItG;AACV,SAAO,QAAQ,KAAK,YAAU,gBAAgB,YAAY,QAAQ,OAAO,CAAC;AAC5E;AAgBO,SAAS,mBAAmB,YAAsD,SAAyB,SAItG;AACV,SAAO,QAAQ,MAAM,YAAU,gBAAgB,YAAY,QAAQ,OAAO,CAAC;AAC7E;AAiBO,SAAS,qBAAqB,YAA+D;AAClG,SAAO,YAAY,aAAa;AAClC;AAoBO,SAAS,mBAAmB,YAEjC;AACA,SAAO,eAAe,QAAQ,eAAe,UAAa,CAAC,WAAW;AACxE;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-J4ZVCXZ4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSupabaseUrl
|
|
3
|
+
} from "./chunk-GC3TBUWE.js";
|
|
4
|
+
import {
|
|
5
|
+
generateUUID,
|
|
6
|
+
useSupabase
|
|
7
|
+
} from "./chunk-5EFDS7SR.js";
|
|
8
|
+
|
|
9
|
+
// src/useSupabaseFunction.ts
|
|
10
|
+
import { c as _c } from "react/compiler-runtime";
|
|
11
|
+
function useSupabaseFunction() {
|
|
12
|
+
const $ = _c(2);
|
|
13
|
+
const supabase = useSupabase();
|
|
14
|
+
let t0;
|
|
15
|
+
if ($[0] !== supabase) {
|
|
16
|
+
const downloadFunctionResponse = async function downloadFunctionResponse2(functionName, body, name) {
|
|
17
|
+
const {
|
|
18
|
+
data,
|
|
19
|
+
error
|
|
20
|
+
} = await supabase.auth.getSession();
|
|
21
|
+
if (error || !data.session) {
|
|
22
|
+
console.error("Error retrieving session:", error);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const accessToken = data.session.access_token;
|
|
26
|
+
const response = await fetch(`${getSupabaseUrl()}/functions/v1/${functionName}`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
"Content-Type": body && "application/json",
|
|
30
|
+
Authorization: `Bearer ${accessToken}`
|
|
31
|
+
},
|
|
32
|
+
body: body && JSON.stringify(body)
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
console.error("Error downloading invoice:", response.statusText);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const contentDisposition = response.headers.get("Content-Disposition");
|
|
39
|
+
const mimeType = response.headers.get("Content-Type") || "application/octet-stream";
|
|
40
|
+
const filename = name ? name : (contentDisposition && contentDisposition.split("filename=")[1]) ?? "Unknown";
|
|
41
|
+
await downloadFile(response.body, filename, mimeType);
|
|
42
|
+
};
|
|
43
|
+
t0 = {
|
|
44
|
+
downloadFunctionResponse
|
|
45
|
+
};
|
|
46
|
+
$[0] = supabase;
|
|
47
|
+
$[1] = t0;
|
|
48
|
+
} else {
|
|
49
|
+
t0 = $[1];
|
|
50
|
+
}
|
|
51
|
+
return t0;
|
|
52
|
+
}
|
|
53
|
+
async function downloadFile(stream, filename, mimeType) {
|
|
54
|
+
if (!stream) return;
|
|
55
|
+
const reader = stream.getReader();
|
|
56
|
+
const a = document.createElement("a");
|
|
57
|
+
a.style.display = "none";
|
|
58
|
+
document.body.appendChild(a);
|
|
59
|
+
try {
|
|
60
|
+
const chunks = [];
|
|
61
|
+
while (true) {
|
|
62
|
+
const {
|
|
63
|
+
done,
|
|
64
|
+
value
|
|
65
|
+
} = await reader.read();
|
|
66
|
+
if (done) break;
|
|
67
|
+
chunks.push(value);
|
|
68
|
+
}
|
|
69
|
+
const blob = new Blob(chunks, {
|
|
70
|
+
type: mimeType
|
|
71
|
+
});
|
|
72
|
+
const url = window.URL.createObjectURL(blob);
|
|
73
|
+
a.href = url;
|
|
74
|
+
a.download = filename;
|
|
75
|
+
a.click();
|
|
76
|
+
window.URL.revokeObjectURL(url);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error("Error downloading file:", error);
|
|
79
|
+
} finally {
|
|
80
|
+
document.body.removeChild(a);
|
|
81
|
+
reader.releaseLock();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/useReceiptAI.ts
|
|
86
|
+
import { useState, useCallback } from "react";
|
|
87
|
+
var pdfjsLib = null;
|
|
88
|
+
async function getPdfJs() {
|
|
89
|
+
if (!pdfjsLib) {
|
|
90
|
+
pdfjsLib = await import("./pdf-PHXP7RHD.js");
|
|
91
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;
|
|
92
|
+
}
|
|
93
|
+
return pdfjsLib;
|
|
94
|
+
}
|
|
95
|
+
var receiptSchema = {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
merchantName: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "The merchant or vendor name from the receipt"
|
|
101
|
+
},
|
|
102
|
+
transactionDate: {
|
|
103
|
+
type: ["string", "null"],
|
|
104
|
+
description: "Transaction date in YYYY-MM-DD format, or null if not found"
|
|
105
|
+
},
|
|
106
|
+
currency: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "Currency code (USD, EUR, GBP, CAD, etc.) or symbol ($, \u20AC, \xA3)"
|
|
109
|
+
},
|
|
110
|
+
subtotal: {
|
|
111
|
+
type: ["number", "null"],
|
|
112
|
+
description: "Subtotal before tax, or null if not found"
|
|
113
|
+
},
|
|
114
|
+
tax: {
|
|
115
|
+
type: ["number", "null"],
|
|
116
|
+
description: "Tax amount, or null if not found"
|
|
117
|
+
},
|
|
118
|
+
total: {
|
|
119
|
+
type: "number",
|
|
120
|
+
description: "Total amount including tax"
|
|
121
|
+
},
|
|
122
|
+
lineItems: {
|
|
123
|
+
type: "array",
|
|
124
|
+
description: "Individual items on the receipt",
|
|
125
|
+
items: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
description: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "Item description"
|
|
131
|
+
},
|
|
132
|
+
amount: {
|
|
133
|
+
type: "number",
|
|
134
|
+
description: "Item amount/price"
|
|
135
|
+
},
|
|
136
|
+
quantity: {
|
|
137
|
+
type: ["number", "null"],
|
|
138
|
+
description: "Quantity if shown"
|
|
139
|
+
},
|
|
140
|
+
suggestedCategory: {
|
|
141
|
+
type: ["string", "null"],
|
|
142
|
+
description: "Suggested expense category (e.g., 'Meals', 'Transportation', 'Supplies', 'Equipment', 'Lodging', 'Utilities', 'Entertainment', 'Office Supplies')"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
required: ["description", "amount", "quantity", "suggestedCategory"],
|
|
146
|
+
additionalProperties: false
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
confidence: {
|
|
150
|
+
type: "number",
|
|
151
|
+
description: "Confidence score from 0 to 1 for the extraction quality"
|
|
152
|
+
},
|
|
153
|
+
notes: {
|
|
154
|
+
type: ["string", "null"],
|
|
155
|
+
description: "Any additional notes or context extracted from the receipt"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
required: ["merchantName", "transactionDate", "currency", "subtotal", "tax", "total", "lineItems", "confidence", "notes"],
|
|
159
|
+
additionalProperties: false
|
|
160
|
+
};
|
|
161
|
+
function generateId() {
|
|
162
|
+
return generateUUID();
|
|
163
|
+
}
|
|
164
|
+
function useReceiptAI(options = {}) {
|
|
165
|
+
const supabase = useSupabase();
|
|
166
|
+
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
|
167
|
+
const [error, setError] = useState(null);
|
|
168
|
+
const [extractedData, setExtractedData] = useState(null);
|
|
169
|
+
const [batchProgress, setBatchProgress] = useState(null);
|
|
170
|
+
const analyzeReceipt = useCallback(async (file) => {
|
|
171
|
+
setIsAnalyzing(true);
|
|
172
|
+
setError(null);
|
|
173
|
+
try {
|
|
174
|
+
const base64 = await fileToBase64(file);
|
|
175
|
+
const context = {};
|
|
176
|
+
if (options.recentExpenses && options.recentExpenses.length > 0) {
|
|
177
|
+
context.recentExpensePatterns = options.recentExpenses.slice(0, 10);
|
|
178
|
+
context.hint = "Use recent expense patterns to help match merchant names and suggest categories based on user's history";
|
|
179
|
+
}
|
|
180
|
+
if (options.taxCategories && options.taxCategories.length > 0) {
|
|
181
|
+
context.availableCategories = options.taxCategories.map((c) => c.title);
|
|
182
|
+
context.categoryHint = "When suggesting categories for line items, try to match one of the available categories";
|
|
183
|
+
}
|
|
184
|
+
const response = await supabase.functions.invoke("ai", {
|
|
185
|
+
body: {
|
|
186
|
+
prompt: `Extract receipt data. Use official merchant names for known chains. Match categories from availableCategories when possible.`,
|
|
187
|
+
context,
|
|
188
|
+
files: [{
|
|
189
|
+
source: "base64",
|
|
190
|
+
data: base64,
|
|
191
|
+
// PDFs and images are all converted to JPEG
|
|
192
|
+
mimeType: file.type === "application/pdf" || file.type.startsWith("image/") ? "image/jpeg" : file.type,
|
|
193
|
+
fileName: file.name
|
|
194
|
+
}],
|
|
195
|
+
outputSchema: {
|
|
196
|
+
name: "receipt_extraction",
|
|
197
|
+
description: "Structured data extracted from a receipt image or document",
|
|
198
|
+
schema: receiptSchema
|
|
199
|
+
},
|
|
200
|
+
options: {
|
|
201
|
+
maxTokens: 5e3
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
if (response.error) {
|
|
206
|
+
throw new Error(`AI function error: ${response.error.message}`);
|
|
207
|
+
}
|
|
208
|
+
if (!response.data?.success) {
|
|
209
|
+
throw new Error(response.data?.error || "Failed to analyze receipt");
|
|
210
|
+
}
|
|
211
|
+
const extracted = response.data.data;
|
|
212
|
+
setExtractedData(extracted);
|
|
213
|
+
return extracted;
|
|
214
|
+
} catch (err) {
|
|
215
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
216
|
+
setError(message);
|
|
217
|
+
console.error("Receipt analysis error:", err);
|
|
218
|
+
return null;
|
|
219
|
+
} finally {
|
|
220
|
+
setIsAnalyzing(false);
|
|
221
|
+
}
|
|
222
|
+
}, [supabase, options.recentExpenses, options.taxCategories]);
|
|
223
|
+
const reset = useCallback(() => {
|
|
224
|
+
setExtractedData(null);
|
|
225
|
+
setError(null);
|
|
226
|
+
setBatchProgress(null);
|
|
227
|
+
}, []);
|
|
228
|
+
const analyzeReceiptInternal = useCallback(async (file_0) => {
|
|
229
|
+
try {
|
|
230
|
+
const base64_0 = await fileToBase64(file_0);
|
|
231
|
+
const context_0 = {};
|
|
232
|
+
if (options.recentExpenses && options.recentExpenses.length > 0) {
|
|
233
|
+
context_0.recentExpensePatterns = options.recentExpenses.slice(0, 10);
|
|
234
|
+
context_0.hint = "Use recent expense patterns to help match merchant names and suggest categories based on user's history";
|
|
235
|
+
}
|
|
236
|
+
if (options.taxCategories && options.taxCategories.length > 0) {
|
|
237
|
+
context_0.availableCategories = options.taxCategories.map((c_0) => c_0.title);
|
|
238
|
+
context_0.categoryHint = "When suggesting categories for line items, try to match one of the available categories";
|
|
239
|
+
}
|
|
240
|
+
const response_0 = await supabase.functions.invoke("ai", {
|
|
241
|
+
body: {
|
|
242
|
+
prompt: `Extract receipt data. Use official merchant names for known chains. Match categories from availableCategories when possible.`,
|
|
243
|
+
context: context_0,
|
|
244
|
+
files: [{
|
|
245
|
+
source: "base64",
|
|
246
|
+
data: base64_0,
|
|
247
|
+
// PDFs and images are all converted to JPEG
|
|
248
|
+
mimeType: file_0.type === "application/pdf" || file_0.type.startsWith("image/") ? "image/jpeg" : file_0.type,
|
|
249
|
+
fileName: file_0.name
|
|
250
|
+
}],
|
|
251
|
+
outputSchema: {
|
|
252
|
+
name: "receipt_extraction",
|
|
253
|
+
description: "Structured data extracted from a receipt image or document",
|
|
254
|
+
schema: receiptSchema
|
|
255
|
+
},
|
|
256
|
+
options: {
|
|
257
|
+
maxTokens: 5e3
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
if (response_0.error) {
|
|
262
|
+
throw new Error(`AI function error: ${response_0.error.message}`);
|
|
263
|
+
}
|
|
264
|
+
if (!response_0.data?.success) {
|
|
265
|
+
throw new Error(response_0.data?.error || "Failed to analyze receipt");
|
|
266
|
+
}
|
|
267
|
+
return response_0.data.data;
|
|
268
|
+
} catch (err_0) {
|
|
269
|
+
console.error("Receipt analysis error:", err_0);
|
|
270
|
+
throw err_0;
|
|
271
|
+
}
|
|
272
|
+
}, [supabase, options.recentExpenses, options.taxCategories]);
|
|
273
|
+
const analyzeMultipleReceipts = useCallback(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_1) => ({
|
|
284
|
+
id: generateId(),
|
|
285
|
+
file: file_1,
|
|
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({
|
|
297
|
+
...progress
|
|
298
|
+
});
|
|
299
|
+
onProgress?.({
|
|
300
|
+
...progress
|
|
301
|
+
});
|
|
302
|
+
try {
|
|
303
|
+
const extracted_0 = await analyzeReceiptInternal(pending.file);
|
|
304
|
+
pending.extractedData = extracted_0;
|
|
305
|
+
pending.status = "ready";
|
|
306
|
+
} catch (err_1) {
|
|
307
|
+
pending.error = err_1 instanceof Error ? err_1.message : "Unknown error";
|
|
308
|
+
pending.status = "error";
|
|
309
|
+
}
|
|
310
|
+
progress.completed++;
|
|
311
|
+
progress.processing = progress.processing.filter((name) => name !== pending.file.name);
|
|
312
|
+
setBatchProgress({
|
|
313
|
+
...progress
|
|
314
|
+
});
|
|
315
|
+
onProgress?.({
|
|
316
|
+
...progress
|
|
317
|
+
});
|
|
318
|
+
onItemComplete?.(pending);
|
|
319
|
+
results.push(pending);
|
|
320
|
+
if (queue.length > 0) {
|
|
321
|
+
await processNext();
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
for (let i = 0; i < Math.min(CONCURRENCY_LIMIT, files.length); i++) {
|
|
325
|
+
inProgress.push(processNext());
|
|
326
|
+
}
|
|
327
|
+
await Promise.all(inProgress);
|
|
328
|
+
setBatchProgress(null);
|
|
329
|
+
return pendingItems;
|
|
330
|
+
}, [analyzeReceiptInternal]);
|
|
331
|
+
return {
|
|
332
|
+
analyzeReceipt,
|
|
333
|
+
analyzeMultipleReceipts,
|
|
334
|
+
isAnalyzing,
|
|
335
|
+
batchProgress,
|
|
336
|
+
error,
|
|
337
|
+
extractedData,
|
|
338
|
+
reset
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
async function pdfToImage(file, maxDimension = 1200) {
|
|
342
|
+
const pdfjs = await getPdfJs();
|
|
343
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
344
|
+
const pdf = await pdfjs.getDocument({
|
|
345
|
+
data: arrayBuffer
|
|
346
|
+
}).promise;
|
|
347
|
+
const page = await pdf.getPage(1);
|
|
348
|
+
const viewport = page.getViewport({
|
|
349
|
+
scale: 1
|
|
350
|
+
});
|
|
351
|
+
const scale = Math.min(maxDimension / viewport.width, maxDimension / viewport.height, 1.5);
|
|
352
|
+
const scaledViewport = page.getViewport({
|
|
353
|
+
scale
|
|
354
|
+
});
|
|
355
|
+
const canvas = document.createElement("canvas");
|
|
356
|
+
canvas.width = scaledViewport.width;
|
|
357
|
+
canvas.height = scaledViewport.height;
|
|
358
|
+
const ctx = canvas.getContext("2d");
|
|
359
|
+
if (!ctx) {
|
|
360
|
+
throw new Error("Could not get canvas context");
|
|
361
|
+
}
|
|
362
|
+
ctx.fillStyle = "#ffffff";
|
|
363
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
364
|
+
await page.render({
|
|
365
|
+
canvasContext: ctx,
|
|
366
|
+
viewport: scaledViewport
|
|
367
|
+
}).promise;
|
|
368
|
+
const dataUrl = canvas.toDataURL("image/jpeg", 0.9);
|
|
369
|
+
const base64 = dataUrl.split(",")[1];
|
|
370
|
+
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)`);
|
|
371
|
+
return base64;
|
|
372
|
+
}
|
|
373
|
+
async function fileToBase64(file, maxDimension = 1024) {
|
|
374
|
+
if (file.type === "application/pdf") {
|
|
375
|
+
return pdfToImage(file, 1200);
|
|
376
|
+
}
|
|
377
|
+
if (!file.type.startsWith("image/")) {
|
|
378
|
+
return new Promise((resolve, reject) => {
|
|
379
|
+
const reader = new FileReader();
|
|
380
|
+
reader.onload = () => {
|
|
381
|
+
const result = reader.result;
|
|
382
|
+
const base64 = result.split(",")[1];
|
|
383
|
+
resolve(base64);
|
|
384
|
+
};
|
|
385
|
+
reader.onerror = reject;
|
|
386
|
+
reader.readAsDataURL(file);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return new Promise((resolve, reject) => {
|
|
390
|
+
const img = new Image();
|
|
391
|
+
img.onload = () => {
|
|
392
|
+
let {
|
|
393
|
+
width,
|
|
394
|
+
height
|
|
395
|
+
} = img;
|
|
396
|
+
if (width > maxDimension || height > maxDimension) {
|
|
397
|
+
if (width > height) {
|
|
398
|
+
height = Math.round(height * maxDimension / width);
|
|
399
|
+
width = maxDimension;
|
|
400
|
+
} else {
|
|
401
|
+
width = Math.round(width * maxDimension / height);
|
|
402
|
+
height = maxDimension;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const canvas = document.createElement("canvas");
|
|
406
|
+
canvas.width = width;
|
|
407
|
+
canvas.height = height;
|
|
408
|
+
const ctx = canvas.getContext("2d");
|
|
409
|
+
if (!ctx) {
|
|
410
|
+
reject(new Error("Could not get canvas context"));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
414
|
+
const dataUrl = canvas.toDataURL("image/jpeg", 0.85);
|
|
415
|
+
const base64 = dataUrl.split(",")[1];
|
|
416
|
+
console.log(`\u{1F4F7} Image resized: ${img.width}x${img.height} \u2192 ${width}x${height}`);
|
|
417
|
+
resolve(base64);
|
|
418
|
+
};
|
|
419
|
+
img.onerror = reject;
|
|
420
|
+
img.src = URL.createObjectURL(file);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
var useReceiptAI_default = useReceiptAI;
|
|
424
|
+
|
|
425
|
+
export {
|
|
426
|
+
useSupabaseFunction,
|
|
427
|
+
useReceiptAI_default
|
|
428
|
+
};
|
|
429
|
+
//# sourceMappingURL=chunk-JAATANS3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useSupabaseFunction.ts","../src/useReceiptAI.ts"],"sourcesContent":["import { c as _c } from \"react/compiler-runtime\";\nimport { getSupabaseUrl } from \"./config\";\nimport useSupabase, { typedSupabase } from \"./useSupabase\";\nexport default function useSupabaseFunction() {\n const $ = _c(2);\n const supabase = useSupabase();\n let t0;\n if ($[0] !== supabase) {\n const downloadFunctionResponse = async function downloadFunctionResponse(functionName, body, name) {\n const {\n data,\n error\n } = await supabase.auth.getSession();\n if (error || !data.session) {\n console.error(\"Error retrieving session:\", error);\n return;\n }\n const accessToken = data.session.access_token;\n const response = await fetch(`${getSupabaseUrl()}/functions/v1/${functionName}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": body && \"application/json\",\n Authorization: `Bearer ${accessToken}`\n },\n body: body && JSON.stringify(body)\n });\n if (!response.ok) {\n console.error(\"Error downloading invoice:\", response.statusText);\n return;\n }\n const contentDisposition = response.headers.get(\"Content-Disposition\");\n const mimeType = response.headers.get(\"Content-Type\") || \"application/octet-stream\";\n const filename = name ? name : (contentDisposition && contentDisposition.split(\"filename=\")[1]) ?? \"Unknown\";\n await downloadFile(response.body as any, filename, mimeType);\n };\n t0 = {\n downloadFunctionResponse\n };\n $[0] = supabase;\n $[1] = t0;\n } else {\n t0 = $[1];\n }\n return t0;\n}\nasync function downloadFile(stream: ReadableStream<Uint8Array> | null, filename: string, mimeType: string) {\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 try {\n const chunks = [];\n while (true) {\n const {\n done,\n value\n } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n const blob = new Blob(chunks, {\n type: mimeType\n });\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","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;\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: \"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: \"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: \"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};\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}\nexport interface BatchAnalysisProgress {\n total: number;\n completed: number;\n processing: string[]; // file names currently processing\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}\ninterface RecentExpenseContext {\n merchantName: string;\n category: string | null;\n amount: number;\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}\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>(null);\n // Batch processing state\n const [batchProgress, setBatchProgress] = useState<BatchAnalysisProgress | null>(null);\n const analyzeReceipt = useCallback(async (file: File): Promise<ExtractedReceipt | null> => {\n setIsAnalyzing(true);\n setError(null);\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 if (options.recentExpenses && options.recentExpenses.length > 0) {\n context.recentExpensePatterns = options.recentExpenses.slice(0, 10);\n context.hint = \"Use recent expense patterns to help match merchant names and suggest categories based on user's history\";\n }\n if (options.taxCategories && options.taxCategories.length > 0) {\n context.availableCategories = options.taxCategories.map(c => c.title);\n context.categoryHint = \"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<AIResponse<ExtractedReceipt>>(\"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 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 outputSchema: {\n name: \"receipt_extraction\",\n description: \"Structured data extracted from a receipt image or document\",\n schema: receiptSchema\n },\n options: {\n maxTokens: 5000\n }\n }\n });\n if (response.error) {\n throw new Error(`AI function error: ${response.error.message}`);\n }\n if (!response.data?.success) {\n throw new Error(response.data?.error || \"Failed to analyze receipt\");\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 }, [supabase, options.recentExpenses, options.taxCategories]);\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(async (file_0: File): Promise<ExtractedReceipt | null> => {\n try {\n const base64_0 = await fileToBase64(file_0);\n const context_0: Record<string, unknown> = {};\n if (options.recentExpenses && options.recentExpenses.length > 0) {\n context_0.recentExpensePatterns = options.recentExpenses.slice(0, 10);\n context_0.hint = \"Use recent expense patterns to help match merchant names and suggest categories based on user's history\";\n }\n if (options.taxCategories && options.taxCategories.length > 0) {\n context_0.availableCategories = options.taxCategories.map(c_0 => c_0.title);\n context_0.categoryHint = \"When suggesting categories for line items, try to match one of the available categories\";\n }\n const response_0 = await supabase.functions.invoke<AIResponse<ExtractedReceipt>>(\"ai\", {\n body: {\n prompt: `Extract receipt data. Use official merchant names for known chains. Match categories from availableCategories when possible.`,\n context: context_0,\n files: [{\n source: \"base64\",\n data: base64_0,\n // PDFs and images are all converted to JPEG\n mimeType: file_0.type === \"application/pdf\" || file_0.type.startsWith(\"image/\") ? \"image/jpeg\" : file_0.type,\n fileName: file_0.name\n }],\n outputSchema: {\n name: \"receipt_extraction\",\n description: \"Structured data extracted from a receipt image or document\",\n schema: receiptSchema\n },\n options: {\n maxTokens: 5000\n }\n }\n });\n if (response_0.error) {\n throw new Error(`AI function error: ${response_0.error.message}`);\n }\n if (!response_0.data?.success) {\n throw new Error(response_0.data?.error || \"Failed to analyze receipt\");\n }\n return response_0.data.data!;\n } catch (err_0) {\n console.error(\"Receipt analysis error:\", err_0);\n throw err_0;\n }\n }, [supabase, options.recentExpenses, options.taxCategories]);\n\n // Batch analyze multiple receipts with concurrency control\n const analyzeMultipleReceipts = useCallback(async (files: File[], onProgress?: (progress: BatchAnalysisProgress) => void, onItemComplete?: (item: PendingExpense) => void): 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 setBatchProgress(progress);\n onProgress?.(progress);\n\n // Initialize all pending expenses\n const pendingItems: PendingExpense[] = files.map(file_1 => ({\n id: generateId(),\n file: file_1,\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 const processNext = async (): Promise<void> => {\n if (queue.length === 0) return;\n const pending = queue.shift()!;\n progress.processing.push(pending.file.name);\n setBatchProgress({\n ...progress\n });\n onProgress?.({\n ...progress\n });\n try {\n const extracted_0 = await analyzeReceiptInternal(pending.file);\n pending.extractedData = extracted_0;\n pending.status = \"ready\";\n } catch (err_1) {\n pending.error = err_1 instanceof Error ? err_1.message : \"Unknown error\";\n pending.status = \"error\";\n }\n\n // Update progress\n progress.completed++;\n progress.processing = progress.processing.filter(name => name !== pending.file.name);\n setBatchProgress({\n ...progress\n });\n onProgress?.({\n ...progress\n });\n\n // Notify caller immediately when this item completes\n onItemComplete?.(pending);\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 setBatchProgress(null);\n return pendingItems;\n }, [analyzeReceiptInternal]);\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({\n data: arrayBuffer\n }).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({\n scale: 1\n });\n const scale = Math.min(maxDimension / viewport.width, maxDimension / viewport.height, 1.5);\n const scaledViewport = page.getViewport({\n scale\n });\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 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 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 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 {\n width,\n height\n } = 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 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}\nexport default useReceiptAI;"],"mappings":"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","names":["downloadFunctionResponse"]}
|