pelagora 0.2.0 → 0.2.2
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/CHANGELOG.md +16 -0
- package/dist/ai/upc-lookup.d.ts +37 -0
- package/dist/ai/upc-lookup.d.ts.map +1 -0
- package/dist/ai/upc-lookup.js +280 -0
- package/dist/ai/upc-lookup.js.map +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/scans.d.ts.map +1 -1
- package/dist/api/scans.js +86 -24
- package/dist/api/scans.js.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +2 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +235 -103
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
- package/footer-brand.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to **pelagora** will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.2.2] — 2026-03-26
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Multi-stage UPC barcode resolution pipeline: UPCitemdb → Serper+AI → Direct AI → Reffo.ai fallback
|
|
11
|
+
- UPC normalization with multi-format digit variants (EAN-8, UPC-A, EAN-13)
|
|
12
|
+
- SKU-based product catalog cache with index for fast UPC lookups
|
|
13
|
+
- Rich barcode result card: product image, name, price with confidence badge, attributes, product URL
|
|
14
|
+
- Unidentified barcode flow: manual product name entry with retry
|
|
15
|
+
- Support for manual name override on barcode retry (`{ upc, name }` request body)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Barcode route now resolves UPC → product name before enrichment (previously treated UPC digits as a product name)
|
|
19
|
+
- Cache lookup uses `sku` field instead of `name_normalized` for barcode results
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
7
23
|
## [Unreleased]
|
|
8
24
|
|
|
9
25
|
> **Note:** Versions 0.1.1–0.1.8 were published to npm but not documented here. Entries below cover all changes since 0.1.0.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-stage UPC/barcode resolution.
|
|
3
|
+
* Resolves a UPC code to a product name via a fallback chain:
|
|
4
|
+
* 1. UPCitemdb free trial API (no key required, 100/day)
|
|
5
|
+
* 2. Google search via Serper + AI extraction
|
|
6
|
+
* 3. Direct AI identification (least reliable)
|
|
7
|
+
*/
|
|
8
|
+
import type { AiProvider } from './product-lookup';
|
|
9
|
+
export interface UpcResolveResult {
|
|
10
|
+
name: string;
|
|
11
|
+
brand: string | null;
|
|
12
|
+
description: string | null;
|
|
13
|
+
category: string | null;
|
|
14
|
+
imageUrl: string | null;
|
|
15
|
+
priceLow: number | null;
|
|
16
|
+
priceHigh: number | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Stage 1: Resolve UPC via UPCitemdb free trial API.
|
|
20
|
+
* Free tier: 100 lookups/day, 6/minute. No API key required.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveUpc(upc: string): Promise<UpcResolveResult | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Stage 2: Search Google via Serper for a UPC and use AI to extract the product name.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveUpcViaSearch(upc: string, provider: AiProvider, aiApiKey: string, serperKey: string): Promise<UpcResolveResult | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Stage 3: Ask AI directly to identify a product from its UPC/EAN barcode.
|
|
29
|
+
* Least reliable method — the AI may not have the UPC in its training data.
|
|
30
|
+
*/
|
|
31
|
+
export declare function identifyUpcWithAI(upc: string, provider: AiProvider, apiKey: string): Promise<string | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Stage 2 via Reffo.ai: Use Reffo.ai's barcode endpoint as the search+AI stage
|
|
34
|
+
* when the user has a Reffo API key but no direct AI provider or Serper key.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveUpcViaReffo(upc: string, reffoApiKey: string, reffoUrl: string): Promise<UpcResolveResult | null>;
|
|
37
|
+
//# sourceMappingURL=upc-lookup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upc-lookup.d.ts","sourceRoot":"","sources":["../../src/ai/upc-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAgBD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA2C9E;AA4FD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAiFlC;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,UAAU,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAUxB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAgClC"}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-stage UPC/barcode resolution.
|
|
4
|
+
* Resolves a UPC code to a product name via a fallback chain:
|
|
5
|
+
* 1. UPCitemdb free trial API (no key required, 100/day)
|
|
6
|
+
* 2. Google search via Serper + AI extraction
|
|
7
|
+
* 3. Direct AI identification (least reliable)
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.resolveUpc = resolveUpc;
|
|
11
|
+
exports.resolveUpcViaSearch = resolveUpcViaSearch;
|
|
12
|
+
exports.identifyUpcWithAI = identifyUpcWithAI;
|
|
13
|
+
exports.resolveUpcViaReffo = resolveUpcViaReffo;
|
|
14
|
+
/**
|
|
15
|
+
* Normalize a UPC/EAN barcode: strip leading zeros to get the core UPC-A (12 digits).
|
|
16
|
+
* Returns both the original and stripped versions for multi-format lookups.
|
|
17
|
+
*/
|
|
18
|
+
function normalizeUpc(upc) {
|
|
19
|
+
const variants = new Set();
|
|
20
|
+
variants.add(upc);
|
|
21
|
+
const stripped = upc.replace(/^0+/, '');
|
|
22
|
+
if (stripped.length >= 8)
|
|
23
|
+
variants.add(stripped);
|
|
24
|
+
if (stripped.length <= 12)
|
|
25
|
+
variants.add(stripped.padStart(12, '0'));
|
|
26
|
+
if (stripped.length <= 13)
|
|
27
|
+
variants.add(stripped.padStart(13, '0'));
|
|
28
|
+
return [...variants];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Stage 1: Resolve UPC via UPCitemdb free trial API.
|
|
32
|
+
* Free tier: 100 lookups/day, 6/minute. No API key required.
|
|
33
|
+
*/
|
|
34
|
+
async function resolveUpc(upc) {
|
|
35
|
+
const variants = normalizeUpc(upc);
|
|
36
|
+
for (const variant of variants) {
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(`https://api.upcitemdb.com/prod/trial/lookup?upc=${encodeURIComponent(variant)}`, { signal: AbortSignal.timeout(5000) });
|
|
39
|
+
if (!res.ok)
|
|
40
|
+
continue;
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
if (data.code !== 'OK' || !data.items?.length)
|
|
43
|
+
continue;
|
|
44
|
+
const item = data.items[0];
|
|
45
|
+
if (!item.title)
|
|
46
|
+
continue;
|
|
47
|
+
const catStr = typeof item.category === 'string' ? item.category : '';
|
|
48
|
+
const topCategory = catStr.split('>')[0]?.trim() || null;
|
|
49
|
+
const images = item.images;
|
|
50
|
+
return {
|
|
51
|
+
name: item.title,
|
|
52
|
+
brand: item.brand || null,
|
|
53
|
+
description: item.description || null,
|
|
54
|
+
category: topCategory,
|
|
55
|
+
imageUrl: images?.[0] || null,
|
|
56
|
+
priceLow: typeof item.lowest_recorded_price === 'number'
|
|
57
|
+
? item.lowest_recorded_price
|
|
58
|
+
: null,
|
|
59
|
+
priceHigh: typeof item.highest_recorded_price === 'number'
|
|
60
|
+
? item.highest_recorded_price
|
|
61
|
+
: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// --- AI helper functions for Serper + AI and direct AI stages ---
|
|
71
|
+
function extractAiText(provider, data) {
|
|
72
|
+
const d = data;
|
|
73
|
+
if (provider === 'anthropic') {
|
|
74
|
+
const content = d.content || [];
|
|
75
|
+
return content[0]?.type === 'text' ? content[0].text.trim() : '';
|
|
76
|
+
}
|
|
77
|
+
if (provider === 'google') {
|
|
78
|
+
const candidates = d.candidates;
|
|
79
|
+
return candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? '';
|
|
80
|
+
}
|
|
81
|
+
// openai / xai compatible
|
|
82
|
+
const choices = d.choices || [];
|
|
83
|
+
return choices[0]?.message?.content?.trim() ?? '';
|
|
84
|
+
}
|
|
85
|
+
function getAiEndpoint(provider) {
|
|
86
|
+
switch (provider) {
|
|
87
|
+
case 'anthropic':
|
|
88
|
+
return { url: 'https://api.anthropic.com/v1/messages', model: 'claude-haiku-4-5-20251001' };
|
|
89
|
+
case 'openai':
|
|
90
|
+
return { url: 'https://api.openai.com/v1/chat/completions', model: 'gpt-4o-mini' };
|
|
91
|
+
case 'google':
|
|
92
|
+
return { url: '', model: 'gemini-2.0-flash-lite' }; // URL constructed differently
|
|
93
|
+
case 'xai':
|
|
94
|
+
return { url: 'https://api.x.ai/v1/chat/completions', model: 'grok-3-mini-fast' };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function callAiSimple(provider, apiKey, prompt, timeoutMs) {
|
|
98
|
+
const { url, model } = getAiEndpoint(provider);
|
|
99
|
+
let res;
|
|
100
|
+
if (provider === 'anthropic') {
|
|
101
|
+
res = await fetch(url, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
'x-api-key': apiKey,
|
|
106
|
+
'anthropic-version': '2023-06-01',
|
|
107
|
+
},
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
model,
|
|
110
|
+
max_tokens: 256,
|
|
111
|
+
temperature: 0,
|
|
112
|
+
messages: [{ role: 'user', content: prompt }],
|
|
113
|
+
}),
|
|
114
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else if (provider === 'google') {
|
|
118
|
+
const googleUrl = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
|
|
119
|
+
res = await fetch(googleUrl, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { 'Content-Type': 'application/json' },
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
124
|
+
generationConfig: { temperature: 0, maxOutputTokens: 256 },
|
|
125
|
+
}),
|
|
126
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// openai / xai
|
|
131
|
+
res = await fetch(url, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
model,
|
|
139
|
+
temperature: 0,
|
|
140
|
+
max_tokens: 256,
|
|
141
|
+
messages: [{ role: 'user', content: prompt }],
|
|
142
|
+
}),
|
|
143
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (!res.ok)
|
|
147
|
+
return '';
|
|
148
|
+
const data = await res.json();
|
|
149
|
+
return extractAiText(provider, data);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Stage 2: Search Google via Serper for a UPC and use AI to extract the product name.
|
|
153
|
+
*/
|
|
154
|
+
async function resolveUpcViaSearch(upc, provider, aiApiKey, serperKey) {
|
|
155
|
+
try {
|
|
156
|
+
// Search Google for the UPC
|
|
157
|
+
const searchRes = await fetch('https://google.serper.dev/search', {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers: {
|
|
160
|
+
'X-API-KEY': serperKey,
|
|
161
|
+
'Content-Type': 'application/json',
|
|
162
|
+
},
|
|
163
|
+
body: JSON.stringify({ q: `"${upc}" UPC product`, num: 10 }),
|
|
164
|
+
signal: AbortSignal.timeout(8000),
|
|
165
|
+
});
|
|
166
|
+
if (!searchRes.ok) {
|
|
167
|
+
console.error(`[upc-lookup] Serper search failed: ${searchRes.status}`);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const searchData = await searchRes.json();
|
|
171
|
+
const organic = searchData.organic;
|
|
172
|
+
const shopping = searchData.shopping;
|
|
173
|
+
const knowledgeGraph = searchData.knowledgeGraph;
|
|
174
|
+
// If knowledge graph has a direct answer, use it
|
|
175
|
+
if (knowledgeGraph?.title && !knowledgeGraph.title.match(/^\d+$/)) {
|
|
176
|
+
console.log(`[upc-lookup] Knowledge graph hit: "${knowledgeGraph.title}"`);
|
|
177
|
+
return {
|
|
178
|
+
name: knowledgeGraph.title,
|
|
179
|
+
brand: null,
|
|
180
|
+
description: knowledgeGraph.description || null,
|
|
181
|
+
category: null,
|
|
182
|
+
imageUrl: null,
|
|
183
|
+
priceLow: null,
|
|
184
|
+
priceHigh: null,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// Combine organic + shopping results for AI to parse
|
|
188
|
+
const allResults = [];
|
|
189
|
+
if (organic?.length) {
|
|
190
|
+
organic.slice(0, 5).forEach((r, i) => {
|
|
191
|
+
allResults.push(`${i + 1}. ${r.title}\n ${r.snippet}`);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (shopping?.length) {
|
|
195
|
+
shopping.slice(0, 3).forEach((r) => {
|
|
196
|
+
allResults.push(`Shopping: ${r.title} (${r.source})`);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (!allResults.length) {
|
|
200
|
+
console.log(`[upc-lookup] Serper returned no results for "${upc}"`);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const resultsSummary = allResults.join('\n');
|
|
204
|
+
// Ask AI to extract the product name from search results
|
|
205
|
+
const prompt = `I searched Google for UPC barcode "${upc}" and got these results:\n\n${resultsSummary}\n\nBased on these results, what is the product? Return ONLY a JSON object: {"name": "product name", "brand": "brand or null"}. If the results don't clearly identify a product, return {"name": null, "brand": null}. No other text.`;
|
|
206
|
+
const text = await callAiSimple(provider, aiApiKey, prompt, 10000);
|
|
207
|
+
if (!text)
|
|
208
|
+
return null;
|
|
209
|
+
const cleaned = text.replace(/```json\s*/g, '').replace(/```\s*/g, '').trim();
|
|
210
|
+
const parsed = JSON.parse(cleaned);
|
|
211
|
+
if (!parsed.name)
|
|
212
|
+
return null;
|
|
213
|
+
return {
|
|
214
|
+
name: parsed.name,
|
|
215
|
+
brand: parsed.brand || null,
|
|
216
|
+
description: null,
|
|
217
|
+
category: null,
|
|
218
|
+
imageUrl: null,
|
|
219
|
+
priceLow: null,
|
|
220
|
+
priceHigh: null,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
console.error('[upc-lookup] Serper search resolve error:', err);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Stage 3: Ask AI directly to identify a product from its UPC/EAN barcode.
|
|
230
|
+
* Least reliable method — the AI may not have the UPC in its training data.
|
|
231
|
+
*/
|
|
232
|
+
async function identifyUpcWithAI(upc, provider, apiKey) {
|
|
233
|
+
try {
|
|
234
|
+
const prompt = `What product has the UPC/EAN barcode: ${upc}?\n\nReturn ONLY the product name as a short string (e.g. "Crest 3D White Toothpaste 4.1oz"). If you cannot confidently identify it, return exactly "UNKNOWN". Do not include any other text.`;
|
|
235
|
+
const text = await callAiSimple(provider, apiKey, prompt, 10000);
|
|
236
|
+
if (!text || text === 'UNKNOWN' || text.length > 200)
|
|
237
|
+
return null;
|
|
238
|
+
return text;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Stage 2 via Reffo.ai: Use Reffo.ai's barcode endpoint as the search+AI stage
|
|
246
|
+
* when the user has a Reffo API key but no direct AI provider or Serper key.
|
|
247
|
+
*/
|
|
248
|
+
async function resolveUpcViaReffo(upc, reffoApiKey, reffoUrl) {
|
|
249
|
+
try {
|
|
250
|
+
const res = await fetch(`${reffoUrl}/api/scan/barcode`, {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: {
|
|
253
|
+
'Content-Type': 'application/json',
|
|
254
|
+
'Authorization': `Bearer ${reffoApiKey}`,
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify({ upc }),
|
|
257
|
+
signal: AbortSignal.timeout(30000),
|
|
258
|
+
});
|
|
259
|
+
if (!res.ok)
|
|
260
|
+
return null;
|
|
261
|
+
const data = await res.json();
|
|
262
|
+
if (data.unidentified || !data.name)
|
|
263
|
+
return null;
|
|
264
|
+
const pe = (data.price_estimate || {});
|
|
265
|
+
return {
|
|
266
|
+
name: data.name,
|
|
267
|
+
brand: null,
|
|
268
|
+
description: data.description || null,
|
|
269
|
+
category: null,
|
|
270
|
+
imageUrl: data.image_url || null,
|
|
271
|
+
priceLow: typeof pe.low === 'number' ? pe.low : null,
|
|
272
|
+
priceHigh: typeof pe.high === 'number' ? pe.high : null,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
console.error('[upc-lookup] Reffo.ai resolve error:', err);
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=upc-lookup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upc-lookup.js","sourceRoot":"","sources":["../../src/ai/upc-lookup.ts"],"names":[],"mappings":"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"}
|
package/dist/api/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAe9B,wBAAgB,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAe9B,wBAAgB,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CA2G9D"}
|
package/dist/api/index.js
CHANGED
|
@@ -65,6 +65,8 @@ function createApp(localToken) {
|
|
|
65
65
|
app.get('/favicon.ico', (_req, res) => { res.type('image/x-icon').sendFile(path_1.default.join(__dirname, '../../favicon.ico')); });
|
|
66
66
|
app.get('/footer-brand.png', (_req, res) => { res.sendFile(path_1.default.join(__dirname, '../../footer-brand.png')); });
|
|
67
67
|
app.get('/header-logo.png', (_req, res) => { res.sendFile(path_1.default.join(__dirname, '../../header-logo.png')); });
|
|
68
|
+
app.get('/pelagora-logo.png', (_req, res) => { res.sendFile(path_1.default.join(__dirname, '../../pelagora-logo.png')); });
|
|
69
|
+
app.get('/pelagora-logo-reverse.png', (_req, res) => { res.sendFile(path_1.default.join(__dirname, '../../pelagora-logo_reverse.png')); });
|
|
68
70
|
app.get('/', (_req, res) => {
|
|
69
71
|
res.type('html').send((0, ui_1.renderUI)(localToken));
|
|
70
72
|
});
|
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":";;;;;AAeA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"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"}
|
package/dist/api/scans.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scans.d.ts","sourceRoot":"","sources":["../../src/api/scans.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scans.d.ts","sourceRoot":"","sources":["../../src/api/scans.ts"],"names":[],"mappings":"AAuCA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAycxB,eAAe,MAAM,CAAC"}
|
package/dist/api/scans.js
CHANGED
|
@@ -13,6 +13,7 @@ const reffo_client_1 = require("../sync/reffo-client");
|
|
|
13
13
|
const schema_1 = require("../db/schema");
|
|
14
14
|
const ref_schemas_1 = require("../ref-schemas");
|
|
15
15
|
const product_lookup_1 = require("../ai/product-lookup");
|
|
16
|
+
const upc_lookup_1 = require("../ai/upc-lookup");
|
|
16
17
|
const UPLOADS_DIR = path_1.default.join(process.cwd(), 'uploads');
|
|
17
18
|
const SCAN_DIR = path_1.default.join(UPLOADS_DIR, 'scans');
|
|
18
19
|
const scanStorage = multer_1.default.diskStorage({
|
|
@@ -286,27 +287,27 @@ router.post('/confirm', async (req, res) => {
|
|
|
286
287
|
}
|
|
287
288
|
res.json({ confirmed: created.length, refs: created });
|
|
288
289
|
});
|
|
289
|
-
// POST /scans/barcode —
|
|
290
|
+
// POST /scans/barcode — Multi-stage UPC/barcode lookup
|
|
291
|
+
// Fallback chain: cache → UPCitemdb → Serper+AI → direct AI → Reffo.ai
|
|
290
292
|
router.post('/barcode', async (req, res) => {
|
|
291
|
-
const { upc } = req.body;
|
|
293
|
+
const { upc, name: manualName } = req.body;
|
|
292
294
|
if (!upc || typeof upc !== 'string' || !upc.trim()) {
|
|
293
295
|
return res.status(400).json({ error: 'upc is required' });
|
|
294
296
|
}
|
|
295
|
-
const
|
|
296
|
-
const nameNormalized = name.toLowerCase();
|
|
297
|
-
// Check local SQLite cache first
|
|
297
|
+
const trimmedUpc = upc.trim();
|
|
298
298
|
const db = (0, schema_1.getDb)();
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
AND expires_at > datetime('now')`).get(nameNormalized);
|
|
299
|
+
// 1. Check cache by SKU (the UPC itself)
|
|
300
|
+
const cached = db.prepare(`SELECT * FROM product_catalog WHERE sku = ? AND expires_at > datetime('now')`).get(trimmedUpc);
|
|
302
301
|
if (cached) {
|
|
303
302
|
db.prepare(`UPDATE product_catalog SET lookup_count = lookup_count + 1 WHERE id = ?`).run(cached.id);
|
|
303
|
+
const attrs = JSON.parse(cached.attributes || '{}');
|
|
304
304
|
return res.json({
|
|
305
|
+
name: attrs.product_name || cached.name_normalized,
|
|
305
306
|
description: cached.description,
|
|
306
307
|
sku: cached.sku,
|
|
307
308
|
product_url: cached.product_url,
|
|
308
309
|
image_url: cached.image_url,
|
|
309
|
-
attributes:
|
|
310
|
+
attributes: attrs,
|
|
310
311
|
price_estimate: {
|
|
311
312
|
low: cached.price_low, high: cached.price_high,
|
|
312
313
|
typical: cached.price_typical, confidence: cached.price_confidence || 'low',
|
|
@@ -314,54 +315,115 @@ router.post('/barcode', async (req, res) => {
|
|
|
314
315
|
cached: true,
|
|
315
316
|
});
|
|
316
317
|
}
|
|
318
|
+
// 2. Resolve UPC → product name via multi-stage fallback
|
|
319
|
+
let productName = null;
|
|
320
|
+
let productCategory;
|
|
317
321
|
const provider = (process.env.AI_PROVIDER || 'reffo').toLowerCase();
|
|
318
|
-
const
|
|
322
|
+
const aiApiKey = process.env.AI_API_KEY;
|
|
323
|
+
const serperKey = process.env.SERPER_API_KEY;
|
|
324
|
+
const reffoApiKey = process.env.REFFO_API_KEY;
|
|
325
|
+
const reffoUrl = process.env.REFFO_API_URL || 'https://reffo.ai';
|
|
326
|
+
// Level 0: User provided the product name manually (retry of unidentified barcode)
|
|
327
|
+
if (manualName && typeof manualName === 'string' && manualName.trim()) {
|
|
328
|
+
productName = manualName.trim();
|
|
329
|
+
console.log(`[barcode] Manual name provided for "${trimmedUpc}" → "${productName}"`);
|
|
330
|
+
}
|
|
331
|
+
// Level 1: UPCitemdb free database (no API key needed)
|
|
332
|
+
if (!productName) {
|
|
333
|
+
const upcProduct = await (0, upc_lookup_1.resolveUpc)(trimmedUpc);
|
|
334
|
+
if (upcProduct?.name) {
|
|
335
|
+
productName = upcProduct.name;
|
|
336
|
+
productCategory = upcProduct.category || undefined;
|
|
337
|
+
console.log(`[barcode] UPCitemdb resolved "${trimmedUpc}" → "${productName}"`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Level 2: Google search via Serper + AI extraction
|
|
341
|
+
if (!productName && serperKey && provider !== 'reffo' && aiApiKey) {
|
|
342
|
+
const searchResult = await (0, upc_lookup_1.resolveUpcViaSearch)(trimmedUpc, provider, aiApiKey, serperKey);
|
|
343
|
+
if (searchResult?.name) {
|
|
344
|
+
productName = searchResult.name;
|
|
345
|
+
productCategory = searchResult.category || undefined;
|
|
346
|
+
console.log(`[barcode] Serper+AI resolved "${trimmedUpc}" → "${productName}"`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Level 3: Direct AI identification (least reliable)
|
|
350
|
+
if (!productName && provider !== 'reffo' && aiApiKey) {
|
|
351
|
+
productName = await (0, upc_lookup_1.identifyUpcWithAI)(trimmedUpc, provider, aiApiKey);
|
|
352
|
+
if (productName) {
|
|
353
|
+
console.log(`[barcode] Direct AI resolved "${trimmedUpc}" → "${productName}"`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// Level 4: Reffo.ai fallback (has its own multi-stage pipeline)
|
|
357
|
+
if (!productName && reffoApiKey) {
|
|
358
|
+
const reffoResult = await (0, upc_lookup_1.resolveUpcViaReffo)(trimmedUpc, reffoApiKey, reffoUrl);
|
|
359
|
+
if (reffoResult?.name) {
|
|
360
|
+
productName = reffoResult.name;
|
|
361
|
+
productCategory = reffoResult.category || undefined;
|
|
362
|
+
console.log(`[barcode] Reffo.ai resolved "${trimmedUpc}" → "${productName}"`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// All stages failed
|
|
366
|
+
if (!productName) {
|
|
367
|
+
console.log(`[barcode] Could not resolve UPC "${trimmedUpc}"`);
|
|
368
|
+
return res.json({
|
|
369
|
+
name: null,
|
|
370
|
+
description: null,
|
|
371
|
+
sku: trimmedUpc,
|
|
372
|
+
product_url: null,
|
|
373
|
+
image_url: null,
|
|
374
|
+
attributes: {},
|
|
375
|
+
price_estimate: { low: 0, high: 0, typical: 0, confidence: 'low' },
|
|
376
|
+
cached: false,
|
|
377
|
+
unidentified: true,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
// 3. Enrich product data using the resolved name
|
|
319
381
|
try {
|
|
382
|
+
const attributeKeys = (0, ref_schemas_1.getAttributeKeys)(productCategory, undefined);
|
|
320
383
|
let result;
|
|
321
384
|
if (provider === 'reffo') {
|
|
322
|
-
|
|
323
|
-
if (!apiKey) {
|
|
385
|
+
if (!reffoApiKey) {
|
|
324
386
|
return res.status(400).json({
|
|
325
387
|
error: 'No AI provider configured. Connect to Reffo.ai or set up a direct AI provider in Settings.',
|
|
326
388
|
});
|
|
327
389
|
}
|
|
328
|
-
const reffoUrl = process.env.REFFO_API_URL || 'https://reffo.ai';
|
|
329
390
|
const upstream = await fetch(`${reffoUrl}/api/product-lookup`, {
|
|
330
391
|
method: 'POST',
|
|
331
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${
|
|
332
|
-
body: JSON.stringify({ name, category: '', subcategory: '' }),
|
|
392
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${reffoApiKey}` },
|
|
393
|
+
body: JSON.stringify({ name: productName, category: productCategory || '', subcategory: '' }),
|
|
333
394
|
});
|
|
334
395
|
if (!upstream.ok) {
|
|
335
396
|
const errData = await upstream.json().catch(() => ({}));
|
|
336
|
-
return res.status(upstream.status).json({ error: errData.error || 'Product
|
|
397
|
+
return res.status(upstream.status).json({ error: errData.error || 'Product enrichment failed' });
|
|
337
398
|
}
|
|
338
399
|
result = await upstream.json();
|
|
339
400
|
}
|
|
340
401
|
else {
|
|
341
|
-
const aiApiKey = process.env.AI_API_KEY;
|
|
342
402
|
if (!aiApiKey) {
|
|
343
403
|
return res.status(400).json({ error: `AI provider "${provider}" selected but no API key configured.` });
|
|
344
404
|
}
|
|
345
405
|
result = await (0, product_lookup_1.callProductLookup)(provider, aiApiKey, {
|
|
346
|
-
name, category: '', subcategory: '', attributeKeys,
|
|
406
|
+
name: productName, category: productCategory || '', subcategory: '', attributeKeys,
|
|
347
407
|
});
|
|
348
408
|
}
|
|
349
|
-
//
|
|
350
|
-
|
|
409
|
+
// Override SKU with the actual UPC barcode
|
|
410
|
+
result.sku = trimmedUpc;
|
|
411
|
+
// 4. Cache with SKU for fast future lookups
|
|
412
|
+
const nameNormalized = productName.toLowerCase().trim();
|
|
351
413
|
const pe = (result.price_estimate || {});
|
|
352
414
|
db.prepare(`INSERT INTO product_catalog (id, name_normalized, category, subcategory, description, sku, product_url, image_url, attributes, price_low, price_high, price_typical, price_confidence, ai_model, expires_at)
|
|
353
|
-
VALUES (?, ?,
|
|
415
|
+
VALUES (?, ?, ?, '', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now', '+30 days'))
|
|
354
416
|
ON CONFLICT(name_normalized, category, subcategory) DO UPDATE SET
|
|
355
417
|
description = excluded.description, sku = excluded.sku, product_url = excluded.product_url,
|
|
356
418
|
image_url = excluded.image_url, attributes = excluded.attributes,
|
|
357
419
|
price_low = excluded.price_low, price_high = excluded.price_high,
|
|
358
420
|
price_typical = excluded.price_typical, price_confidence = excluded.price_confidence,
|
|
359
421
|
ai_model = excluded.ai_model, lookup_count = product_catalog.lookup_count + 1,
|
|
360
|
-
updated_at = datetime('now'), expires_at = datetime('now', '+30 days')`).run(
|
|
361
|
-
return res.json({ ...result, cached: false });
|
|
422
|
+
updated_at = datetime('now'), expires_at = datetime('now', '+30 days')`).run((0, uuid_1.v4)(), nameNormalized, productCategory || '', result.description ?? null, trimmedUpc, result.product_url ?? null, result.image_url ?? null, JSON.stringify({ ...(result.attributes || {}), product_name: productName }), pe.low ?? null, pe.high ?? null, pe.typical ?? null, pe.confidence ?? 'low', provider);
|
|
423
|
+
return res.json({ ...result, name: productName, cached: false, unidentified: false });
|
|
362
424
|
}
|
|
363
425
|
catch (err) {
|
|
364
|
-
console.error('Barcode
|
|
426
|
+
console.error('Barcode enrichment error:', err);
|
|
365
427
|
return res.status(502).json({ error: 'Product lookup failed. Check your AI provider configuration.' });
|
|
366
428
|
}
|
|
367
429
|
});
|