@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.
- package/dist-lib/components/MailboxAuthBanner.d.ts +16 -0
- package/dist-lib/components/MailboxAuthBanner.d.ts.map +1 -0
- package/dist-lib/components/MailboxAuthBanner.js +14 -0
- package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProcessedPage.js +10 -2
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +425 -104
- package/dist-lib/components/SearchableSelect.d.ts +1 -0
- package/dist-lib/components/SearchableSelect.d.ts.map +1 -1
- package/dist-lib/components/SearchableSelect.js +2 -1
- package/dist-lib/components/settings/MailboxAiPromptCard.d.ts +2 -0
- package/dist-lib/components/settings/MailboxAiPromptCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MailboxAiPromptCard.js +39 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +2 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/auth-attacher.js +5 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2 -11202
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/auth-middleware.js +8 -7
- package/dist-lib/integrations/supabase/client.d.ts +16514 -7163
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +16516 -7164
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +16768 -7202
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +59 -8
- package/dist-lib/lib/ai-config.server.d.ts +46 -0
- package/dist-lib/lib/ai-config.server.d.ts.map +1 -0
- package/dist-lib/lib/ai-config.server.js +99 -0
- package/dist-lib/lib/delivery-date.d.ts +37 -0
- package/dist-lib/lib/delivery-date.d.ts.map +1 -0
- package/dist-lib/lib/delivery-date.js +96 -0
- package/dist-lib/lib/load-carriers.d.ts +26 -0
- package/dist-lib/lib/load-carriers.d.ts.map +1 -0
- package/dist-lib/lib/load-carriers.js +73 -0
- package/dist-lib/lib/mailbox-ai-prompt.d.ts +9 -0
- package/dist-lib/lib/mailbox-ai-prompt.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-ai-prompt.js +30 -0
- package/dist-lib/lib/mailbox-ai-settings.functions.d.ts +23 -0
- package/dist-lib/lib/mailbox-ai-settings.functions.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-ai-settings.functions.js +58 -0
- package/dist-lib/lib/mailbox-ai.server.d.ts +49 -4
- package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ai.server.js +284 -6
- package/dist-lib/lib/mailbox-body.d.ts +7 -0
- package/dist-lib/lib/mailbox-body.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-body.js +38 -0
- package/dist-lib/lib/mailbox-ingest.server.d.ts +76 -0
- package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-ingest.server.js +863 -0
- package/dist-lib/lib/mailbox-template.functions.d.ts +7 -67213
- package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.d.ts +107 -190506
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +544 -270
- 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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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":"
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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(/ /gi, " ")
|
|
14
|
+
.replace(/&/gi, "&")
|
|
15
|
+
.replace(/</gi, "<")
|
|
16
|
+
.replace(/>/gi, ">")
|
|
17
|
+
.replace(/"/gi, '"')
|
|
18
|
+
.replace(/'/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"}
|