@flowselections/mailbox-orders 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) 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 +16 -0
  7. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  8. package/dist-lib/components/MailboxProposalsPage.js +520 -108
  9. package/dist-lib/components/SearchableSelect.d.ts +1 -0
  10. package/dist-lib/components/SearchableSelect.d.ts.map +1 -1
  11. package/dist-lib/components/SearchableSelect.js +2 -1
  12. package/dist-lib/components/settings/MailboxAiPromptCard.d.ts +2 -0
  13. package/dist-lib/components/settings/MailboxAiPromptCard.d.ts.map +1 -0
  14. package/dist-lib/components/settings/MailboxAiPromptCard.js +39 -0
  15. package/dist-lib/index.d.ts.map +1 -1
  16. package/dist-lib/index.js +2 -0
  17. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/auth-attacher.js +5 -1
  19. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2 -11202
  20. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  21. package/dist-lib/integrations/supabase/auth-middleware.js +8 -7
  22. package/dist-lib/integrations/supabase/client.d.ts +16689 -7180
  23. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  24. package/dist-lib/integrations/supabase/client.server.d.ts +16691 -7181
  25. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  26. package/dist-lib/integrations/supabase/types.d.ts +16944 -7220
  27. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/types.js +63 -1
  29. package/dist-lib/lib/ai-config.server.d.ts +60 -0
  30. package/dist-lib/lib/ai-config.server.d.ts.map +1 -0
  31. package/dist-lib/lib/ai-config.server.js +112 -0
  32. package/dist-lib/lib/delivery-date.d.ts +37 -0
  33. package/dist-lib/lib/delivery-date.d.ts.map +1 -0
  34. package/dist-lib/lib/delivery-date.js +96 -0
  35. package/dist-lib/lib/load-carriers.d.ts +26 -0
  36. package/dist-lib/lib/load-carriers.d.ts.map +1 -0
  37. package/dist-lib/lib/load-carriers.js +73 -0
  38. package/dist-lib/lib/mailbox-ai-prompt.d.ts +9 -0
  39. package/dist-lib/lib/mailbox-ai-prompt.d.ts.map +1 -0
  40. package/dist-lib/lib/mailbox-ai-prompt.js +30 -0
  41. package/dist-lib/lib/mailbox-ai-settings.functions.d.ts +23 -0
  42. package/dist-lib/lib/mailbox-ai-settings.functions.d.ts.map +1 -0
  43. package/dist-lib/lib/mailbox-ai-settings.functions.js +58 -0
  44. package/dist-lib/lib/mailbox-ai.server.d.ts +56 -4
  45. package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
  46. package/dist-lib/lib/mailbox-ai.server.js +329 -6
  47. package/dist-lib/lib/mailbox-body.d.ts +7 -0
  48. package/dist-lib/lib/mailbox-body.d.ts.map +1 -0
  49. package/dist-lib/lib/mailbox-body.js +38 -0
  50. package/dist-lib/lib/mailbox-ingest.server.d.ts +89 -0
  51. package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -0
  52. package/dist-lib/lib/mailbox-ingest.server.js +928 -0
  53. package/dist-lib/lib/mailbox-template.functions.d.ts +7 -67213
  54. package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -1
  55. package/dist-lib/lib/mailbox.functions.d.ts +130 -190511
  56. package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
  57. package/dist-lib/lib/mailbox.functions.js +641 -274
  58. package/package.json +4 -3
  59. package/dist-lib/components/CartAssignmentWizard.d.ts +0 -6
  60. package/dist-lib/components/CartAssignmentWizard.d.ts.map +0 -1
  61. package/dist-lib/components/CartAssignmentWizard.js +0 -199
  62. package/dist-lib/components/MailboxInboxPage.d.ts +0 -2
  63. package/dist-lib/components/MailboxInboxPage.d.ts.map +0 -1
  64. package/dist-lib/components/MailboxInboxPage.js +0 -120
  65. package/dist-lib/components/settings/ImapAccountsCard.d.ts +0 -2
  66. package/dist-lib/components/settings/ImapAccountsCard.d.ts.map +0 -1
  67. package/dist-lib/components/settings/ImapAccountsCard.js +0 -136
  68. package/dist-lib/components/settings/ImapProfilesCard.d.ts +0 -2
  69. package/dist-lib/components/settings/ImapProfilesCard.d.ts.map +0 -1
  70. package/dist-lib/components/settings/ImapProfilesCard.js +0 -101
  71. package/dist-lib/components/settings/MailboxSettingsCard.d.ts +0 -2
  72. package/dist-lib/components/settings/MailboxSettingsCard.d.ts.map +0 -1
  73. package/dist-lib/components/settings/MailboxSettingsCard.js +0 -85
  74. package/dist-lib/lib/imap.functions.d.ts +0 -32852
  75. package/dist-lib/lib/imap.functions.d.ts.map +0 -1
  76. package/dist-lib/lib/imap.functions.js +0 -235
  77. package/dist-lib/lib/mailbox-auto.server.d.ts +0 -9
  78. package/dist-lib/lib/mailbox-auto.server.d.ts.map +0 -1
  79. package/dist-lib/lib/mailbox-auto.server.js +0 -198
