@flowselections/mailbox-orders 1.0.61 → 1.0.64

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.
@@ -8,8 +8,12 @@ export type PreviewAttachment = {
8
8
  size?: number | null;
9
9
  contentId?: string | null;
10
10
  url?: string | null;
11
+ isInline?: boolean;
12
+ downloadable?: boolean;
13
+ skippedReason?: string | null;
11
14
  };
12
15
  type Props = {
16
+ messageId?: string | null;
13
17
  subject?: string | null;
14
18
  fromName?: string | null;
15
19
  fromEmail?: string | null;
@@ -21,6 +25,6 @@ type Props = {
21
25
  attachments?: PreviewAttachment[];
22
26
  hasAttachments?: boolean;
23
27
  };
24
- export declare function EmailPreview({ subject, fromName, fromEmail, receivedAt, to, cc, bodyHtml, bodyText, attachments, hasAttachments, }: Props): import("react").JSX.Element;
28
+ export declare function EmailPreview({ messageId, subject, fromName, fromEmail, receivedAt, to, cc, bodyHtml, bodyText, attachments, hasAttachments, }: Props): import("react").JSX.Element;
25
29
  export {};
26
30
  //# sourceMappingURL=EmailPreview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EmailPreview.d.ts","sourceRoot":"","sources":["../../src/components/EmailPreview.tsx"],"names":[],"mappings":"AAKA,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC;IACtB,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAsEF,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,QAAQ,EACR,SAAS,EACT,UAAU,EACV,EAAO,EACP,EAAO,EACP,QAAQ,EACR,QAAQ,EACR,WAAgB,EAChB,cAAc,GACf,EAAE,KAAK,+BAsHP"}
1
+ {"version":3,"file":"EmailPreview.d.ts","sourceRoot":"","sources":["../../src/components/EmailPreview.tsx"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC;IACtB,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAsEF,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,OAAO,EACP,QAAQ,EACR,SAAS,EACT,UAAU,EACV,EAAO,EACP,EAAO,EACP,QAAQ,EACR,QAAQ,EACR,WAAgB,EAChB,cAAc,GACf,EAAE,KAAK,+BAkJP"}
@@ -3,6 +3,10 @@ import { fmtDayTime } from "../lib/format-date.js";
3
3
  import { useEffect, useMemo, useState } from "react";
4
4
  import { Button, Badge } from "@flowselections/core";
5
5
  import { Paperclip, Download, Code, FileText, ShieldOff, Shield } from "lucide-react";
6
+ import { useServerFn } from "@tanstack/react-start";
7
+ import { toast } from "sonner";
8
+ import { getMailboxAttachmentUrl } from "../lib/mailbox.functions.js";
9
+ import { skippedLabel } from "../lib/mailbox-attachments.js";
6
10
  function addrLabel(a) {
7
11
  if (a.name && a.email && a.name !== a.email)
8
12
  return `${a.name} <${a.email}>`;
@@ -43,7 +47,7 @@ function EmailFrame({ html }) {
43
47
  // en niet bij de applicatie. Popups blijven werken voor links.
44
48
  sandbox: "allow-popups allow-popups-to-escape-sandbox", referrerPolicy: "no-referrer", className: "h-full w-full rounded border bg-white" }));
45
49
  }
46
- export function EmailPreview({ subject, fromName, fromEmail, receivedAt, to = [], cc = [], bodyHtml, bodyText, attachments = [], hasAttachments, }) {
50
+ export function EmailPreview({ messageId, subject, fromName, fromEmail, receivedAt, to = [], cc = [], bodyHtml, bodyText, attachments = [], hasAttachments, }) {
47
51
  const html = (bodyHtml ?? "").trim();
48
52
  const [mode, setMode] = useState(html ? "html" : "text");
49
53
  const [blockRemoteContent, setBlockRemoteContent] = useState(false);
@@ -51,7 +55,31 @@ export function EmailPreview({ subject, fromName, fromEmail, receivedAt, to = []
51
55
  setMode(html ? "html" : "text");
52
56
  }, [html]);
53
57
  const rendered = blockRemoteContent ? blockRemote(html) : html;
54
- const safeAttachments = Array.isArray(attachments) ? attachments : [];
55
- const inlineOnly = safeAttachments.every((a) => !!a.contentId);
56
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-1 border-b pb-3", children: [_jsx("div", { className: "text-base font-semibold leading-snug", children: subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-foreground", children: fromName || fromEmail }), fromName && fromEmail ? ` <${fromEmail}>` : "", receivedAt ? ` · ${fmtDayTime(receivedAt)}` : ""] }), _jsx(AddressLine, { label: "Aan", list: to }), _jsx(AddressLine, { label: "CC", list: cc }), (safeAttachments.length > 0 || hasAttachments) && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2", children: [safeAttachments.length === 0 ? (_jsxs(Badge, { variant: "outline", className: "gap-1 font-normal", children: [_jsx(Paperclip, { className: "h-3 w-3" }), " Deze mail bevat bijlagen (nog niet opgehaald)"] })) : (safeAttachments.map((a, i) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded border bg-muted/40 px-2 py-1 text-xs", children: [_jsx(Paperclip, { className: "h-3 w-3 shrink-0" }), _jsx("span", { className: "max-w-[16rem] truncate", children: a.name }), formatSize(a.size) && (_jsx("span", { className: "text-muted-foreground", children: formatSize(a.size) })), a.url && (_jsx("a", { href: a.url, target: "_blank", rel: "noopener noreferrer", className: "text-primary", "aria-label": `Download ${a.name}`, children: _jsx(Download, { className: "h-3 w-3" }) }))] }, `${a.name}-${i}`)))), safeAttachments.length > 0 && inlineOnly && (_jsx("span", { className: "text-[11px] text-muted-foreground", children: "(in de mail ingesloten afbeeldingen)" }))] })), _jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2", children: [_jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: () => setMode(mode === "html" ? "text" : "html"), disabled: !html, children: mode === "html" ? (_jsxs(_Fragment, { children: [_jsx(FileText, { className: "mr-1 h-3.5 w-3.5" }), " Platte tekst"] })) : (_jsxs(_Fragment, { children: [_jsx(Code, { className: "mr-1 h-3.5 w-3.5" }), " Opgemaakte mail"] })) }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", disabled: mode !== "html" || !html, onClick: () => setBlockRemoteContent((v) => !v), children: blockRemoteContent ? (_jsxs(_Fragment, { children: [_jsx(Shield, { className: "mr-1 h-3.5 w-3.5" }), " Externe inhoud tonen"] })) : (_jsxs(_Fragment, { children: [_jsx(ShieldOff, { className: "mr-1 h-3.5 w-3.5" }), " Externe inhoud blokkeren"] })) })] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden pt-3", children: mode === "html" && html ? (_jsx(EmailFrame, { html: rendered })) : (_jsx("div", { className: "h-full w-full overflow-y-auto overscroll-contain rounded border bg-white p-3", children: _jsx("pre", { className: "whitespace-pre-wrap break-words font-sans text-sm leading-relaxed text-[#111]", children: bodyText || "(geen tekst)" }) })) })] }));
58
+ const allAttachments = Array.isArray(attachments) ? attachments : [];
59
+ // Ingesloten afbeeldingen staan in de mail zelf, niet als losse bijlage.
60
+ const safeAttachments = allAttachments
61
+ .map((a, index) => ({ a, index }))
62
+ .filter(({ a }) => !(a.isInline ?? !!a.contentId));
63
+ const fetchUrl = useServerFn(getMailboxAttachmentUrl);
64
+ const [busy, setBusy] = useState(null);
65
+ async function download(index) {
66
+ if (!messageId)
67
+ return;
68
+ setBusy(index);
69
+ try {
70
+ const res = await fetchUrl({ data: { messageId, index } });
71
+ if (res?.url)
72
+ window.open(res.url, "_blank", "noopener,noreferrer");
73
+ else
74
+ toast("Deze bijlage is niet meer beschikbaar.");
75
+ }
76
+ catch (e) {
77
+ console.error("[mailbox] downloadlink ophalen mislukt", e);
78
+ toast.error("Downloaden lukte niet. Probeer het opnieuw of neem contact op met de beheerder.");
79
+ }
80
+ finally {
81
+ setBusy(null);
82
+ }
83
+ }
84
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-1 border-b pb-3", children: [_jsx("div", { className: "text-base font-semibold leading-snug", children: subject || "(geen onderwerp)" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-foreground", children: fromName || fromEmail }), fromName && fromEmail ? ` <${fromEmail}>` : "", receivedAt ? ` · ${fmtDayTime(receivedAt)}` : ""] }), _jsx(AddressLine, { label: "Aan", list: to }), _jsx(AddressLine, { label: "CC", list: cc }), (safeAttachments.length > 0 || (hasAttachments && allAttachments.length === 0)) && (_jsx("div", { className: "flex flex-wrap items-center gap-2 pt-2", children: safeAttachments.length === 0 ? (_jsxs(Badge, { variant: "outline", className: "gap-1 font-normal", children: [_jsx(Paperclip, { className: "h-3 w-3" }), " Deze mail bevat bijlagen (nog niet opgehaald)"] })) : (safeAttachments.map(({ a, index: i }) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded border bg-muted/40 px-2 py-1 text-xs", children: [_jsx(Paperclip, { className: "h-3 w-3 shrink-0" }), _jsx("span", { className: "max-w-[16rem] truncate", children: a.name }), formatSize(a.size) && (_jsx("span", { className: "text-muted-foreground", children: formatSize(a.size) })), skippedLabel(a.skippedReason) && (_jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 ", skippedLabel(a.skippedReason)] })), a.downloadable && messageId ? (_jsx("button", { type: "button", className: "text-primary disabled:opacity-50", disabled: busy === i, onClick: () => download(i), "aria-label": `Download ${a.name}`, children: _jsx(Download, { className: "h-3 w-3" }) })) : a.url && !a.skippedReason && (_jsx("a", { href: a.url, target: "_blank", rel: "noopener noreferrer", className: "text-primary", "aria-label": `Download ${a.name}`, children: _jsx(Download, { className: "h-3 w-3" }) }))] }, `${a.name}-${i}`)))) })), _jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2", children: [_jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: () => setMode(mode === "html" ? "text" : "html"), disabled: !html, children: mode === "html" ? (_jsxs(_Fragment, { children: [_jsx(FileText, { className: "mr-1 h-3.5 w-3.5" }), " Platte tekst"] })) : (_jsxs(_Fragment, { children: [_jsx(Code, { className: "mr-1 h-3.5 w-3.5" }), " Opgemaakte mail"] })) }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", disabled: mode !== "html" || !html, onClick: () => setBlockRemoteContent((v) => !v), children: blockRemoteContent ? (_jsxs(_Fragment, { children: [_jsx(Shield, { className: "mr-1 h-3.5 w-3.5" }), " Externe inhoud tonen"] })) : (_jsxs(_Fragment, { children: [_jsx(ShieldOff, { className: "mr-1 h-3.5 w-3.5" }), " Externe inhoud blokkeren"] })) })] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden pt-3", children: mode === "html" && html ? (_jsx(EmailFrame, { html: rendered })) : (_jsx("div", { className: "h-full w-full overflow-y-auto overscroll-contain rounded border bg-white p-3", children: _jsx("pre", { className: "whitespace-pre-wrap break-words font-sans text-sm leading-relaxed text-[#111]", children: bodyText || "(geen tekst)" }) })) })] }));
57
85
  }
