@flowselections/mailbox-orders 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist-lib/components/MailboxAuthBanner.d.ts +16 -0
  2. package/dist-lib/components/MailboxAuthBanner.d.ts.map +1 -0
  3. package/dist-lib/components/MailboxAuthBanner.js +14 -0
  4. package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
  5. package/dist-lib/components/MailboxProcessedPage.js +10 -2
  6. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  7. package/dist-lib/components/MailboxProposalsPage.js +425 -104
  8. package/dist-lib/components/SearchableSelect.d.ts +1 -0
  9. package/dist-lib/components/SearchableSelect.d.ts.map +1 -1
  10. package/dist-lib/components/SearchableSelect.js +2 -1
  11. package/dist-lib/components/settings/MailboxAiPromptCard.d.ts +2 -0
  12. package/dist-lib/components/settings/MailboxAiPromptCard.d.ts.map +1 -0
  13. package/dist-lib/components/settings/MailboxAiPromptCard.js +39 -0
  14. package/dist-lib/index.d.ts.map +1 -1
  15. package/dist-lib/index.js +2 -0
  16. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
  17. package/dist-lib/integrations/supabase/auth-attacher.js +5 -1
  18. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2 -11202
  19. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  20. package/dist-lib/integrations/supabase/auth-middleware.js +8 -7
  21. package/dist-lib/integrations/supabase/client.d.ts +16514 -7163
  22. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  23. package/dist-lib/integrations/supabase/client.server.d.ts +16516 -7164
  24. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  25. package/dist-lib/integrations/supabase/types.d.ts +16768 -7202
  26. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  27. package/dist-lib/integrations/supabase/types.js +59 -8
  28. package/dist-lib/lib/ai-config.server.d.ts +46 -0
  29. package/dist-lib/lib/ai-config.server.d.ts.map +1 -0
  30. package/dist-lib/lib/ai-config.server.js +99 -0
  31. package/dist-lib/lib/delivery-date.d.ts +37 -0
  32. package/dist-lib/lib/delivery-date.d.ts.map +1 -0
  33. package/dist-lib/lib/delivery-date.js +96 -0
  34. package/dist-lib/lib/load-carriers.d.ts +26 -0
  35. package/dist-lib/lib/load-carriers.d.ts.map +1 -0
  36. package/dist-lib/lib/load-carriers.js +73 -0
  37. package/dist-lib/lib/mailbox-ai-prompt.d.ts +9 -0
  38. package/dist-lib/lib/mailbox-ai-prompt.d.ts.map +1 -0
  39. package/dist-lib/lib/mailbox-ai-prompt.js +30 -0
  40. package/dist-lib/lib/mailbox-ai-settings.functions.d.ts +23 -0
  41. package/dist-lib/lib/mailbox-ai-settings.functions.d.ts.map +1 -0
  42. package/dist-lib/lib/mailbox-ai-settings.functions.js +58 -0
  43. package/dist-lib/lib/mailbox-ai.server.d.ts +49 -4
  44. package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
  45. package/dist-lib/lib/mailbox-ai.server.js +284 -6
  46. package/dist-lib/lib/mailbox-body.d.ts +7 -0
  47. package/dist-lib/lib/mailbox-body.d.ts.map +1 -0
  48. package/dist-lib/lib/mailbox-body.js +38 -0
  49. package/dist-lib/lib/mailbox-ingest.server.d.ts +76 -0
  50. package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -0
  51. package/dist-lib/lib/mailbox-ingest.server.js +863 -0
  52. package/dist-lib/lib/mailbox-template.functions.d.ts +7 -67213
  53. package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -1
  54. package/dist-lib/lib/mailbox.functions.d.ts +107 -190506
  55. package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
  56. package/dist-lib/lib/mailbox.functions.js +544 -270
  57. package/package.json +4 -3