@@ -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,60 @@ 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
+ /** Tijdelijke storing → opnieuw proberen loont. */
59
+ export declare function isTransientAiError(message: string): boolean;
60
+ export declare class AiUnavailableError extends Error {
61
+ readonly transient: boolean;
62
+ readonly attempts: number;
63
+ constructor(message: string, transient: boolean, attempts: number);
64
+ }
65
+ export declare function parseOrderEmail(admin: AdminClient, args: {
66
+ bodyText: string;
67
+ subject: string;
68
+ templateFields: TemplateFieldSpec[];
69
+ productCatalog: Array<{
70
+ id: string;
71
+ name: string;
72
+ }>;
73
+ deliveryLocations?: Array<{
74
+ id: string;
75
+ name: string;
76
+ }>;
77
+ plantTypes?: string[];
78
+ requiredFields?: string[];
79
+ systemPrompt?: string | null;
80
+ }): Promise<{
81
+ parsed: ParsedOrder;
82
+ meta: AiParseMeta;
83
+ }>;
32
84
  //# 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":"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"}
@@ -1,7 +1,330 @@
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
+ /** HTTP-status uit een providerfout ("Gemini 503: …"). */
239
+ function errorStatus(message) {
240
+ const m = /\b(\d{3})\b/.exec(message);
241
+ const code = m ? Number(m[1]) : NaN;
242
+ return code >= 400 && code <= 599 ? code : null;
243
+ }
244
+ /** Tijdelijke storing → opnieuw proberen loont. */
245
+ export function isTransientAiError(message) {
246
+ const s = errorStatus(message);
247
+ if (s !== null)
248
+ return s === 408 || s === 409 || s === 425 || s === 429 || s >= 500;
249
+ return /timeout|timed out|fetch failed|network|ECONN|socket|overload|unavailable|temporar/i.test(message);
250
+ }
251
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
252
+ export class AiUnavailableError extends Error {
253
+ constructor(message, transient, attempts) {
254
+ super(message);
255
+ this.name = "AiUnavailableError";
256
+ this.transient = transient;
257
+ this.attempts = attempts;
258
+ }
259
+ }
260
+ export async function parseOrderEmail(admin, args) {
261
+ const { resolveModuleAiAll } = await import("./ai-config.server");
262
+ const resolution = await resolveModuleAiAll(admin, MAILBOX_AI_MODULE_KEY);
263
+ if (!resolution.ok)
264
+ throw new AiUnavailableError(resolution.reason, false, 0);
265
+ const configs = resolution.configs;
266
+ const primary = configs[0];
267
+ const { system, user } = buildPrompt(args.bodyText, args.subject, args.templateFields, args.productCatalog, {
268
+ deliveryLocations: args.deliveryLocations,
269
+ plantTypes: args.plantTypes,
270
+ requiredFields: args.requiredFields,
271
+ systemPrompt: args.systemPrompt,
272
+ });
273
+ // Per model maximaal 3 pogingen met exponentiële backoff; daarna uitwijken
274
+ // naar het volgende model in de keten. De vingerafdruk blijft die van het
275
+ // standaardmodel, zodat een fallback geen eindeloze her-analyse veroorzaakt.
276
+ const MAX_ATTEMPTS_PER_MODEL = 3;
277
+ let attemptNo = 0;
278
+ let lastMessage = "AI niet beschikbaar.";
279
+ let lastTransient = false;
280
+ for (const cfg of configs) {
281
+ for (let i = 0; i < MAX_ATTEMPTS_PER_MODEL; i++) {
282
+ attemptNo++;
283
+ const started = Date.now();
284
+ try {
285
+ const completion = await callProvider(cfg, system, user);
286
+ const parsed = normalize(extractJson(completion.text));
287
+ const durationMs = Date.now() - started;
288
+ await logAiCall(admin, {
289
+ moduleKey: cfg.moduleKey,
290
+ providerSlug: cfg.providerSlug,
291
+ modelSlug: cfg.modelSlug,
292
+ status: "success",
293
+ inputTokens: completion.inputTokens,
294
+ outputTokens: completion.outputTokens,
295
+ durationMs,
296
+ attempt: attemptNo,
297
+ });
298
+ return {
299
+ parsed,
300
+ meta: {
301
+ providerSlug: cfg.providerSlug,
302
+ modelSlug: cfg.modelSlug,
303
+ modelName: cfg.modelName,
304
+ durationMs,
305
+ fingerprint: primary.fingerprint,
306
+ },
307
+ };
308
+ }
309
+ catch (e) {
310
+ lastMessage = String(e?.message ?? e);
311
+ lastTransient = isTransientAiError(lastMessage);
312
+ await logAiCall(admin, {
313
+ moduleKey: cfg.moduleKey,
314
+ providerSlug: cfg.providerSlug,
315
+ modelSlug: cfg.modelSlug,
316
+ status: "error",
317
+ durationMs: Date.now() - started,
318
+ errorMessage: lastMessage,
319
+ errorCode: errorStatus(lastMessage) ? String(errorStatus(lastMessage)) : null,
320
+ attempt: attemptNo,
321
+ });
322
+ if (!lastTransient)
323
+ break; // permanente fout: direct volgend model
324
+ if (i < MAX_ATTEMPTS_PER_MODEL - 1)
325
+ await sleep(600 * 2 ** i + Math.floor(Math.random() * 250));
326
+ }
327
+ }
328
+ }
329
+ throw new AiUnavailableError(lastMessage, lastTransient, attemptNo);
7
330
  }