@@ -890,7 +890,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
890
890
  setSubmitting(false);
891
891
  }
892
892
  };
893
- return (_jsxs("div", { className: "grid grid-cols-2 gap-4 h-full min-h-0", children: [_jsx(Card, { className: "flex flex-col min-h-0 overflow-hidden", children: _jsxs(CardContent, { className: "flex-1 min-h-0 p-4 flex flex-col gap-2", children: [msg.account_email && (_jsxs("div", { className: "shrink-0 text-xs text-muted-foreground", children: ["Ontvangen op: ", msg.account_email] })), proposal.parsed_payload?.reason === "geen_ai_agent" && (_jsxs("div", { className: "shrink-0 rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground", children: ["Automatisch uitlezen staat uit. Vul de gegevens zelf in; een beheerder kan dit aanzetten bij Instellingen.", " ", _jsx("a", { href: AI_AUTH_PATH, className: "underline font-medium", children: "Naar Instellingen" })] })), _jsx(EmailPreview, { subject: msg.subject, fromName: msg.from_name, fromEmail: msg.from_email, receivedAt: msg.received_at, to: Array.isArray(msg.to_recipients) ? msg.to_recipients : [], cc: Array.isArray(msg.cc_recipients) ? msg.cc_recipients : [], bodyHtml: msg.body_html, bodyText: msg.body_text, attachments: Array.isArray(msg.attachments) ? msg.attachments : [], hasAttachments: msg.has_attachments === true })] }) }), _jsxs(Card, { className: "flex flex-col min-h-0 overflow-hidden", children: [_jsx(CardHeader, { className: "shrink-0", children: _jsx(CardTitle, { className: "text-base", children: "Bestelvoorstel" }) }), _jsxs(CardContent, { className: "space-y-4 flex-1 min-h-0 overflow-y-auto overscroll-contain", children: [(() => {
893
+ return (_jsxs("div", { className: "grid grid-cols-2 gap-4 h-full min-h-0", children: [_jsx(Card, { className: "flex flex-col min-h-0 overflow-hidden", children: _jsxs(CardContent, { className: "flex-1 min-h-0 p-4 flex flex-col gap-2", children: [msg.account_email && (_jsxs("div", { className: "shrink-0 text-xs text-muted-foreground", children: ["Ontvangen op: ", msg.account_email] })), proposal.parsed_payload?.reason === "geen_ai_agent" && (_jsxs("div", { className: "shrink-0 rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-soft-foreground", children: ["Automatisch uitlezen staat uit. Vul de gegevens zelf in; een beheerder kan dit aanzetten bij Instellingen.", " ", _jsx("a", { href: AI_AUTH_PATH, className: "underline font-medium", children: "Naar Instellingen" })] })), _jsx(EmailPreview, { messageId: msg.id ?? proposal.message_id ?? null, subject: msg.subject, fromName: msg.from_name, fromEmail: msg.from_email, receivedAt: msg.received_at, to: Array.isArray(msg.to_recipients) ? msg.to_recipients : [], cc: Array.isArray(msg.cc_recipients) ? msg.cc_recipients : [], bodyHtml: msg.body_html, bodyText: msg.body_text, attachments: Array.isArray(msg.attachments) ? msg.attachments : [], hasAttachments: msg.has_attachments === true })] }) }), _jsxs(Card, { className: "flex flex-col min-h-0 overflow-hidden", children: [_jsx(CardHeader, { className: "shrink-0", children: _jsx(CardTitle, { className: "text-base", children: "Bestelvoorstel" }) }), _jsxs(CardContent, { className: "space-y-4 flex-1 min-h-0 overflow-y-auto overscroll-contain", children: [(() => {
894
894
  // Klantveld staat altijd bovenaan, ook als het template (nog) leeg is.
895
895
  const customerField = visibleFields.find((f) => f.key === "customer") ?? {
896
896
  id: "customer",
@@ -1,6 +1,8 @@
1
1
  import type { SupabaseClient } from "@supabase/supabase-js";
2
2
  import type { Database } from "../integrations/supabase/types.js";
3
3
  import { DEFAULT_MAILBOX_SYSTEM_PROMPT } from "./mailbox-ai-prompt.js";
4
+ import type { AiFile } from "./mailbox-attachments.server.js";
5
+ export declare const ATTACHMENT_PROMPT_LINE = "Bestelregels kunnen ook in de bijgevoegde bestanden staan. Lees die net zo goed uit als de mailtekst.";
4
6
  export { DEFAULT_MAILBOX_SYSTEM_PROMPT };
5
7
  type AdminClient = SupabaseClient<Database>;
6
8
  export declare const MAILBOX_AI_MODULE_KEY = "mailbox-bestellingen";
@@ -68,6 +70,9 @@ export interface AiParseMeta {
68
70
  }
69
71
  /** Huidige vingerafdruk van de gekoppelde AI-agent, of null als AI niet beschikbaar is. */
70
72
  export declare function currentAiFingerprint(admin: AdminClient): Promise<string | null>;
73
+ export declare function geminiParts(user: string, files: AiFile[]): any[];
74
+ export declare function openAiContent(user: string, files: AiFile[]): any;
75
+ export declare function anthropicContent(user: string, files: AiFile[]): any;
71
76
  /** Concrete ankerdatums zodat het model relatieve aanduidingen niet hoeft te rekenen. */
72
77
  export declare function buildDateContext(now?: Date): {
73
78
  today: string;
@@ -84,7 +89,19 @@ export declare class AiUnavailableError extends Error {
84
89
  readonly attempts: number;
85
90
  constructor(message: string, transient: boolean, attempts: number);
86
91
  }
87
- export declare function parseOrderEmail(admin: AdminClient, args: {
92
+ /**
93
+ * Leest de mail uit, met bijlagen als die er zijn. Weigert de AI de bijlagen
94
+ * met een blijvende fout, dan volgt één nieuwe poging zonder bijlagen en
95
+ * zonder bijlage-promptzin; `readAttachmentIndexes` is dan leeg.
96
+ */
97
+ export declare function parseOrderEmail(admin: AdminClient, args: Parameters<typeof parseOrderEmailOnce>[1] & {
98
+ files?: AiFile[];
99
+ }): Promise<{
100
+ parsed: ParsedOrder;
101
+ meta: AiParseMeta;
102
+ readAttachmentIndexes: number[];
103
+ }>;
104
+ declare function parseOrderEmailOnce(admin: AdminClient, args: {
88
105
  bodyText: string;
89
106
  subject: string;
90
107
  templateFields: TemplateFieldSpec[];
@@ -98,7 +115,7 @@ export declare function parseOrderEmail(admin: AdminClient, args: {
98
115
  systemPrompt?: string | null;
99
116
  now?: Date;
100
117
  maxAttemptsPerModel?: number;
101
- }): Promise<{
118
+ }, files?: AiFile[]): Promise<{
102
119
  parsed: ParsedOrder;
103
120
  meta: AiParseMeta;
104
121
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"mailbox-ai.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai.server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mailbox-ai.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai.server.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@
4
4
  import { resolveModuleAi, logAiCall } from "./ai-config.server.js";
5
5
  import { DEFAULT_MAILBOX_SYSTEM_PROMPT } from "./mailbox-ai-prompt.js";
6
6
  import { stripVersionSuffix } from "./ai-base-url.js";
7
+ export const ATTACHMENT_PROMPT_LINE = "Bestelregels kunnen ook in de bijgevoegde bestanden staan. Lees die net zo goed uit als de mailtekst.";
7
8
  export { DEFAULT_MAILBOX_SYSTEM_PROMPT };
8
9
  export const MAILBOX_AI_MODULE_KEY = "mailbox-bestellingen";
9
10
  /** Huidige vingerafdruk van de gekoppelde AI-agent, of null als AI niet beschikbaar is. */
@@ -11,7 +12,7 @@ export async function currentAiFingerprint(admin) {
11
12
  const res = await resolveModuleAi(admin, MAILBOX_AI_MODULE_KEY);
12
13
  return res.ok ? res.config.fingerprint : null;
13
14
  }
14
- async function callGemini(cfg, system, user) {
15
+ async function callGemini(cfg, system, user, files = []) {
15
16
  const base = stripVersionSuffix(cfg.baseUrl, ["v1beta", "v1"]);
16
17
  const url = `${base}/v1beta/models/${encodeURIComponent(cfg.modelSlug)}:generateContent?key=${encodeURIComponent(cfg.apiKey)}`;
17
18
  const res = await fetch(url, {
@@ -19,7 +20,7 @@ async function callGemini(cfg, system, user) {
19
20
  headers: { "Content-Type": "application/json" },
20
21
  body: JSON.stringify({
21
22
  systemInstruction: { parts: [{ text: system }] },
22
- contents: [{ role: "user", parts: [{ text: user }] }],
23
+ contents: [{ role: "user", parts: geminiParts(user, files) }],
23
24
  generationConfig: { temperature: 0, responseMimeType: "application/json" },
24
25
  }),
25
26
  });
@@ -36,7 +37,7 @@ async function callGemini(cfg, system, user) {
36
37
  outputTokens: json?.usageMetadata?.candidatesTokenCount ?? null,
37
38
  };
38
39
  }
39
- async function callOpenAi(cfg, system, user) {
40
+ async function callOpenAi(cfg, system, user, files = []) {
40
41
  const res = await fetch(`${cfg.baseUrl}/chat/completions`, {
41
42
  method: "POST",
42
43
  headers: {
@@ -47,7 +48,7 @@ async function callOpenAi(cfg, system, user) {
47
48
  model: cfg.modelSlug,
48
49
  messages: [
49
50
  { role: "system", content: system },
50
- { role: "user", content: user },
51
+ { role: "user", content: openAiContent(user, files) },
51
52
  ],
52
53
  response_format: { type: "json_object" },
53
54
  }),
@@ -62,7 +63,7 @@ async function callOpenAi(cfg, system, user) {
62
63
  outputTokens: json?.usage?.completion_tokens ?? null,
63
64
  };
64
65
  }
65
- async function callAnthropic(cfg, system, user) {
66
+ async function callAnthropic(cfg, system, user, files = []) {
66
67
  const base = stripVersionSuffix(cfg.baseUrl, ["v1"]);
67
68
  const res = await fetch(`${base}/v1/messages`, {
68
69
  method: "POST",
@@ -75,7 +76,7 @@ async function callAnthropic(cfg, system, user) {
75
76
  model: cfg.modelSlug,
76
77
  max_tokens: 4096,
77
78
  system,
78
- messages: [{ role: "user", content: user }],
79
+ messages: [{ role: "user", content: anthropicContent(user, files) }],
79
80
  }),
80
81
  });
81
82
  const body = await res.text();
@@ -91,15 +92,43 @@ async function callAnthropic(cfg, system, user) {
91
92
  outputTokens: json?.usage?.output_tokens ?? null,
92
93
  };
93
94
  }
94
- async function callProvider(cfg, system, user) {
95
+ // Zonder bijlagen blijft de inhoud exact gelijk aan vroeger (alleen tekst).
96
+ export function geminiParts(user, files) {
97
+ return [
98
+ { text: user },
99
+ ...files.map((f) => ({ inline_data: { mime_type: f.mimeType, data: f.data } })),
100
+ ];
101
+ }
102
+ export function openAiContent(user, files) {
103
+ if (files.length === 0)
104
+ return user;
105
+ return [
106
+ { type: "text", text: user },
107
+ ...files.map((f, i) => f.mimeType === "application/pdf"
108
+ ? { type: "file", file: { filename: `bijlage-${i + 1}.pdf`, file_data: `data:${f.mimeType};base64,${f.data}` } }
109
+ : { type: "image_url", image_url: { url: `data:${f.mimeType};base64,${f.data}` } }),
110
+ ];
111
+ }
112
+ export function anthropicContent(user, files) {
113
+ if (files.length === 0)
114
+ return user;
115
+ return [
116
+ ...files.map((f) => ({
117
+ type: f.mimeType === "application/pdf" ? "document" : "image",
118
+ source: { type: "base64", media_type: f.mimeType, data: f.data },
119
+ })),
120
+ { type: "text", text: user },
121
+ ];
122
+ }
123
+ async function callProvider(cfg, system, user, files = []) {
95
124
  switch (cfg.providerSlug) {
96
125
  case "gemini":
97
126
  case "google":
98
- return callGemini(cfg, system, user);
127
+ return callGemini(cfg, system, user, files);
99
128
  case "openai":
100
- return callOpenAi(cfg, system, user);
129
+ return callOpenAi(cfg, system, user, files);
101
130
  case "anthropic":
102
- return callAnthropic(cfg, system, user);
131
+ return callAnthropic(cfg, system, user, files);
103
132
  default:
104
133
  throw new Error(`Provider '${cfg.providerSlug}' wordt nog niet ondersteund.`);
105
134
  }
@@ -291,20 +320,49 @@ export class AiUnavailableError extends Error {
291
320
  this.attempts = attempts;
292
321
  }
293
322
  }
323
+ /**
324
+ * Leest de mail uit, met bijlagen als die er zijn. Weigert de AI de bijlagen
325
+ * met een blijvende fout, dan volgt één nieuwe poging zonder bijlagen en
326
+ * zonder bijlage-promptzin; `readAttachmentIndexes` is dan leeg.
327
+ */
294
328
  export async function parseOrderEmail(admin, args) {
329
+ const files = Array.isArray(args.files) ? args.files : [];
330
+ if (files.length === 0) {
331
+ const res = await parseOrderEmailOnce(admin, args);
332
+ return { ...res, readAttachmentIndexes: [] };
333
+ }
334
+ try {
335
+ const res = await parseOrderEmailOnce(admin, args, files);
336
+ return { ...res, readAttachmentIndexes: files.map((f) => f.index) };
337
+ }
338
+ catch (e) {
339
+ const transient = e?.name === "AiUnavailableError" ? Boolean(e.transient) : isTransientAiError(String(e?.message ?? e));
340
+ if (transient)
341
+ throw e;
342
+ console.error("[mailbox] AI weigerde bijlagen; opnieuw zonder bijlagen", {
343
+ types: files.map((f) => f.mimeType),
344
+ reason: String(e?.message ?? e).slice(0, 300),
345
+ });
346
+ const res = await parseOrderEmailOnce(admin, args);
347
+ return { ...res, readAttachmentIndexes: [] };
348
+ }
349
+ }
350
+ async function parseOrderEmailOnce(admin, args, files = []) {
295
351
  const { resolveModuleAiAll } = await import("./ai-config.server.js");
296
352
  const resolution = await resolveModuleAiAll(admin, MAILBOX_AI_MODULE_KEY);
297
353
  if (!resolution.ok)
298
354
  throw new AiUnavailableError(resolution.reason, false, 0);
299
355
  const configs = resolution.configs;
300
356
  const primary = configs[0];
301
- const { system, user } = buildPrompt(args.bodyText, args.subject, args.templateFields, args.productCatalog, {
357
+ const prompt = buildPrompt(args.bodyText, args.subject, args.templateFields, args.productCatalog, {
302
358
  deliveryLocations: args.deliveryLocations,
303
359
  plantTypes: args.plantTypes,
304
360
  requiredFields: args.requiredFields,
305
361
  systemPrompt: args.systemPrompt,
306
362
  now: args.now,
307
363
  });
364
+ const user = prompt.user;
365
+ const system = files.length > 0 ? `${prompt.system}\n\n${ATTACHMENT_PROMPT_LINE}` : prompt.system;
308
366
  // Per model een begrensd aantal pogingen (instelling) met exponentiële
309
367
  // backoff; daarna uitwijken naar het volgende model in de keten.
310
368
  const MAX_ATTEMPTS_PER_MODEL = Math.max(1, Math.min(5, Number(args.maxAttemptsPerModel ?? 3)));
@@ -316,7 +374,7 @@ export async function parseOrderEmail(admin, args) {
316
374
  attemptNo++;
317
375
  const started = Date.now();
318
376
  try {
319
- const completion = await callProvider(cfg, system, user);
377
+ const completion = await callProvider(cfg, system, user, files);
320
378
  const parsed = normalize(extractJson(completion.text), args.productCatalog);
321
379
  const durationMs = Date.now() - started;
322
380
  await logAiCall(admin, {
@@ -355,6 +413,12 @@ export async function parseOrderEmail(admin, args) {
355
413
  errorCode: errorStatus(lastMessage) ? String(errorStatus(lastMessage)) : null,
356
414
  attempt: attemptNo,
357
415
  });
416
+ if (files.length > 0) {
417
+ console.error("[mailbox] AI-fout met bijlagen", {
418
+ provider: cfg.providerSlug,
419
+ types: files.map((f) => f.mimeType),
420
+ });
421
+ }
358
422
  if (!lastTransient)
359
423
  break; // permanente fout: direct volgend model
360
424
  if (i < MAX_ATTEMPTS_PER_MODEL - 1)
@@ -0,0 +1,22 @@
1
+ import type { EmailAttachment } from "./mailbox-body.js";
2
+ export declare const ATTACHMENT_BUCKET = "email-auth-attachments";
3
+ export declare const AI_MAX_ATTACHMENTS = 3;
4
+ export declare const AI_MAX_ATTACHMENT_BYTES: number;
5
+ export declare const AI_MAX_TOTAL_BYTES: number;
6
+ export declare const INLINE_URL_SECONDS: number;
7
+ export declare const DOWNLOAD_URL_SECONDS: number;
8
+ export declare const AI_READABLE_TYPES: Set<string>;
9
+ /** Ingesloten afbeelding (cid:) in de mailtekst, geen losse bijlage. */
10
+ export declare function isInlineAttachment(a: EmailAttachment): boolean;
11
+ export declare function normalizedMime(a: EmailAttachment): string;
12
+ /** Type en status laten toe dat de AI de bijlage leest (los van de totaalgrens). */
13
+ export declare function isAiReadableAttachment(a: EmailAttachment): boolean;
14
+ /** Indexen (in de originele lijst) die naar de AI gaan: max 3 en samen max 10 MB. */
15
+ export declare function selectAiAttachments(list: EmailAttachment[]): number[];
16
+ /**
17
+ * True als er een echte (niet-ingesloten) bijlage is die de AI niet heeft
18
+ * gelezen. `readIndexes` = bijlagen die écht zijn meegestuurd en gelezen.
19
+ */
20
+ export declare function computeAttachmentUnread(list: EmailAttachment[], hasAttachments: boolean, readIndexes: ReadonlySet<number>): boolean;
21
+ export declare function skippedLabel(reason: string | null | undefined): string | null;
22
+ //# sourceMappingURL=mailbox-attachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailbox-attachments.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-attachments.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,eAAO,MAAM,iBAAiB,2BAA2B,CAAC;AAC1D,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,uBAAuB,QAAmB,CAAC;AACxD,eAAO,MAAM,kBAAkB,QAAmB,CAAC;AACnD,eAAO,MAAM,kBAAkB,QAAU,CAAC;AAC1C,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C,eAAO,MAAM,iBAAiB,aAAwE,CAAC;AAEvG,wEAAwE;AACxE,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,eAAe,GAAG,OAAO,CAE9D;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAGzD;AAED,oFAAoF;AACpF,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,eAAe,GAAG,OAAO,CAMlE;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,CAWrE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,eAAe,EAAE,EACvB,cAAc,EAAE,OAAO,EACvB,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B,OAAO,CAGT;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAI7E"}
@@ -0,0 +1,58 @@
1
+ export const ATTACHMENT_BUCKET = "email-auth-attachments";
2
+ export const AI_MAX_ATTACHMENTS = 3;
3
+ export const AI_MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
4
+ export const AI_MAX_TOTAL_BYTES = 10 * 1024 * 1024;
5
+ export const INLINE_URL_SECONDS = 60 * 60;
6
+ export const DOWNLOAD_URL_SECONDS = 5 * 60;
7
+ export const AI_READABLE_TYPES = new Set(["application/pdf", "image/png", "image/jpeg", "image/webp"]);
8
+ /** Ingesloten afbeelding (cid:) in de mailtekst, geen losse bijlage. */
9
+ export function isInlineAttachment(a) {
10
+ return a.isInline === true;
11
+ }
12
+ export function normalizedMime(a) {
13
+ const t = String(a.contentType ?? "").toLowerCase().split(";")[0].trim();
14
+ return t === "image/jpg" ? "image/jpeg" : t;
15
+ }
16
+ /** Type en status laten toe dat de AI de bijlage leest (los van de totaalgrens). */
17
+ export function isAiReadableAttachment(a) {
18
+ if (isInlineAttachment(a))
19
+ return false;
20
+ if (!a.storagePath || a.skippedReason)
21
+ return false;
22
+ if (!AI_READABLE_TYPES.has(normalizedMime(a)))
23
+ return false;
24
+ if (typeof a.size === "number" && a.size > AI_MAX_ATTACHMENT_BYTES)
25
+ return false;
26
+ return true;
27
+ }
28
+ /** Indexen (in de originele lijst) die naar de AI gaan: max 3 en samen max 10 MB. */
29
+ export function selectAiAttachments(list) {
30
+ const out = [];
31
+ let total = 0;
32
+ list.forEach((a, i) => {
33
+ if (out.length >= AI_MAX_ATTACHMENTS || !isAiReadableAttachment(a))
34
+ return;
35
+ const size = typeof a.size === "number" && a.size > 0 ? a.size : 0;
36
+ if (total + size > AI_MAX_TOTAL_BYTES)
37
+ return;
38
+ total += size;
39
+ out.push(i);
40
+ });
41
+ return out;
42
+ }
43
+ /**
44
+ * True als er een echte (niet-ingesloten) bijlage is die de AI niet heeft
45
+ * gelezen. `readIndexes` = bijlagen die écht zijn meegestuurd en gelezen.
46
+ */
47
+ export function computeAttachmentUnread(list, hasAttachments, readIndexes) {
48
+ if (list.length === 0)
49
+ return hasAttachments;
50
+ return list.some((a, i) => !isInlineAttachment(a) && !readIndexes.has(i));
51
+ }
52
+ export function skippedLabel(reason) {
53
+ if (reason === "te_groot")
54
+ return "Te groot om op te halen";
55
+ if (reason === "ophalen_mislukt")
56
+ return "Bijlage kon niet worden opgehaald";
57
+ return null;
58
+ }
@@ -0,0 +1,13 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ import type { EmailAttachment } from "./mailbox-body.js";
3
+ export interface AiFile {
4
+ index: number;
5
+ mimeType: string;
6
+ data: string;
7
+ }
8
+ /**
9
+ * Downloadt de bijlagen die de AI mag lezen. Ontbrekende bestanden of een
10
+ * ontbrekende bucket worden overgeslagen (tellen dan als niet gelezen).
11
+ */
12
+ export declare function loadAiAttachmentFiles(admin: SupabaseClient<any>, list: EmailAttachment[]): Promise<AiFile[]>;
13
+ //# sourceMappingURL=mailbox-attachments.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailbox-attachments.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-attachments.server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAStD,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAYD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,EAC1B,IAAI,EAAE,eAAe,EAAE,GACtB,OAAO,CAAC,MAAM,EAAE,CAAC,CA6BnB"}
@@ -0,0 +1,45 @@
1
+ import { ATTACHMENT_BUCKET, AI_MAX_ATTACHMENT_BYTES, AI_MAX_TOTAL_BYTES, normalizedMime, selectAiAttachments, } from "./mailbox-attachments.js";
2
+ function toBase64(buf) {
3
+ const bytes = new Uint8Array(buf);
4
+ let bin = "";
5
+ const chunk = 0x8000;
6
+ for (let i = 0; i < bytes.length; i += chunk) {
7
+ bin += String.fromCharCode(...bytes.subarray(i, i + chunk));
8
+ }
9
+ return btoa(bin);
10
+ }
11
+ /**
12
+ * Downloadt de bijlagen die de AI mag lezen. Ontbrekende bestanden of een
13
+ * ontbrekende bucket worden overgeslagen (tellen dan als niet gelezen).
14
+ */
15
+ export async function loadAiAttachmentFiles(admin, list) {
16
+ const files = [];
17
+ let total = 0;
18
+ for (const index of selectAiAttachments(list)) {
19
+ const a = list[index];
20
+ try {
21
+ const { data, error } = await admin.storage.from(ATTACHMENT_BUCKET).download(a.storagePath);
22
+ if (error || !data) {
23
+ console.error("[mailbox] bijlage niet beschikbaar, overgeslagen", {
24
+ type: normalizedMime(a),
25
+ reason: error?.message ?? "leeg",
26
+ });
27
+ continue;
28
+ }
29
+ const buf = await data.arrayBuffer();
30
+ if (buf.byteLength > AI_MAX_ATTACHMENT_BYTES || total + buf.byteLength > AI_MAX_TOTAL_BYTES) {
31
+ console.error("[mailbox] bijlage boven grens, overgeslagen", { type: normalizedMime(a), bytes: buf.byteLength });
32
+ continue;
33
+ }
34
+ total += buf.byteLength;
35
+ files.push({ index, mimeType: normalizedMime(a), data: toBase64(buf) });
36
+ }
37
+ catch (e) {
38
+ console.error("[mailbox] bijlage ophalen mislukt, overgeslagen", {
39
+ type: normalizedMime(a),
40
+ reason: String(e?.message ?? e).slice(0, 200),
41
+ });
42
+ }
43
+ }
44
+ return files;
45
+ }
@@ -26,8 +26,11 @@ export type EmailAttachment = {
26
26
  contentId?: string | null;
27
27
  contentBytes?: string | null;
28
28
  url?: string | null;
29
+ storagePath?: string | null;
30
+ isInline?: boolean;
31
+ skippedReason?: string | null;
29
32
  };
30
33
  export declare function normalizeAttachments(value: unknown): EmailAttachment[];
31
34
  /** Vervangt `cid:`-verwijzingen door data-URL's van de bijbehorende bijlage. */
32
- export declare function inlineCidImages(html: string, attachments: EmailAttachment[]): string;
35
+ export declare function inlineCidImages(html: string, attachments: EmailAttachment[], signedUrls?: ReadonlyMap<number, string>): string;
33
36
  //# sourceMappingURL=mailbox-body.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mailbox-body.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-body.ts"],"names":[],"mappings":"AAIA,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsB/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;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAWzE;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE3E,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,CAenE;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,CAgBtE;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,MAAM,CAUpF"}
1
+ {"version":3,"file":"mailbox-body.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-body.ts"],"names":[],"mappings":"AAIA,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsB/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;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAWzE;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE3E,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,CAenE;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,CAsBtE;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,eAAe,EAAE,EAC9B,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACvC,MAAM,CAgBR"}
@@ -87,7 +87,13 @@ export function normalizeAttachments(value) {
87
87
  if (!v || typeof v !== "object")
88
88
  continue;
89
89
  const o = v;
90
+ const cid = o["contentId"] ?? o["content_id"] ?? o["cid"] ?? null;
91
+ const inlineFlag = o["isInline"] ?? o["is_inline"] ?? o["inline"];
90
92
  out.push({
93
+ storagePath: o["storagePath"] ?? o["storage_path"] ?? null,
94
+ // Oud formaat kende geen isInline: daar gold een contentId als ingesloten.
95
+ isInline: typeof inlineFlag === "boolean" ? inlineFlag : !!cid,
96
+ skippedReason: o["skippedReason"] ?? o["skipped_reason"] ?? null,
91
97
  name: String(o["name"] ?? o["filename"] ?? o["fileName"] ?? "bijlage"),
92
98
  contentType: o["contentType"] ?? o["content_type"] ?? o["mimeType"] ?? null,
93
99
  size: typeof o["size"] === "number" ? o["size"] : Number(o["size"]) || null,
@@ -99,16 +105,23 @@ export function normalizeAttachments(value) {
99
105
  return out;
100
106
  }
101
107
  /** Vervangt `cid:`-verwijzingen door data-URL's van de bijbehorende bijlage. */
102
- export function inlineCidImages(html, attachments) {
108
+ export function inlineCidImages(html, attachments, signedUrls) {
103
109
  if (!html || attachments.length === 0)
104
110
  return html;
105
111
  let out = html;
106
- for (const a of attachments) {
107
- if (!a.contentId || !a.contentBytes)
108
- continue;
112
+ attachments.forEach((a, i) => {
113
+ if (!a.contentId)
114
+ return;
115
+ const signed = signedUrls?.get(i);
116
+ const target = signed
117
+ ? signed
118
+ : a.contentBytes
119
+ ? `data:${a.contentType || "application/octet-stream"};base64,${a.contentBytes}`
120
+ : null;
121
+ if (!target)
122
+ return;
109
123
  const cid = a.contentId.replace(/^<|>$/g, "");
110
- const dataUrl = `data:${a.contentType || "application/octet-stream"};base64,${a.contentBytes}`;
111
- out = out.replace(new RegExp(`cid:${cid.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "gi"), dataUrl);
112
- }
124
+ out = out.replace(new RegExp(`cid:${cid.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "gi"), target);
125
+ });
113
126
  return out;
114
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mailbox-ingest.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ingest.server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mailbox-ingest.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ingest.server.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,8 @@
8
8
  // GEEN imports van "@/integrations/supabase/client.server" op module-scope —
9
9
  // die worden per aanroep dynamisch geladen (client-reachable modules mogen
10
10
  // server-only helpers niet direct binnenhalen).
11
+ import { normalizeAttachments } from "./mailbox-body.js";
12
+ import { computeAttachmentUnread, selectAiAttachments } from "./mailbox-attachments.js";
11
13
  import { emailBodyText } from "./mailbox-body.js";
12
14
  import { detectLoadCarrierInText, isLoadCarrierAllowed, loadCarrierLabel, normalizeAllowedLoadCarriers, } from "./load-carriers.js";
13
15
  import { classifyMessage, extractLatestPart } from "./mailbox-filter.js";
@@ -268,6 +270,12 @@ async function aiParseMessage(admin, args) {
268
270
  throw new Error("Bericht bevat geen tekst om te lezen.");
269
271
  const ctx = await buildAiContext(admin, args.customerId, `${args.subject}\n${args.text}`, args.settings.max_candidates, args.idx);
270
272
  const { parseOrderEmail } = await import("./mailbox-ai.server.js");
273
+ const attList = normalizeAttachments(args.attachments);
274
+ let files = [];
275
+ if (selectAiAttachments(attList).length > 0) {
276
+ const { loadAiAttachmentFiles } = await import("./mailbox-attachments.server.js");
277
+ files = await loadAiAttachmentFiles(admin, attList);
278
+ }
271
279
  const res = await parseOrderEmail(admin, {
272
280
  bodyText: args.text,
273
281
  subject: args.subject,
@@ -279,6 +287,7 @@ async function aiParseMessage(admin, args) {
279
287
  systemPrompt: ctx.settings.systemPrompt,
280
288
  now: args.receivedAt,
281
289
  maxAttemptsPerModel: args.settings.max_retry_attempts,
290
+ files,
282
291
  });
283
292
  return {
284
293
  parsed: res.parsed,
@@ -287,6 +296,7 @@ async function aiParseMessage(admin, args) {
287
296
  transient: false,
288
297
  requiredFields: ctx.settings.requiredFields,
289
298
  candidateIds: new Set(ctx.candidates.map((c) => c.id)),
299
+ readAttachmentIndexes: Array.isArray(res.readAttachmentIndexes) ? res.readAttachmentIndexes : [],
290
300
  };
291
301
  }
292
302
  catch (e) {
@@ -529,11 +539,9 @@ async function loadMessage(admin, messageId) {
529
539
  .maybeSingle();
530
540
  return data ?? null;
531
541
  }
532
- /** Bijlagen die geen ingesloten afbeelding zijn worden niet automatisch gelezen. */
533
- function hasUnreadAttachment(msg) {
534
- const list = Array.isArray(msg.attachments) ? msg.attachments : [];
535
- const real = list.filter((a) => !(a?.isInline === true || a?.inline === true || a?.contentId));
536
- return real.length > 0 || (msg.has_attachments === true && list.length === 0);
542
+ /** Echte bijlage die de AI niet heeft kunnen lezen (type, grootte, ontbrekend of geweigerd). */
543
+ function hasUnreadAttachment(msg, readIndexes = []) {
544
+ return computeAttachmentUnread(normalizeAttachments(msg.attachments), msg.has_attachments === true, new Set(readIndexes));
537
545
  }
538
546
  async function senderMatch(admin, email, s) {
539
547
  const match = await matchCustomerByEmail(admin, email);
@@ -635,6 +643,7 @@ async function analyzeAndBuild(admin, msg, customerId, text, s) {
635
643
  receivedAt: msg.received_at ? new Date(msg.received_at) : new Date(),
636
644
  settings: s,
637
645
  idx,
646
+ attachments: msg.attachments,
638
647
  });
639
648
  const parsed = attempt.parsed;
640
649
  const fieldMeta = {};
@@ -709,7 +718,7 @@ async function analyzeAndBuild(admin, msg, customerId, text, s) {
709
718
  load_carrier_detected: lc.detectedLabel,
710
719
  field_meta: fieldMeta,
711
720
  duplicate_of: duplicate,
712
- attachment_unread: hasUnreadAttachment(msg),
721
+ attachment_unread: hasUnreadAttachment(msg, attempt.readAttachmentIndexes ?? []),
713
722
  readiness: readiness(validation, lines, !!loc, !!customerId),
714
723
  thresholds: { green: Number(s.conf_green), orange: Number(s.conf_orange) },
715
724
  blockedMessageStatus: lc.blocked,
@@ -92,6 +92,24 @@ export declare const getProposalDetail: import("@tanstack/start-client-core").Op
92
92
  }, Promise<{
93
93
  item: any;
94
94
  }>>;
95
+ /** Korte downloadlink (5 minuten) voor één bijlage van een bericht. */
96
+ export declare const getMailboxAttachmentUrl: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
97
+ supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
98
+ userId: string;
99
+ claims: import("@supabase/auth-js").User;
100
+ }, undefined, undefined, undefined>], (input: unknown) => {
101
+ messageId: string;
102
+ index: number;
103
+ }, Promise<{
104
+ url: string | null;
105
+ error: "not_found";
106
+ } | {
107
+ url: null;
108
+ error: "not_available";
109
+ } | {
110
+ url: string | null;
111
+ error: null;
112
+ }>>;
95
113
  export declare const listAllowedLoadCarriers: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
96
114
  supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
97
115
  userId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js
7
7
  import { createClient } from "@supabase/supabase-js";
8
8
  import { z } from "zod";
9
9
  import { emailBodyText, inlineCidImages, normalizeAddressList, normalizeAttachments, sanitizeEmailHtml, stripInvisible, } from "./mailbox-body.js";
10
+ import { ATTACHMENT_BUCKET, DOWNLOAD_URL_SECONDS, INLINE_URL_SECONDS } from "./mailbox-attachments.js";
10
11
  import { EMPTY_LOAD_CARRIERS, isLoadCarrierAllowed, loadCarrierLabel, normalizeAllowedLoadCarriers, } from "./load-carriers.js";
11
12
  // ─── Actieve mailbox (uit gedeelde e-mail-auth module) ──────────────────────
12
13
  // De RPC email_auth_for_purpose vereist een ingelogde gebruiker (anders 42501).
@@ -204,7 +205,7 @@ export const getProposalDetail = createServerFn({ method: "GET" })
204
205
  const sel = (s) => s;
205
206
  const { data: row, error } = await supabaseAdmin
206
207
  .from("mailbox_order_proposals")
207
- .select(sel("*, mailbox_messages(subject, from_email, from_name, body_text, body_html, received_at, connection_id, account_email, to_recipients, cc_recipients, attachments, has_attachments), " +
208
+ .select(sel("*, mailbox_messages(id, subject, from_email, from_name, body_text, body_html, received_at, connection_id, account_email, to_recipients, cc_recipients, attachments, has_attachments), " +
208
209
  "mailbox_proposal_lines(*), mailbox_proposal_field_values(*)"))
209
210
  .eq("id", data.proposalId)
210
211
  .maybeSingle();
@@ -216,7 +217,21 @@ export const getProposalDetail = createServerFn({ method: "GET" })
216
217
  if (!m)
217
218
  return { item: row };
218
219
  const attachments = normalizeAttachments(m.attachments);
219
- const html = inlineCidImages(sanitizeEmailHtml(m.body_html), attachments);
220
+ // Ingesloten afbeeldingen: korte ondertekende link (1 uur) uit de privé bucket.
221
+ const signed = new Map();
222
+ await Promise.all(attachments.map(async (a, i) => {
223
+ if (!a.contentId || !a.storagePath)
224
+ return;
225
+ const { data: s, error: e } = await supabaseAdmin.storage
226
+ .from(ATTACHMENT_BUCKET)
227
+ .createSignedUrl(a.storagePath, INLINE_URL_SECONDS);
228
+ if (e || !s?.signedUrl) {
229
+ console.error("[mailbox] ingesloten afbeelding niet beschikbaar", { reason: e?.message ?? "leeg" });
230
+ return;
231
+ }
232
+ signed.set(i, s.signedUrl);
233
+ }));
234
+ const html = inlineCidImages(sanitizeEmailHtml(m.body_html), attachments, signed);
220
235
  return {
221
236
  item: {
222
237
  ...row,
@@ -233,12 +248,51 @@ export const getProposalDetail = createServerFn({ method: "GET" })
233
248
  size: a.size ?? null,
234
249
  contentId: a.contentId ?? null,
235
250
  url: a.url ?? null,
251
+ isInline: a.isInline === true,
252
+ downloadable: !!a.storagePath && !a.skippedReason,
253
+ skippedReason: a.skippedReason ?? null,
236
254
  })),
237
255
  has_attachments: m.has_attachments === true || attachments.length > 0,
238
256
  },
239
257
  },
240
258
  };
241
259
  });
260
+ /** Korte downloadlink (5 minuten) voor één bijlage van een bericht. */
261
+ export const getMailboxAttachmentUrl = createServerFn({ method: "POST" })
262
+ .middleware([requireSupabaseAuth])
263
+ .inputValidator((input) => z.object({ messageId: z.string().uuid(), index: z.number().int().min(0).max(500) }).parse(input))
264
+ .handler(async ({ data }) => {
265
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
266
+ // Zelfde regel als getProposalDetail: ingelogd en het bericht hoort bij een voorstel.
267
+ const { data: prop, error: pe } = await supabaseAdmin
268
+ .from("mailbox_order_proposals")
269
+ .select("id")
270
+ .eq("message_id", data.messageId)
271
+ .limit(1)
272
+ .maybeSingle();
273
+ if (pe)
274
+ throw new Error(pe.message);
275
+ if (!prop)
276
+ return { url: null, error: "not_found" };
277
+ const { data: m, error } = await supabaseAdmin
278
+ .from("mailbox_messages")
279
+ .select("attachments")
280
+ .eq("id", data.messageId)
281
+ .maybeSingle();
282
+ if (error)
283
+ throw new Error(error.message);
284
+ const a = normalizeAttachments(m?.attachments)[data.index];
285
+ if (!a || !a.storagePath || a.skippedReason)
286
+ return { url: null, error: "not_available" };
287
+ const { data: s, error: se } = await supabaseAdmin.storage
288
+ .from(ATTACHMENT_BUCKET)
289
+ .createSignedUrl(a.storagePath, DOWNLOAD_URL_SECONDS, { download: a.name });
290
+ if (se || !s?.signedUrl) {
291
+ console.error("[mailbox] bijlage niet meer beschikbaar", { reason: se?.message ?? "leeg" });
292
+ return { url: null, error: "not_available" };
293
+ }
294
+ return { url: s.signedUrl, error: null };
295
+ });
242
296
  // ─── Ladingdragers uit de Logistics Engine ─────────────────────────────────
243
297
  // Nooit cachen: de instelling kan op elk moment wijzigen.
244
298
  export const listAllowedLoadCarriers = createServerFn({ method: "GET" })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/mailbox-orders",
3
- "version": "1.0.61",
3
+ "version": "1.0.64",
4
4
  "private": false,
5
5
  "sideEffects": false,
6
6
  "type": "module",