@@ -1,3 +1,9 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ import type { Database } from "../integrations/supabase/types";
3
+ import { DEFAULT_MAILBOX_SYSTEM_PROMPT } from "./mailbox-ai-prompt";
4
+ export { DEFAULT_MAILBOX_SYSTEM_PROMPT };
5
+ type AdminClient = SupabaseClient<Database>;
6
+ export declare const MAILBOX_AI_MODULE_KEY = "mailbox-bestellingen";
1
7
  export interface TemplateFieldSpec {
2
8
  key: string;
3
9
  label: string;
@@ -19,14 +25,53 @@ export interface ParsedOrder {
19
25
  lines: Array<{
20
26
  raw_product_text: string;
21
27
  product_id: string | null;
28
+ plant_type: string | null;
22
29
  quantity: number | null;
23
30
  unit: string | null;
24
31
  match_confidence: number;
25
32
  }>;
33
+ validation: {
34
+ missing: string[];
35
+ uncertain: string[];
36
+ notes: string | null;
37
+ };
26
38
  overall_confidence: number;
27
39
  }
28
- export declare function parseOrderEmail(_bodyText: string, _subject: string, _templateFields: TemplateFieldSpec[], _productCatalog: Array<{
29
- id: string;
30
- name: string;
31
- }>): Promise<ParsedOrder>;
40
+ export interface AiParseMeta {
41
+ providerSlug: string;
42
+ modelSlug: string;
43
+ modelName: string;
44
+ durationMs: number;
45
+ fingerprint: string;
46
+ }
47
+ /** Huidige vingerafdruk van de gekoppelde AI-agent, of null als AI niet beschikbaar is. */
48
+ export declare function currentAiFingerprint(admin: AdminClient): Promise<string | null>;
49
+ /** Concrete ankerdatums zodat het model relatieve aanduidingen niet hoeft te rekenen. */
50
+ export declare function buildDateContext(now?: Date): {
51
+ today: string;
52
+ today_weekday: string;
53
+ tomorrow: string;
54
+ day_after_tomorrow: string;
55
+ aanstaande: Record<string, string>;
56
+ volgende_week: Record<string, string>;
57
+ };
58
+ export declare function parseOrderEmail(admin: AdminClient, args: {
59
+ bodyText: string;
60
+ subject: string;
61
+ templateFields: TemplateFieldSpec[];
62
+ productCatalog: Array<{
63
+ id: string;
64
+ name: string;
65
+ }>;
66
+ deliveryLocations?: Array<{
67
+ id: string;
68
+ name: string;
69
+ }>;
70
+ plantTypes?: string[];
71
+ requiredFields?: string[];
72
+ systemPrompt?: string | null;
73
+ }): Promise<{
74
+ parsed: ParsedOrder;
75
+ meta: AiParseMeta;
76
+ }>;
32
77
  //# sourceMappingURL=mailbox-ai.server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mailbox-ai.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai.server.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACzC,KAAK,EAAE,KAAK,CAAC;QACX,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;IACH,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,iBAAiB,EAAE,EACpC,eAAe,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GACnD,OAAO,CAAC,WAAW,CAAC,CAItB"}
1
+ {"version":3,"file":"mailbox-ai.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai.server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAE9D,OAAO,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAAE,6BAA6B,EAAE,CAAC;AAEzC,KAAK,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;AAE5C,eAAO,MAAM,qBAAqB,yBAAyB,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACzC,KAAK,EAAE,KAAK,CAAC;QACX,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;IACH,UAAU,EAAE;QACV,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC;IACF,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,2FAA2F;AAC3F,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGrF;AAgJD,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,GAAG,OAAa;;;;;;;EA4BhD;AA0FD,wBAAsB,eAAe,CACnC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,iBAAiB,EAAE,CAAC;IACpC,cAAc,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,iBAAiB,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GACA,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAqDrD"}
@@ -1,7 +1,285 @@
1
- // AI-parsing is uitgeschakeld. We stappen over op een extern model dat later
2
- // gekoppeld wordt. Dit bestand bewaart alleen de types zodat de rest van de
3
- // module blijft compileren; elke aanroep van parseOrderEmail gooit direct een
4
- // fout zodat er geen tokens bij Lovable AI verbruikt worden.
5
- export async function parseOrderEmail(_bodyText, _subject, _templateFields, _productCatalog) {
6
- throw new Error("AI-parsing is uitgeschakeld — wacht op de externe model-koppeling.");
1
+ // AI-parsing van bestelmails. De provider, het model en de API-sleutel komen
2
+ // uit de centrale AI-Authenticatie module (zie ai-config.server.ts).
3
+ // Er wordt GEEN gebruik gemaakt van Lovable AI / de Lovable AI Gateway.
4
+ import { resolveModuleAi, logAiCall } from "./ai-config.server";
5
+ import { DEFAULT_MAILBOX_SYSTEM_PROMPT } from "./mailbox-ai-prompt";
6
+ export { DEFAULT_MAILBOX_SYSTEM_PROMPT };
7
+ export const MAILBOX_AI_MODULE_KEY = "mailbox-bestellingen";
8
+ /** Huidige vingerafdruk van de gekoppelde AI-agent, of null als AI niet beschikbaar is. */
9
+ export async function currentAiFingerprint(admin) {
10
+ const res = await resolveModuleAi(admin, MAILBOX_AI_MODULE_KEY);
11
+ return res.ok ? res.config.fingerprint : null;
12
+ }
13
+ async function callGemini(cfg, system, user) {
14
+ const url = `${cfg.baseUrl}/v1beta/models/${encodeURIComponent(cfg.modelSlug)}:generateContent?key=${encodeURIComponent(cfg.apiKey)}`;
15
+ const res = await fetch(url, {
16
+ method: "POST",
17
+ headers: { "Content-Type": "application/json" },
18
+ body: JSON.stringify({
19
+ systemInstruction: { parts: [{ text: system }] },
20
+ contents: [{ role: "user", parts: [{ text: user }] }],
21
+ generationConfig: { temperature: 0, responseMimeType: "application/json" },
22
+ }),
23
+ });
24
+ const body = await res.text();
25
+ if (!res.ok)
26
+ throw new Error(`Gemini ${res.status}: ${body.slice(0, 400)}`);
27
+ const json = JSON.parse(body);
28
+ const text = (json?.candidates?.[0]?.content?.parts ?? [])
29
+ .map((p) => (typeof p?.text === "string" ? p.text : ""))
30
+ .join("") || "";
31
+ return {
32
+ text,
33
+ inputTokens: json?.usageMetadata?.promptTokenCount ?? null,
34
+ outputTokens: json?.usageMetadata?.candidatesTokenCount ?? null,
35
+ };
36
+ }
37
+ async function callOpenAi(cfg, system, user) {
38
+ const res = await fetch(`${cfg.baseUrl}/chat/completions`, {
39
+ method: "POST",
40
+ headers: {
41
+ "Content-Type": "application/json",
42
+ Authorization: `Bearer ${cfg.apiKey}`,
43
+ },
44
+ body: JSON.stringify({
45
+ model: cfg.modelSlug,
46
+ messages: [
47
+ { role: "system", content: system },
48
+ { role: "user", content: user },
49
+ ],
50
+ response_format: { type: "json_object" },
51
+ }),
52
+ });
53
+ const body = await res.text();
54
+ if (!res.ok)
55
+ throw new Error(`OpenAI ${res.status}: ${body.slice(0, 400)}`);
56
+ const json = JSON.parse(body);
57
+ return {
58
+ text: json?.choices?.[0]?.message?.content ?? "",
59
+ inputTokens: json?.usage?.prompt_tokens ?? null,
60
+ outputTokens: json?.usage?.completion_tokens ?? null,
61
+ };
62
+ }
63
+ async function callAnthropic(cfg, system, user) {
64
+ const res = await fetch(`${cfg.baseUrl}/v1/messages`, {
65
+ method: "POST",
66
+ headers: {
67
+ "Content-Type": "application/json",
68
+ "x-api-key": cfg.apiKey,
69
+ "anthropic-version": "2023-06-01",
70
+ },
71
+ body: JSON.stringify({
72
+ model: cfg.modelSlug,
73
+ max_tokens: 4096,
74
+ system,
75
+ messages: [{ role: "user", content: user }],
76
+ }),
77
+ });
78
+ const body = await res.text();
79
+ if (!res.ok)
80
+ throw new Error(`Anthropic ${res.status}: ${body.slice(0, 400)}`);
81
+ const json = JSON.parse(body);
82
+ const text = (json?.content ?? [])
83
+ .map((c) => (typeof c?.text === "string" ? c.text : ""))
84
+ .join("");
85
+ return {
86
+ text,
87
+ inputTokens: json?.usage?.input_tokens ?? null,
88
+ outputTokens: json?.usage?.output_tokens ?? null,
89
+ };
90
+ }
91
+ async function callProvider(cfg, system, user) {
92
+ switch (cfg.providerSlug) {
93
+ case "gemini":
94
+ case "google":
95
+ return callGemini(cfg, system, user);
96
+ case "openai":
97
+ return callOpenAi(cfg, system, user);
98
+ case "anthropic":
99
+ return callAnthropic(cfg, system, user);
100
+ default:
101
+ throw new Error(`Provider '${cfg.providerSlug}' wordt nog niet ondersteund.`);
102
+ }
103
+ }
104
+ // ---------------------------------------------------------------------------
105
+ // Prompt + parsing
106
+ // ---------------------------------------------------------------------------
107
+ function extractJson(text) {
108
+ const trimmed = (text ?? "").trim().replace(/^```(?:json)?/i, "").replace(/```$/, "").trim();
109
+ try {
110
+ return JSON.parse(trimmed);
111
+ }
112
+ catch {
113
+ const start = trimmed.indexOf("{");
114
+ const end = trimmed.lastIndexOf("}");
115
+ if (start >= 0 && end > start)
116
+ return JSON.parse(trimmed.slice(start, end + 1));
117
+ throw new Error("AI gaf geen geldige JSON terug.");
118
+ }
119
+ }
120
+ function num(v) {
121
+ const n = typeof v === "string" ? Number(v.replace(",", ".")) : v;
122
+ return typeof n === "number" && Number.isFinite(n) ? n : null;
123
+ }
124
+ function clamp01(v) {
125
+ const n = num(v);
126
+ if (n === null)
127
+ return 0.5;
128
+ return Math.min(1, Math.max(0, n));
129
+ }
130
+ const OUTPUT_CONTRACT = [
131
+ "Antwoord uitsluitend met JSON, zonder uitleg en zonder markdown.",
132
+ "Structuur:",
133
+ '{"fields":{"<key>":{"value":<any|null>,"confidence":0..1,"needs_review":true|false}},',
134
+ '"lines":[{"raw_product_text":string,"product_id":string|null,"plant_type":string|null,"quantity":number|null,"unit":"fust"|"planten"|null,"match_confidence":0..1}],',
135
+ '"validation":{"missing":[string],"uncertain":[string],"notes":string|null},',
136
+ '"overall_confidence":0..1}',
137
+ "Datums altijd als YYYY-MM-DD.",
138
+ ].join("\n");
139
+ const DAY_NAMES = ["zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"];
140
+ /** Concrete ankerdatums zodat het model relatieve aanduidingen niet hoeft te rekenen. */
141
+ export function buildDateContext(now = new Date()) {
142
+ const iso = (d) => d.toISOString().slice(0, 10);
143
+ const add = (days) => {
144
+ const d = new Date(now.getTime());
145
+ d.setUTCDate(d.getUTCDate() + days);
146
+ return d;
147
+ };
148
+ const dow = now.getUTCDay();
149
+ const nextWeekday = {};
150
+ for (let i = 1; i <= 7; i++) {
151
+ const d = add(i);
152
+ const name = DAY_NAMES[d.getUTCDay()];
153
+ if (!nextWeekday[name])
154
+ nextWeekday[name] = iso(d);
155
+ }
156
+ const weekAfter = {};
157
+ for (let i = 8; i <= 14; i++) {
158
+ const d = add(i);
159
+ const name = DAY_NAMES[d.getUTCDay()];
160
+ if (!weekAfter[name])
161
+ weekAfter[name] = iso(d);
162
+ }
163
+ return {
164
+ today: iso(now),
165
+ today_weekday: DAY_NAMES[dow],
166
+ tomorrow: iso(add(1)),
167
+ day_after_tomorrow: iso(add(2)),
168
+ aanstaande: nextWeekday,
169
+ volgende_week: weekAfter,
170
+ };
171
+ }
172
+ function buildPrompt(bodyText, subject, templateFields, productCatalog, extra) {
173
+ const fieldSpec = templateFields.map((f) => ({
174
+ key: f.key,
175
+ label: f.label,
176
+ type: f.field_type,
177
+ hint: f.ai_hint ?? undefined,
178
+ allowed: f.allowed?.slice(0, 250),
179
+ }));
180
+ const base = (extra.systemPrompt ?? "").trim() || DEFAULT_MAILBOX_SYSTEM_PROMPT;
181
+ const system = `${base}\n\n${OUTPUT_CONTRACT}`;
182
+ const user = JSON.stringify({
183
+ email: { subject, body: (bodyText ?? "").slice(0, 20000) },
184
+ date_context: buildDateContext(extra.now ?? new Date()),
185
+ required_fields: extra.requiredFields ?? [],
186
+ fields: fieldSpec,
187
+ delivery_locations: extra.deliveryLocations?.slice(0, 100) ?? [],
188
+ plant_types: (extra.plantTypes ?? []).slice(0, 400),
189
+ product_catalog: productCatalog.slice(0, 250),
190
+ });
191
+ return { system, user };
192
+ }
193
+ function strList(v) {
194
+ return Array.isArray(v)
195
+ ? v.map((x) => (typeof x === "string" ? x.slice(0, 120) : "")).filter(Boolean).slice(0, 50)
196
+ : [];
197
+ }
198
+ function normalize(raw) {
199
+ const fields = {};
200
+ const rawFields = raw?.fields && typeof raw.fields === "object" ? raw.fields : {};
201
+ for (const [k, v] of Object.entries(rawFields)) {
202
+ const value = v && typeof v === "object" && "value" in v ? v.value : v;
203
+ const confidence = v && typeof v === "object" ? clamp01(v.confidence) : 0.5;
204
+ fields[k] = {
205
+ value: value === undefined ? null : value,
206
+ confidence,
207
+ needs_review: v && typeof v === "object" && typeof v.needs_review === "boolean"
208
+ ? v.needs_review
209
+ : confidence < 0.8,
210
+ };
211
+ }
212
+ const rawLines = Array.isArray(raw?.lines) ? raw.lines : [];
213
+ const lines = rawLines
214
+ .map((l) => ({
215
+ raw_product_text: typeof l?.raw_product_text === "string" ? l.raw_product_text.slice(0, 500) : "",
216
+ product_id: typeof l?.product_id === "string" && l.product_id.length === 36 ? l.product_id : null,
217
+ plant_type: typeof l?.plant_type === "string" && l.plant_type.trim() ? l.plant_type.trim().slice(0, 200) : null,
218
+ quantity: num(l?.quantity),
219
+ unit: typeof l?.unit === "string" ? l.unit.slice(0, 40) : null,
220
+ match_confidence: clamp01(l?.match_confidence),
221
+ }))
222
+ .filter((l) => l.raw_product_text || l.product_id);
223
+ const rv = raw?.validation ?? {};
224
+ return {
225
+ fields,
226
+ lines,
227
+ validation: {
228
+ missing: strList(rv?.missing),
229
+ uncertain: strList(rv?.uncertain),
230
+ notes: typeof rv?.notes === "string" ? rv.notes.slice(0, 1000) : null,
231
+ },
232
+ overall_confidence: clamp01(raw?.overall_confidence),
233
+ };
234
+ }
235
+ // ---------------------------------------------------------------------------
236
+ // Publieke API
237
+ // ---------------------------------------------------------------------------
238
+ export async function parseOrderEmail(admin, args) {
239
+ const resolution = await resolveModuleAi(admin, MAILBOX_AI_MODULE_KEY);
240
+ if (!resolution.ok)
241
+ throw new Error(resolution.reason);
242
+ const cfg = resolution.config;
243
+ const { system, user } = buildPrompt(args.bodyText, args.subject, args.templateFields, args.productCatalog, {
244
+ deliveryLocations: args.deliveryLocations,
245
+ plantTypes: args.plantTypes,
246
+ requiredFields: args.requiredFields,
247
+ systemPrompt: args.systemPrompt,
248
+ });
249
+ const started = Date.now();
250
+ try {
251
+ const completion = await callProvider(cfg, system, user);
252
+ const parsed = normalize(extractJson(completion.text));
253
+ const durationMs = Date.now() - started;
254
+ await logAiCall(admin, {
255
+ moduleKey: cfg.moduleKey,
256
+ providerSlug: cfg.providerSlug,
257
+ modelSlug: cfg.modelSlug,
258
+ status: "success",
259
+ inputTokens: completion.inputTokens,
260
+ outputTokens: completion.outputTokens,
261
+ durationMs,
262
+ });
263
+ return {
264
+ parsed,
265
+ meta: {
266
+ providerSlug: cfg.providerSlug,
267
+ modelSlug: cfg.modelSlug,
268
+ modelName: cfg.modelName,
269
+ durationMs,
270
+ fingerprint: cfg.fingerprint,
271
+ },
272
+ };
273
+ }
274
+ catch (e) {
275
+ await logAiCall(admin, {
276
+ moduleKey: cfg.moduleKey,
277
+ providerSlug: cfg.providerSlug,
278
+ modelSlug: cfg.modelSlug,
279
+ status: "error",
280
+ durationMs: Date.now() - started,
281
+ errorMessage: String(e?.message ?? e),
282
+ });
283
+ throw e;
284
+ }
7
285
  }
@@ -0,0 +1,7 @@
1
+ export declare function htmlToText(html: string): string;
2
+ /**
3
+ * Kiest de meest volledige tekstversie van een bericht: de HTML-body wint
4
+ * zodra die duidelijk meer inhoud bevat dan de (mogelijk afgekapte) preview.
5
+ */
6
+ export declare function emailBodyText(bodyText: string | null | undefined, bodyHtml: string | null | undefined): string;
7
+ //# sourceMappingURL=mailbox-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailbox-body.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-body.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB/C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,MAAM,CAMR"}
@@ -0,0 +1,38 @@
1
+ // Gedeelde helper: zet de HTML-versie van een e-mail om naar leesbare tekst.
2
+ // Nodig omdat Microsoft Graph in `body_text` vaak alleen een korte preview
3
+ // (~255 tekens) meestuurt, terwijl de volledige inhoud in `body_html` zit.
4
+ export function htmlToText(html) {
5
+ return (html ?? "")
6
+ .replace(/<style[\s\S]*?<\/style>/gi, " ")
7
+ .replace(/<script[\s\S]*?<\/script>/gi, " ")
8
+ .replace(/<!--[\s\S]*?-->/g, " ")
9
+ .replace(/<br\s*\/?>/gi, "\n")
10
+ .replace(/<\/(p|div|tr|li|h[1-6]|table)>/gi, "\n")
11
+ .replace(/<li[^>]*>/gi, "• ")
12
+ .replace(/<[^>]+>/g, " ")
13
+ .replace(/&nbsp;/gi, " ")
14
+ .replace(/&amp;/gi, "&")
15
+ .replace(/&lt;/gi, "<")
16
+ .replace(/&gt;/gi, ">")
17
+ .replace(/&quot;/gi, '"')
18
+ .replace(/&#39;/gi, "'")
19
+ .replace(/[ \t\u00a0]+/g, " ")
20
+ .replace(/\n\s*\n\s*\n+/g, "\n\n")
21
+ .split("\n")
22
+ .map((l) => l.trim())
23
+ .join("\n")
24
+ .trim();
25
+ }
26
+ /**
27
+ * Kiest de meest volledige tekstversie van een bericht: de HTML-body wint
28
+ * zodra die duidelijk meer inhoud bevat dan de (mogelijk afgekapte) preview.
29
+ */
30
+ export function emailBodyText(bodyText, bodyHtml) {
31
+ const plain = (bodyText ?? "").trim();
32
+ const fromHtml = htmlToText(bodyHtml ?? "");
33
+ if (!plain)
34
+ return fromHtml;
35
+ if (!fromHtml)
36
+ return plain;
37
+ return fromHtml.length > plain.length + 20 ? fromHtml : plain;
38
+ }
@@ -0,0 +1,76 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ import type { Database } from "../integrations/supabase/types";
3
+ type AdminClient = SupabaseClient<Database>;
4
+ /** Centrale AI-instellingen van deze module (aanpasbaar via Instellingen). */
5
+ export declare function loadMailboxAiSettings(admin: AdminClient): Promise<{
6
+ systemPrompt: string | null;
7
+ requiredFields: string[];
8
+ }>;
9
+ /**
10
+ * Combineert de zelfcontrole van de AI met een harde servercheck op de
11
+ * verplichte velden. Ontbrekende of onzekere gegevens worden expliciet
12
+ * benoemd in plaats van stilzwijgend leeg gelaten.
13
+ */
14
+ export declare function computeValidation(parsed: import("./mailbox-ai.server").ParsedOrder, requiredFields: string[], extra: {
15
+ customerId: string | null;
16
+ }): {
17
+ missing: string[];
18
+ uncertain: string[];
19
+ notes: string | null;
20
+ ok: boolean;
21
+ };
22
+ /**
23
+ * Vult het afleveradres aan met de hoofdafleverlocatie (is_primary) van de klant
24
+ * wanneer de mail/AI géén geldige locatie heeft herkend. Herkent de AI wél een
25
+ * locatie van deze klant, dan blijft die staan.
26
+ */
27
+ export declare function resolveDeliveryLocation(admin: AdminClient, customerId: string | null, aiValue: unknown): Promise<{
28
+ id: string;
29
+ primary: boolean;
30
+ } | null>;
31
+ /**
32
+ * Afhaallocatie (eigen tuin/warehouse): AI-herkenning wint, anders de hoofdlocatie
33
+ * (Floriday warehouse met isDefault = true).
34
+ */
35
+ export declare function resolvePickupLocation(admin: AdminClient, aiValue: unknown): Promise<{
36
+ id: string;
37
+ primary: boolean;
38
+ } | null>;
39
+ /**
40
+ * Maakt exact één voorstel per bericht. Probeert eerst de AI-parse via de
41
+ * centrale AI-Authenticatie module; lukt dat niet, dan valt hij terug op een
42
+ * leeg voorstel met de reden erbij. Ingest mag nooit stukvallen op AI.
43
+ */
44
+ export declare function createStubProposal(admin: AdminClient, messageId: string, fromEmail: string | null): Promise<void>;
45
+ /**
46
+ * Analyseert een bestaand (nog niet goedgekeurd) voorstel opnieuw met de
47
+ * huidige AI-agent. De vorige AI-analyse wordt vervangen.
48
+ */
49
+ export declare function reparseProposal(admin: AdminClient, proposalId: string): Promise<{
50
+ ok: boolean;
51
+ error: string | null;
52
+ }>;
53
+ /**
54
+ * Detecteert een gewijzigde AI-agent: alle openstaande voorstellen waarvan de
55
+ * opgeslagen vingerafdruk afwijkt van de huidige worden opnieuw geanalyseerd.
56
+ */
57
+ export declare function reparseChangedAgentProposals(admin: AdminClient, limit?: number): Promise<{
58
+ checked: number;
59
+ reparsed: number;
60
+ failed: number;
61
+ fingerprint: string | null;
62
+ }>;
63
+ type Trigger = "cron" | "realtime" | "manual";
64
+ export type IngestRunResult = {
65
+ ok: true;
66
+ runId: string | null;
67
+ processed: number;
68
+ inserted: number;
69
+ errors: number;
70
+ errorSample: string | null;
71
+ connectionId: string | null;
72
+ skipped?: "no_active_connection";
73
+ };
74
+ export declare function runIngest(trigger: Trigger): Promise<IngestRunResult>;
75
+ export {};
76
+ //# sourceMappingURL=mailbox-ingest.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailbox-ingest.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ingest.server.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAU9D,KAAK,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;AAuK5C,8EAA8E;AAC9E,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IACvE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC,CAWD;AAiHD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,qBAAqB,EAAE,WAAW,EACjD,cAAc,EAAE,MAAM,EAAE,EACxB,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACnC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,EAAE,EAAE,OAAO,CAAA;CAAE,CAkC/E;AAoBD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CAiBlD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CAiBlD;AAqCD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,IAAI,CAAC,CAmJf;AAGD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAuIhD;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,WAAW,EAClB,KAAK,SAAK,GACT,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CA4B5F;AA4ED,KAAK,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,sBAAsB,CAAC;CAClC,CAAC;AAGF,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CA0H1E"}