tablefacts 0.1.0 → 0.3.0
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/.env.example +3 -1
- package/CHANGELOG.md +61 -0
- package/README.md +67 -19
- package/package.json +14 -3
- package/src/lib/types.mjs +16 -4
- package/src/menu/README.md +63 -15
- package/src/menu/cluvi/config.mjs +6 -0
- package/src/menu/cluvi/import.mjs +6 -2
- package/src/menu/lib/db.mjs +81 -17
- package/src/menu/lib/import.mjs +20 -3
- package/src/menu/lib/run.mjs +18 -0
- package/src/menu/lib/tables.mjs +44 -0
- package/src/menu/raw/config.mjs +12 -2
- package/src/menu/raw/extract.mjs +36 -15
- package/src/menu/raw/images.mjs +109 -0
- package/src/menu/raw/import.mjs +330 -51
- package/src/menu/raw/normalize.mjs +15 -4
- package/src/menu/raw/pdf.mjs +330 -0
- package/src/menu/raw/pdfjs.mjs +57 -0
- package/src/menu/raw/source.mjs +9 -2
- package/src/menu/raw/vision.mjs +124 -65
- package/src/research/README.md +23 -13
- package/src/research/index.mjs +52 -17
- package/src/research/lib/merge.mjs +11 -10
- package/src/research/lib/report.mjs +72 -19
- package/src/research/lib/search.mjs +127 -0
- package/src/research/lib/social.mjs +137 -26
- package/src/research/lib/util.mjs +22 -0
- package/src/research/lib/website.mjs +3 -14
- package/src/research/research.mjs +12 -8
- package/types/lib/types.d.mts +70 -6
- package/types/menu/cluvi/config.d.mts +1 -0
- package/types/menu/cluvi/import.d.mts +2 -0
- package/types/menu/lib/db.d.mts +31 -3
- package/types/menu/lib/import.d.mts +1 -1
- package/types/menu/lib/run.d.mts +6 -0
- package/types/menu/lib/tables.d.mts +18 -0
- package/types/menu/raw/config.d.mts +2 -0
- package/types/menu/raw/images.d.mts +27 -0
- package/types/menu/raw/import.d.mts +36 -7
- package/types/menu/raw/normalize.d.mts +7 -1
- package/types/menu/raw/pdf.d.mts +98 -0
- package/types/menu/raw/pdfjs.d.mts +12 -0
- package/types/menu/raw/source.d.mts +2 -0
- package/types/menu/raw/vision.d.mts +11 -1
- package/types/research/lib/merge.d.mts +4 -1
- package/types/research/lib/report.d.mts +14 -1
- package/types/research/lib/search.d.mts +58 -0
- package/types/research/lib/social.d.mts +32 -31
- package/types/research/lib/util.d.mts +5 -0
- package/types/research/lib/website.d.mts +20 -20
package/types/lib/types.d.mts
CHANGED
|
@@ -253,7 +253,19 @@ export type ImportOptions = {
|
|
|
253
253
|
*/
|
|
254
254
|
json?: string;
|
|
255
255
|
/**
|
|
256
|
-
*
|
|
256
|
+
* This restaurant's table prefix (e.g. "makibar_"); empty for the unprefixed menu_* tables. Default "": importCluvi/importImageMenu fall back to the source config's.
|
|
257
|
+
*/
|
|
258
|
+
tablePrefix?: string;
|
|
259
|
+
/**
|
|
260
|
+
* Write the unprefixed menu_* tables even when other restaurants' prefixed tables exist. Only for a single-restaurant database.
|
|
261
|
+
*/
|
|
262
|
+
allowUnprefixed?: boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Confirm a destructive `replaceAll`, which empties the target tables.
|
|
265
|
+
*/
|
|
266
|
+
yes?: boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Replace the whole menu, not only the categories in this import. Needs `yes`.
|
|
257
269
|
*/
|
|
258
270
|
replaceAll?: boolean;
|
|
259
271
|
/**
|
|
@@ -284,6 +296,7 @@ export type ImportResult = {
|
|
|
284
296
|
*/
|
|
285
297
|
database: {
|
|
286
298
|
label: string;
|
|
299
|
+
tables: string[];
|
|
287
300
|
current: {
|
|
288
301
|
categories: number;
|
|
289
302
|
products: number;
|
|
@@ -297,6 +310,10 @@ export type ImportMenuOptions = ImportOptions & {
|
|
|
297
310
|
title?: string;
|
|
298
311
|
};
|
|
299
312
|
export type CluviConfig = {
|
|
313
|
+
/**
|
|
314
|
+
* This restaurant's table prefix in a shared database (e.g. "cannario_"); empty for the unprefixed menu_* tables.
|
|
315
|
+
*/
|
|
316
|
+
tablePrefix?: string;
|
|
300
317
|
/**
|
|
301
318
|
* Any page of the restaurant's Cluvi menu.
|
|
302
319
|
*/
|
|
@@ -315,6 +332,10 @@ export type CluviConfig = {
|
|
|
315
332
|
sections?: Record<string, string>;
|
|
316
333
|
};
|
|
317
334
|
export type RawConfig = {
|
|
335
|
+
/**
|
|
336
|
+
* This restaurant's table prefix in a shared database (e.g. "mombasa_"); empty for the unprefixed menu_* tables.
|
|
337
|
+
*/
|
|
338
|
+
tablePrefix?: string;
|
|
318
339
|
/**
|
|
319
340
|
* Page with the menu pictures, or a direct image URL.
|
|
320
341
|
*/
|
|
@@ -335,6 +356,10 @@ export type RawConfig = {
|
|
|
335
356
|
* Multiplies every price. Default 1.
|
|
336
357
|
*/
|
|
337
358
|
scale?: number;
|
|
359
|
+
/**
|
|
360
|
+
* Resolution a PDF page is rendered at before its product photos are screenshot. Default 2.
|
|
361
|
+
*/
|
|
362
|
+
imageScale?: number;
|
|
338
363
|
/**
|
|
339
364
|
* Category that lists each group.
|
|
340
365
|
*/
|
|
@@ -372,7 +397,7 @@ export type CluviMenuOptions = {
|
|
|
372
397
|
export type ImportCluviOptions = ImportOptions & CluviMenuOptions;
|
|
373
398
|
export type ImageMenuReadOptions = {
|
|
374
399
|
/**
|
|
375
|
-
* Pages or image URLs. Default: the config's url.
|
|
400
|
+
* Pages or image URLs, or PDF file paths/URLs. Default: the config's url.
|
|
376
401
|
*/
|
|
377
402
|
urls?: string[];
|
|
378
403
|
/**
|
|
@@ -396,6 +421,18 @@ export type ImageMenuReadOptions = {
|
|
|
396
421
|
* Read the pages again instead of using the saved transcriptions.
|
|
397
422
|
*/
|
|
398
423
|
refresh?: boolean;
|
|
424
|
+
/**
|
|
425
|
+
* Also save the dish photos printed on a PDF page here (resolved against projectDir). Enables photo extraction.
|
|
426
|
+
*/
|
|
427
|
+
imageDir?: string;
|
|
428
|
+
/**
|
|
429
|
+
* https folder the saved photos will be published at; fills each product's image_url with it plus the file name.
|
|
430
|
+
*/
|
|
431
|
+
imageBaseUrl?: string;
|
|
432
|
+
/**
|
|
433
|
+
* How photos are found: 'auto' (default) reads a PDF page from its text and matches placed photos by position, using the model's boxes only when the page has none; 'always' reads every PDF page as a picture so the model boxes every dish's photo. Default 'auto'.
|
|
434
|
+
*/
|
|
435
|
+
imageBoxes?: 'auto' | 'always';
|
|
399
436
|
/**
|
|
400
437
|
* Default: the raw config.mjs.
|
|
401
438
|
*/
|
|
@@ -406,6 +443,10 @@ export type ListMenuImagesOptions = {
|
|
|
406
443
|
urls?: string[];
|
|
407
444
|
only?: string | number[];
|
|
408
445
|
minWidth?: number;
|
|
446
|
+
/**
|
|
447
|
+
* Project folder. Default: TABLEFACTS_PROJECT or the current folder.
|
|
448
|
+
*/
|
|
449
|
+
projectDir?: string;
|
|
409
450
|
config?: RawConfig;
|
|
410
451
|
};
|
|
411
452
|
export type MenuImage = {
|
|
@@ -413,8 +454,19 @@ export type MenuImage = {
|
|
|
413
454
|
* Position as the CLI's --list numbers it.
|
|
414
455
|
*/
|
|
415
456
|
number: number;
|
|
457
|
+
/**
|
|
458
|
+
* The image URL, or "<pdf path or URL>#<page number>" for a PDF page.
|
|
459
|
+
*/
|
|
416
460
|
url: string;
|
|
417
461
|
alt: string;
|
|
462
|
+
/**
|
|
463
|
+
* Where the page came from. Default: 'image'.
|
|
464
|
+
*/
|
|
465
|
+
kind?: 'image' | 'pdf';
|
|
466
|
+
/**
|
|
467
|
+
* Characters of text a PDF page has (0 means it is a scan).
|
|
468
|
+
*/
|
|
469
|
+
chars?: number;
|
|
418
470
|
};
|
|
419
471
|
export type PriceFormat = {
|
|
420
472
|
/**
|
|
@@ -574,7 +626,10 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
|
|
|
574
626
|
* @typedef {object} ImportOptions
|
|
575
627
|
* @property {boolean} [dryRun] Check and report, write nothing.
|
|
576
628
|
* @property {string} [json] Also save the extracted menu as JSON at this path (resolved against projectDir).
|
|
577
|
-
* @property {
|
|
629
|
+
* @property {string} [tablePrefix] This restaurant's table prefix (e.g. "makibar_"); empty for the unprefixed menu_* tables. Default "": importCluvi/importImageMenu fall back to the source config's.
|
|
630
|
+
* @property {boolean} [allowUnprefixed] Write the unprefixed menu_* tables even when other restaurants' prefixed tables exist. Only for a single-restaurant database.
|
|
631
|
+
* @property {boolean} [yes] Confirm a destructive `replaceAll`, which empties the target tables.
|
|
632
|
+
* @property {boolean} [replaceAll] Replace the whole menu, not only the categories in this import. Needs `yes`.
|
|
578
633
|
* @property {boolean} [force] Write even if the import has far fewer products than it replaces.
|
|
579
634
|
* @property {string} [databaseUrl] Default: env.SUPABASE_DB_URL.
|
|
580
635
|
* @property {Env} [env] Environment the database URL and keys are read from. Default process.env.
|
|
@@ -587,7 +642,7 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
|
|
|
587
642
|
* @property {string[]} notes
|
|
588
643
|
* @property {boolean} written
|
|
589
644
|
* @property {boolean} dryRun
|
|
590
|
-
* @property {{ label: string, current: { categories: number, products: number, kept: string[] } } | null} database Null when the database was not reached.
|
|
645
|
+
* @property {{ label: string, tables: string[], current: { categories: number, products: number, kept: string[] } } | null} database Null when the database was not reached.
|
|
591
646
|
*/
|
|
592
647
|
/**
|
|
593
648
|
* @typedef {ImportOptions & { menu: Menu, notes?: string[], title?: string }} ImportMenuOptions
|
|
@@ -595,6 +650,7 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
|
|
|
595
650
|
/**
|
|
596
651
|
* Restaurant-specific part of the Cluvi source (src/menu/cluvi/config.mjs).
|
|
597
652
|
* @typedef {object} CluviConfig
|
|
653
|
+
* @property {string} [tablePrefix] This restaurant's table prefix in a shared database (e.g. "cannario_"); empty for the unprefixed menu_* tables.
|
|
598
654
|
* @property {string} [url] Any page of the restaurant's Cluvi menu.
|
|
599
655
|
* @property {{ slug: string, name: string, from: string[] }[]} [categories] Cluvi main categories folded into each site category.
|
|
600
656
|
* @property {Record<string, string>} [sections] Cluvi subcategory to section name.
|
|
@@ -602,11 +658,13 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
|
|
|
602
658
|
/**
|
|
603
659
|
* Restaurant-specific part of the picture-menu source (src/menu/raw/config.mjs).
|
|
604
660
|
* @typedef {object} RawConfig
|
|
661
|
+
* @property {string} [tablePrefix] This restaurant's table prefix in a shared database (e.g. "mombasa_"); empty for the unprefixed menu_* tables.
|
|
605
662
|
* @property {string} [url] Page with the menu pictures, or a direct image URL.
|
|
606
663
|
* @property {string} currency ISO code of the prices.
|
|
607
664
|
* @property {string} [thousands] Thousands separator the menu prints. Default ".".
|
|
608
665
|
* @property {string} [decimal] Decimal separator the menu prints. Default ",".
|
|
609
666
|
* @property {number} [scale] Multiplies every price. Default 1.
|
|
667
|
+
* @property {number} [imageScale] Resolution a PDF page is rendered at before its product photos are screenshot. Default 2.
|
|
610
668
|
* @property {{ slug: string, name: string, groups?: ('food' | 'drink')[] }[]} [categories] Category that lists each group.
|
|
611
669
|
* @property {Record<string, string>} [placeIn] Section title to category slug.
|
|
612
670
|
* @property {Record<string, string>} [sections] Section title to the name stored.
|
|
@@ -623,13 +681,16 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
|
|
|
623
681
|
/** @typedef {ImportOptions & CluviMenuOptions} ImportCluviOptions */
|
|
624
682
|
/**
|
|
625
683
|
* @typedef {object} ImageMenuReadOptions
|
|
626
|
-
* @property {string[]} [urls] Pages or image URLs. Default: the config's url.
|
|
684
|
+
* @property {string[]} [urls] Pages or image URLs, or PDF file paths/URLs. Default: the config's url.
|
|
627
685
|
* @property {string | number[]} [only] Pages to read, e.g. '1,3-5' or [1, 3, 4, 5].
|
|
628
686
|
* @property {string} [provider] Vision provider, see `providers`. Default MENU_VISION_PROVIDER or `defaultProvider`.
|
|
629
687
|
* @property {string} [model]
|
|
630
688
|
* @property {number} [minWidth] Ignore images declaring a smaller width. Default 500.
|
|
631
689
|
* @property {string} [apiKey] Default: the provider's key in env.
|
|
632
690
|
* @property {boolean} [refresh] Read the pages again instead of using the saved transcriptions.
|
|
691
|
+
* @property {string} [imageDir] Also save the dish photos printed on a PDF page here (resolved against projectDir). Enables photo extraction.
|
|
692
|
+
* @property {string} [imageBaseUrl] https folder the saved photos will be published at; fills each product's image_url with it plus the file name.
|
|
693
|
+
* @property {'auto' | 'always'} [imageBoxes] How photos are found: 'auto' (default) reads a PDF page from its text and matches placed photos by position, using the model's boxes only when the page has none; 'always' reads every PDF page as a picture so the model boxes every dish's photo. Default 'auto'.
|
|
633
694
|
* @property {RawConfig} [config] Default: the raw config.mjs.
|
|
634
695
|
*/
|
|
635
696
|
/** @typedef {ImportOptions & ImageMenuReadOptions} ImportImageMenuOptions */
|
|
@@ -638,13 +699,16 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
|
|
|
638
699
|
* @property {string[]} [urls]
|
|
639
700
|
* @property {string | number[]} [only]
|
|
640
701
|
* @property {number} [minWidth]
|
|
702
|
+
* @property {string} [projectDir] Project folder. Default: TABLEFACTS_PROJECT or the current folder.
|
|
641
703
|
* @property {RawConfig} [config]
|
|
642
704
|
*/
|
|
643
705
|
/**
|
|
644
706
|
* @typedef {object} MenuImage
|
|
645
707
|
* @property {number} number Position as the CLI's --list numbers it.
|
|
646
|
-
* @property {string} url
|
|
708
|
+
* @property {string} url The image URL, or "<pdf path or URL>#<page number>" for a PDF page.
|
|
647
709
|
* @property {string} alt
|
|
710
|
+
* @property {'image' | 'pdf'} [kind] Where the page came from. Default: 'image'.
|
|
711
|
+
* @property {number} [chars] Characters of text a PDF page has (0 means it is a scan).
|
|
648
712
|
*/
|
|
649
713
|
/**
|
|
650
714
|
* @typedef {object} PriceFormat
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function fetchCluviMenu({ url, service, lang, config }?: {
|
|
6
6
|
config?: {
|
|
7
|
+
tablePrefix: string;
|
|
7
8
|
url: string;
|
|
8
9
|
categories: {
|
|
9
10
|
slug: string;
|
|
@@ -26,6 +27,7 @@ export declare function fetchCluviMenu({ url, service, lang, config }?: {
|
|
|
26
27
|
}[];
|
|
27
28
|
notes: string[];
|
|
28
29
|
title: string;
|
|
30
|
+
tablePrefix: string;
|
|
29
31
|
}>;
|
|
30
32
|
/**
|
|
31
33
|
* Reads the menu from Cluvi and imports it. Takes importMenu's options too.
|
package/types/menu/lib/db.d.mts
CHANGED
|
@@ -3,19 +3,47 @@ export declare function connect(url: any, { env }?: {}): Promise<{
|
|
|
3
3
|
client: any;
|
|
4
4
|
label: string;
|
|
5
5
|
}>;
|
|
6
|
+
/**
|
|
7
|
+
* Refuses to touch another site's tables before anything is written.
|
|
8
|
+
*
|
|
9
|
+
* Checks the three target tables exist (pointing at the migration when they do not). Then, when
|
|
10
|
+
* no prefix is set, lists the other restaurants' `<prefix>_menu_categories` tables: any of them
|
|
11
|
+
* means this database is shared, so the import stops unless the caller confirmed it targets the
|
|
12
|
+
* one unprefixed set with `allowUnprefixed`. A whole-menu `replaceAll` is refused in that case
|
|
13
|
+
* whatever the flags say, because deleting every unprefixed row could destroy another site.
|
|
14
|
+
* Only reads; returns `{ prefix, tables, others }`.
|
|
15
|
+
* @param {any} client
|
|
16
|
+
* @param {{ tablePrefix?: string, allowUnprefixed?: boolean, replaceAll?: boolean }} [options]
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertTarget(client: any, { tablePrefix, allowUnprefixed, replaceAll }?: {
|
|
19
|
+
tablePrefix?: string;
|
|
20
|
+
allowUnprefixed?: boolean;
|
|
21
|
+
replaceAll?: boolean;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
prefix: string;
|
|
24
|
+
tables: {
|
|
25
|
+
categories: string;
|
|
26
|
+
sections: string;
|
|
27
|
+
products: string;
|
|
28
|
+
};
|
|
29
|
+
others: any;
|
|
30
|
+
}>;
|
|
6
31
|
/** What an import would replace: the categories it writes, or the whole menu. */
|
|
7
|
-
export declare function inspect(client: any, menu: any, { replaceAll }?: {
|
|
32
|
+
export declare function inspect(client: any, menu: any, { replaceAll, tablePrefix }?: {
|
|
8
33
|
replaceAll?: boolean | undefined;
|
|
34
|
+
tablePrefix?: string | undefined;
|
|
9
35
|
}): Promise<any>;
|
|
10
36
|
/**
|
|
11
37
|
* Replaces the menu in one transaction, so readers see the old menu or the new
|
|
12
38
|
* one and a failure changes nothing. By default only the categories in `menu`
|
|
13
39
|
* are replaced (deleting a category cascades to its sections and products);
|
|
14
40
|
* `replaceAll` empties the menu first. Ids are generated here so the rows can
|
|
15
|
-
* be inserted in bulk, a column at a time.
|
|
41
|
+
* be inserted in bulk, a column at a time. `tablePrefix` is the restaurant's
|
|
42
|
+
* own table set; the delete is scoped to it, never to another site's tables.
|
|
16
43
|
*/
|
|
17
|
-
export declare function replaceMenu(client: any, menu: any, { replaceAll }?: {
|
|
44
|
+
export declare function replaceMenu(client: any, menu: any, { replaceAll, tablePrefix }?: {
|
|
18
45
|
replaceAll?: boolean | undefined;
|
|
46
|
+
tablePrefix?: string | undefined;
|
|
19
47
|
}): Promise<{
|
|
20
48
|
categories: any;
|
|
21
49
|
sections: any;
|
|
@@ -8,4 +8,4 @@ export declare function templateHints(menu: any, { projectDir }?: {}): Promise<s
|
|
|
8
8
|
* @param {import('../../lib/types.mjs').ImportMenuOptions} options
|
|
9
9
|
* @returns {Promise<import('../../lib/types.mjs').ImportResult>}
|
|
10
10
|
*/
|
|
11
|
-
export declare function importMenu({ menu, notes, title, dryRun, json, replaceAll, force, databaseUrl, env, projectDir, log: logOption, }?: import('../../lib/types.mjs').ImportMenuOptions): Promise<import('../../lib/types.mjs').ImportResult>;
|
|
11
|
+
export declare function importMenu({ menu, notes, title, dryRun, json, replaceAll, force, tablePrefix, allowUnprefixed, yes, databaseUrl, env, projectDir, log: logOption, }?: import('../../lib/types.mjs').ImportMenuOptions): Promise<import('../../lib/types.mjs').ImportResult>;
|
package/types/menu/lib/run.d.mts
CHANGED
|
@@ -4,6 +4,12 @@ export declare const menuFlags: {
|
|
|
4
4
|
provider: string;
|
|
5
5
|
model: string;
|
|
6
6
|
minWidth: string;
|
|
7
|
+
imageDir: string;
|
|
8
|
+
imageBaseUrl: string;
|
|
9
|
+
imageBoxes: string;
|
|
10
|
+
tablePrefix: string;
|
|
11
|
+
allowUnprefixed: string;
|
|
12
|
+
yes: string;
|
|
7
13
|
replaceAll: string;
|
|
8
14
|
force: string;
|
|
9
15
|
dryRun: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tablePrefix` when it is empty or a safe `<name>_` prefix (an ECONFIG error otherwise).
|
|
3
|
+
* @param {string} [tablePrefix]
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateTablePrefix(tablePrefix?: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* The three `public` menu tables for a prefix, e.g.
|
|
9
|
+
* `{ categories: "public.makibar_menu_categories", sections: "public.makibar_menu_sections", products: "public.makibar_menu_products" }`.
|
|
10
|
+
* The default (empty prefix) is the unprefixed `public.menu_*` set.
|
|
11
|
+
* @param {string} [tablePrefix]
|
|
12
|
+
* @returns {{ categories: string, sections: string, products: string }}
|
|
13
|
+
*/
|
|
14
|
+
export declare function menuTables(tablePrefix?: string): {
|
|
15
|
+
categories: string;
|
|
16
|
+
sections: string;
|
|
17
|
+
products: string;
|
|
18
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page's text item that best matches `name`: an exact match first, then a
|
|
3
|
+
* line that contains it (preferring the shortest such line). null when no line
|
|
4
|
+
* holds the name, which happens when a heading was drawn as an outline.
|
|
5
|
+
*/
|
|
6
|
+
export declare function findNameItem(items: any, name: any): any;
|
|
7
|
+
/**
|
|
8
|
+
* One crop per product, in page order. `pages` maps a page number to
|
|
9
|
+
* `{ width, items, placed, direct }` (see the file comment). A crop is used
|
|
10
|
+
* once, so a single photo between two items goes to the closer one. Returns
|
|
11
|
+
* `{ matches, notes }`; matches carry the placement and the crop it won.
|
|
12
|
+
* @param {{ page: number, name: string, box: number[] | null, products: any[] }[]} placements
|
|
13
|
+
* @param {Map<number, any>} pages
|
|
14
|
+
* @returns {{ matches: { placement: any, crop: any }[], notes: string[] }}
|
|
15
|
+
*/
|
|
16
|
+
export declare function matchPlacements(placements: {
|
|
17
|
+
page: number;
|
|
18
|
+
name: string;
|
|
19
|
+
box: number[] | null;
|
|
20
|
+
products: any[];
|
|
21
|
+
}[], pages: Map<number, any>): {
|
|
22
|
+
matches: {
|
|
23
|
+
placement: any;
|
|
24
|
+
crop: any;
|
|
25
|
+
}[];
|
|
26
|
+
notes: string[];
|
|
27
|
+
};
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Every page found, and the numbered ones `only` selects. A PDF argument
|
|
3
|
+
* contributes one page per PDF page; anything else is scanned for menu pictures.
|
|
4
|
+
* `close()` releases the open PDFs.
|
|
5
|
+
*/
|
|
6
|
+
export declare function findPages({ urls, only, minWidth, projectDir, config }?: {
|
|
3
7
|
config?: {
|
|
8
|
+
tablePrefix: string;
|
|
4
9
|
url: string;
|
|
5
10
|
currency: string;
|
|
6
11
|
thousands: string;
|
|
7
12
|
decimal: string;
|
|
8
13
|
scale: number;
|
|
14
|
+
imageScale: number;
|
|
9
15
|
categories: {
|
|
10
16
|
slug: string;
|
|
11
17
|
name: string;
|
|
@@ -20,23 +26,44 @@ export declare function findPages({ urls, only, minWidth, config }?: {
|
|
|
20
26
|
minWidth?: number | undefined;
|
|
21
27
|
urls?: never[] | undefined;
|
|
22
28
|
}): Promise<{
|
|
23
|
-
pages:
|
|
24
|
-
|
|
29
|
+
pages: {
|
|
30
|
+
kind: string;
|
|
31
|
+
source: any;
|
|
32
|
+
host: string;
|
|
33
|
+
label: string;
|
|
34
|
+
pageNumber: number;
|
|
35
|
+
url: string;
|
|
36
|
+
chars: number;
|
|
37
|
+
number: number;
|
|
38
|
+
}[];
|
|
39
|
+
chosen: {
|
|
40
|
+
kind: string;
|
|
41
|
+
source: any;
|
|
42
|
+
host: string;
|
|
43
|
+
label: string;
|
|
44
|
+
pageNumber: number;
|
|
45
|
+
url: string;
|
|
46
|
+
chars: number;
|
|
47
|
+
number: number;
|
|
48
|
+
}[];
|
|
49
|
+
close: () => Promise<void>;
|
|
25
50
|
}>;
|
|
26
51
|
/**
|
|
27
|
-
* The
|
|
52
|
+
* The pages `--list` prints, numbered, including PDF pages with their text size.
|
|
28
53
|
* @param {import('../../lib/types.mjs').ListMenuImagesOptions} [options]
|
|
29
54
|
* @returns {Promise<import('../../lib/types.mjs').MenuImage[]>}
|
|
30
55
|
*/
|
|
31
56
|
export declare function listMenuImages(options?: import('../../lib/types.mjs').ListMenuImagesOptions): Promise<import('../../lib/types.mjs').MenuImage[]>;
|
|
32
57
|
/** Reads the pages and normalizes them: `{ menu, notes, title }`, ready for importMenu. */
|
|
33
|
-
export declare function fetchImageMenu({ urls, only, provider, model, minWidth, refresh, apiKey, env, projectDir, config, log: logOption }?: {
|
|
58
|
+
export declare function fetchImageMenu({ urls, only, provider, model, minWidth, refresh, apiKey, env, projectDir, config, imageDir, imageBaseUrl, imageBoxes, log: logOption }?: {
|
|
34
59
|
config?: {
|
|
60
|
+
tablePrefix: string;
|
|
35
61
|
url: string;
|
|
36
62
|
currency: string;
|
|
37
63
|
thousands: string;
|
|
38
64
|
decimal: string;
|
|
39
65
|
scale: number;
|
|
66
|
+
imageScale: number;
|
|
40
67
|
categories: {
|
|
41
68
|
slug: string;
|
|
42
69
|
name: string;
|
|
@@ -48,6 +75,7 @@ export declare function fetchImageMenu({ urls, only, provider, model, minWidth,
|
|
|
48
75
|
};
|
|
49
76
|
skipSections: never[];
|
|
50
77
|
} | undefined;
|
|
78
|
+
imageBoxes?: string | undefined;
|
|
51
79
|
minWidth?: number | undefined;
|
|
52
80
|
refresh?: boolean | undefined;
|
|
53
81
|
urls?: never[] | undefined;
|
|
@@ -55,9 +83,10 @@ export declare function fetchImageMenu({ urls, only, provider, model, minWidth,
|
|
|
55
83
|
menu: import("../../lib/types.mjs").Menu;
|
|
56
84
|
notes: string[];
|
|
57
85
|
title: string;
|
|
86
|
+
tablePrefix: string;
|
|
58
87
|
}>;
|
|
59
88
|
/**
|
|
60
|
-
* Reads the menu from pictures
|
|
89
|
+
* Reads the menu from pictures or a PDF and imports it. Takes importMenu's options too.
|
|
61
90
|
* @param {import('../../lib/types.mjs').ImportImageMenuOptions} [options]
|
|
62
91
|
* @returns {Promise<import('../../lib/types.mjs').ImportResult>}
|
|
63
92
|
*/
|
|
@@ -16,7 +16,7 @@ export declare function parsePrice(text: string, { thousands, decimal, scale }?:
|
|
|
16
16
|
* product per column, "Name (Botella)", because a product has one price.
|
|
17
17
|
* @param {{ number?: number, notes?: string[], sections?: any[] }[]} pages transcriptions, one per page
|
|
18
18
|
* @param {import('../../lib/types.mjs').RawConfig} config
|
|
19
|
-
* @returns {{ menu: import('../../lib/types.mjs').Menu, notes: string[], currency: string }}
|
|
19
|
+
* @returns {{ menu: import('../../lib/types.mjs').Menu, notes: string[], currency: string, placements: { page: number, name: string, box: number[] | null, products: import('../../lib/types.mjs').MenuProduct[] }[] }}
|
|
20
20
|
*/
|
|
21
21
|
export declare function normalizePages(pages: {
|
|
22
22
|
number?: number;
|
|
@@ -26,4 +26,10 @@ export declare function normalizePages(pages: {
|
|
|
26
26
|
menu: import('../../lib/types.mjs').Menu;
|
|
27
27
|
notes: string[];
|
|
28
28
|
currency: string;
|
|
29
|
+
placements: {
|
|
30
|
+
page: number;
|
|
31
|
+
name: string;
|
|
32
|
+
box: number[] | null;
|
|
33
|
+
products: import('../../lib/types.mjs').MenuProduct[];
|
|
34
|
+
}[];
|
|
29
35
|
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when an argument names a PDF: a local file ending in .pdf, or a URL whose
|
|
3
|
+
* path ends in .pdf. A URL that hides the extension comes back from the fetch as
|
|
4
|
+
* an HTML page with no pictures, and the reader says to pass the file instead.
|
|
5
|
+
* @param {string} input
|
|
6
|
+
* @returns {boolean}
|
|
7
|
+
*/
|
|
8
|
+
export declare function isPdfInput(input: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The PDF's bytes, from a local file (`projectDir`-relative) or an http(s) URL.
|
|
11
|
+
* `host` names the cache folder, `label` is what a note calls the file.
|
|
12
|
+
* @param {string} input
|
|
13
|
+
* @param {{ projectDir?: string }} [options]
|
|
14
|
+
* @returns {Promise<{ bytes: Uint8Array, host: string, label: string }>}
|
|
15
|
+
*/
|
|
16
|
+
export declare function readPdfSource(input: string, { projectDir }?: {
|
|
17
|
+
projectDir?: string;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
bytes: Uint8Array;
|
|
20
|
+
host: string;
|
|
21
|
+
label: string;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Opens a PDF for reading. `close()` releases pdfjs's worker; call it when the
|
|
25
|
+
* run is done with the file. `textCache` keeps a page's text from being read twice.
|
|
26
|
+
* @param {Uint8Array} bytes
|
|
27
|
+
* @returns {Promise<any>} the open document handle
|
|
28
|
+
*/
|
|
29
|
+
export declare function openPdf(bytes: Uint8Array): Promise<any>;
|
|
30
|
+
/**
|
|
31
|
+
* One page's text and its pieces. `items` are pdfjs's text items, kept for
|
|
32
|
+
* matching a product name to a spot on the page when placing product photos.
|
|
33
|
+
* @returns {Promise<{ page: any, text: string, items: any[] }>}
|
|
34
|
+
*/
|
|
35
|
+
export declare function readPdfText(source: any, number: any): Promise<{
|
|
36
|
+
page: any;
|
|
37
|
+
text: string;
|
|
38
|
+
items: any[];
|
|
39
|
+
}>;
|
|
40
|
+
/** Every page that has text worth transcribing, and how much. Used by `--list`. */
|
|
41
|
+
export declare function inspectPdf(source: any): Promise<{
|
|
42
|
+
number: number;
|
|
43
|
+
hasText: boolean;
|
|
44
|
+
chars: number;
|
|
45
|
+
}[]>;
|
|
46
|
+
/**
|
|
47
|
+
* The text items flattened to `{ str, x, y, width, height }`, which both the
|
|
48
|
+
* text builder and the product-photo matcher understand.
|
|
49
|
+
*/
|
|
50
|
+
export declare const positionedItems: (items: any) => any;
|
|
51
|
+
/**
|
|
52
|
+
* Rebuilds readable text from pdfjs text items: pieces on the same line are
|
|
53
|
+
* joined in reading order, a wide vertical gap becomes a blank line so the model
|
|
54
|
+
* still sees section breaks. A page with two clear columns is read column by
|
|
55
|
+
* column, so the two columns' rows are not merged. Pure, so it is tested without
|
|
56
|
+
* a PDF.
|
|
57
|
+
* @param {{ str?: string, transform?: number[], width?: number, height?: number }[]} items
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
export declare function textFromItems(items: {
|
|
61
|
+
str?: string;
|
|
62
|
+
transform?: number[];
|
|
63
|
+
width?: number;
|
|
64
|
+
height?: number;
|
|
65
|
+
}[]): string;
|
|
66
|
+
/** A page's rendered pixels, the context, and the viewport they were rendered with. */
|
|
67
|
+
export declare function renderPdfPage(source: any, page: any, { scale, recordImages }?: {
|
|
68
|
+
recordImages?: boolean | undefined;
|
|
69
|
+
scale?: number | undefined;
|
|
70
|
+
}): Promise<{
|
|
71
|
+
canvas: any;
|
|
72
|
+
context: any;
|
|
73
|
+
viewport: any;
|
|
74
|
+
scale: number;
|
|
75
|
+
}>;
|
|
76
|
+
/** Releases a rendered canvas's native memory. */
|
|
77
|
+
export declare function destroyPage(source: any, { canvas, context }?: {}): void;
|
|
78
|
+
/**
|
|
79
|
+
* The rectangle each image occupies on a page render. pdfjs records this itself
|
|
80
|
+
* when rendering with `recordImages` (three points per image, fractions of the
|
|
81
|
+
* canvas, top-left origin): it is clip- and group-aware, unlike walking the
|
|
82
|
+
* operator list by hand.
|
|
83
|
+
*/
|
|
84
|
+
export declare function imageRects(canvas: any, coordinates: any): {
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
}[];
|
|
90
|
+
/** Crops a pixel rectangle out of a page render, as a PNG buffer. */
|
|
91
|
+
export declare function cropPdfPixels(source: any, canvas: any, { x, y, width, height }: {
|
|
92
|
+
height: any;
|
|
93
|
+
width: any;
|
|
94
|
+
x: any;
|
|
95
|
+
y: any;
|
|
96
|
+
}, { padding }?: {
|
|
97
|
+
padding?: number | undefined;
|
|
98
|
+
}): any;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pdfjs module plus the asset URLs it needs (standard fonts and CMaps) so
|
|
3
|
+
* text is extracted and pages render without the warnings Node otherwise logs.
|
|
4
|
+
* @returns {Promise<{ pdfjs: any, standardFontDataUrl: string, cMapUrl: string }>}
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadPdfjs(): Promise<{
|
|
7
|
+
pdfjs: any;
|
|
8
|
+
standardFontDataUrl: string;
|
|
9
|
+
cMapUrl: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** A rendering failure caused by the canvas package being absent becomes EDEPENDENCY with the install hint. */
|
|
12
|
+
export declare function renderError(error: any): any;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** A fetch with retries on server errors; shared with the PDF reader. */
|
|
2
|
+
export declare function get(url: any, what: any): Promise<Response>;
|
|
1
3
|
/**
|
|
2
4
|
* The picture URLs of a page, in document order. Lazy-loaders keep the real
|
|
3
5
|
* address in data-orig-src / data-src and put a placeholder in src, so those
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
/** @type {Record<string, import('../../lib/types.mjs').VisionProvider>} */
|
|
2
2
|
export declare const providers: Record<string, import('../../lib/types.mjs').VisionProvider>;
|
|
3
3
|
export declare const defaultProvider = "anthropic";
|
|
4
|
-
/** `file` is a downloaded picture; returns `{ sections, notes }` as the schema above describes. */
|
|
4
|
+
/** `file` is a downloaded picture; returns `{ sections, notes }` as the schema above describes. `boxes` also asks for each item's printed photo rectangle. */
|
|
5
5
|
export declare function readPage({ file, mediaType }: {
|
|
6
6
|
file: any;
|
|
7
7
|
mediaType: any;
|
|
8
|
+
}, { provider, model, apiKey, env, boxes }?: {
|
|
9
|
+
boxes?: boolean | undefined;
|
|
10
|
+
provider?: string | undefined;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
sections: any;
|
|
13
|
+
notes: any;
|
|
14
|
+
}>;
|
|
15
|
+
/** `text` is one page's extracted text; returns the same `{ sections, notes }` the picture path does. */
|
|
16
|
+
export declare function readText({ text }: {
|
|
17
|
+
text: any;
|
|
8
18
|
}, { provider, model, apiKey, env }?: {
|
|
9
19
|
provider?: string | undefined;
|
|
10
20
|
}): Promise<{
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export declare function buildProfile({ query, google, osm, site, hub, instagram, tripadvisor }: {
|
|
1
|
+
export declare function buildProfile({ query, google, osm, site, hub, instagram, tripadvisor, search }: {
|
|
2
2
|
google: any;
|
|
3
3
|
hub: any;
|
|
4
4
|
instagram: any;
|
|
5
5
|
osm: any;
|
|
6
6
|
query: any;
|
|
7
|
+
search: any;
|
|
7
8
|
site: any;
|
|
8
9
|
tripadvisor: any;
|
|
9
10
|
}): {
|
|
@@ -82,6 +83,8 @@ export declare function buildProfile({ query, google, osm, site, hub, instagram,
|
|
|
82
83
|
followers: any;
|
|
83
84
|
posts: any;
|
|
84
85
|
blocked: any;
|
|
86
|
+
partial: any;
|
|
87
|
+
surface: any;
|
|
85
88
|
} | null;
|
|
86
89
|
};
|
|
87
90
|
};
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Splits the fields into what was discovered from a source, what was only echoed
|
|
3
|
+
* back from the user's own input, and low-confidence guesses, so a summary can
|
|
4
|
+
* say "N facts, M from your input" instead of counting all of them as found.
|
|
5
|
+
*/
|
|
6
|
+
export declare function fieldSummary(p: any): {
|
|
7
|
+
discovered: string[];
|
|
8
|
+
echoed: string[];
|
|
9
|
+
guessed: string[];
|
|
10
|
+
};
|
|
11
|
+
/** The CLI summary: what came from a source, what was only echoed, what was a low guess. */
|
|
12
|
+
export declare function summaryLines(p: any): string[];
|
|
13
|
+
export declare function renderReport(p: any, { notes, photos, kept }: {
|
|
14
|
+
kept?: never[] | undefined;
|
|
2
15
|
notes: any;
|
|
3
16
|
photos: any;
|
|
4
17
|
}): string;
|