@@ -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,89 @@
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
+ /** Maximaal aantal achtergrondpogingen voordat we het voorstel handmatig laten. */
23
+ export declare const AI_MAX_BACKGROUND_RETRIES = 6;
24
+ /**
25
+ * Vult het afleveradres aan met de hoofdafleverlocatie (is_primary) van de klant
26
+ * wanneer de mail/AI géén geldige locatie heeft herkend. Herkent de AI wél een
27
+ * locatie van deze klant, dan blijft die staan.
28
+ */
29
+ export declare function resolveDeliveryLocation(admin: AdminClient, customerId: string | null, aiValue: unknown): Promise<{
30
+ id: string;
31
+ primary: boolean;
32
+ } | null>;
33
+ /**
34
+ * Afhaallocatie (eigen tuin/warehouse): AI-herkenning wint, anders de hoofdlocatie
35
+ * (Floriday warehouse met isDefault = true).
36
+ */
37
+ export declare function resolvePickupLocation(admin: AdminClient, aiValue: unknown): Promise<{
38
+ id: string;
39
+ primary: boolean;
40
+ } | null>;
41
+ /**
42
+ * Maakt exact één voorstel per bericht. Probeert eerst de AI-parse via de
43
+ * centrale AI-Authenticatie module; lukt dat niet, dan valt hij terug op een
44
+ * leeg voorstel met de reden erbij. Ingest mag nooit stukvallen op AI.
45
+ */
46
+ export declare function createStubProposal(admin: AdminClient, messageId: string, fromEmail: string | null): Promise<void>;
47
+ /**
48
+ * Analyseert een bestaand (nog niet goedgekeurd) voorstel opnieuw met de
49
+ * huidige AI-agent. De vorige AI-analyse wordt vervangen.
50
+ */
51
+ export declare function reparseProposal(admin: AdminClient, proposalId: string): Promise<{
52
+ ok: boolean;
53
+ error: string | null;
54
+ }>;
55
+ /**
56
+ * Detecteert een gewijzigde AI-agent: alle openstaande voorstellen waarvan de
57
+ * opgeslagen vingerafdruk afwijkt van de huidige worden opnieuw geanalyseerd.
58
+ */
59
+ export declare function reparseChangedAgentProposals(admin: AdminClient, limit?: number): Promise<{
60
+ checked: number;
61
+ reparsed: number;
62
+ failed: number;
63
+ fingerprint: string | null;
64
+ }>;
65
+ /**
66
+ * Achtergrondherstel: voorstellen waarvan de AI-analyse door een tijdelijke
67
+ * storing (bijv. model overbelast) mislukte, worden bij een volgende run
68
+ * automatisch opnieuw geanalyseerd — tot AI_MAX_BACKGROUND_RETRIES pogingen.
69
+ * Blijft strikt idempotent: alleen 'pending' voorstellen worden aangeraakt.
70
+ */
71
+ export declare function retryFailedAiProposals(admin: AdminClient, limit?: number): Promise<{
72
+ retried: number;
73
+ recovered: number;
74
+ stillFailing: number;
75
+ }>;
76
+ type Trigger = "cron" | "realtime" | "manual";
77
+ export type IngestRunResult = {
78
+ ok: true;
79
+ runId: string | null;
80
+ processed: number;
81
+ inserted: number;
82
+ errors: number;
83
+ errorSample: string | null;
84
+ connectionId: string | null;
85
+ skipped?: "no_active_connection";
86
+ };
87
+ export declare function runIngest(trigger: Trigger): Promise<IngestRunResult>;
88
+ export {};
89
+ //# 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":"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"}