opensentinel 2.1.1 → 3.1.1
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/README.md +354 -283
- package/dist/archiver-AVNBYCKQ.js +15340 -0
- package/dist/archiver-AVNBYCKQ.js.map +1 -0
- package/dist/audit-logger-OBPR7CRO.js +22 -0
- package/dist/auth-UOX5K2BE.js +18 -0
- package/dist/autonomy-ZXDBDQUJ.js +86 -0
- package/dist/autonomy-ZXDBDQUJ.js.map +1 -0
- package/dist/aws-s3-Q4LLZZPD.js +146 -0
- package/dist/aws-s3-Q4LLZZPD.js.map +1 -0
- package/dist/backup-restore-PZ7CYYB7.js +16 -0
- package/dist/blocks-R3PODY47.js +23 -0
- package/dist/bot-QRARP4UN.js +36 -0
- package/dist/brain-7XLLM3KC.js +56 -0
- package/dist/camera-monitor-M5CYKUU4.js +335 -0
- package/dist/camera-monitor-M5CYKUU4.js.map +1 -0
- package/dist/{charts-MMXM6BWW.js → charts-V7ARZNKF.js} +2 -2
- package/dist/chunk-22VGGA7S.js +330 -0
- package/dist/chunk-22VGGA7S.js.map +1 -0
- package/dist/chunk-35WYTA3C.js +382 -0
- package/dist/chunk-35WYTA3C.js.map +1 -0
- package/dist/chunk-3E2PSU2C.js +146 -0
- package/dist/chunk-3E2PSU2C.js.map +1 -0
- package/dist/{chunk-L3F43VPB.js → chunk-4GLYY4NN.js} +2 -2
- package/dist/{chunk-L3F43VPB.js.map → chunk-4GLYY4NN.js.map} +1 -1
- package/dist/{chunk-L3PDU3XN.js → chunk-4UOE5TUZ.js} +4 -4
- package/dist/{chunk-6SNHU3CY.js → chunk-66OJ3WB4.js} +2 -2
- package/dist/chunk-6KONMXQ6.js +297 -0
- package/dist/chunk-6KONMXQ6.js.map +1 -0
- package/dist/chunk-6PMVAAA7.js +196 -0
- package/dist/chunk-6PMVAAA7.js.map +1 -0
- package/dist/chunk-766ASQWE.js +32620 -0
- package/dist/chunk-766ASQWE.js.map +1 -0
- package/dist/chunk-7WQO5J2M.js +29 -0
- package/dist/chunk-7WQO5J2M.js.map +1 -0
- package/dist/chunk-APHSRMBS.js +148 -0
- package/dist/chunk-APHSRMBS.js.map +1 -0
- package/dist/{chunk-4LVWXUNC.js → chunk-AYUKPTSM.js} +57 -39
- package/dist/chunk-AYUKPTSM.js.map +1 -0
- package/dist/chunk-BIPYADGB.js +84 -0
- package/dist/chunk-BIPYADGB.js.map +1 -0
- package/dist/chunk-BRBWNV65.js +457 -0
- package/dist/chunk-BRBWNV65.js.map +1 -0
- package/dist/chunk-BXZ6EA52.js +382 -0
- package/dist/chunk-BXZ6EA52.js.map +1 -0
- package/dist/chunk-EVE7MIIY.js +290 -0
- package/dist/chunk-EVE7MIIY.js.map +1 -0
- package/dist/chunk-F3TTNID2.js +138 -0
- package/dist/chunk-F3TTNID2.js.map +1 -0
- package/dist/chunk-H5RQOFO2.js +190 -0
- package/dist/chunk-H5RQOFO2.js.map +1 -0
- package/dist/chunk-HN3F4WSW.js +145 -0
- package/dist/chunk-HN3F4WSW.js.map +1 -0
- package/dist/{chunk-6DRDKB45.js → chunk-I6BDYQIG.js} +20 -9
- package/dist/chunk-I6BDYQIG.js.map +1 -0
- package/dist/chunk-IZJMVV7O.js +347 -0
- package/dist/chunk-IZJMVV7O.js.map +1 -0
- package/dist/chunk-KM22GV7G.js +211 -0
- package/dist/chunk-KM22GV7G.js.map +1 -0
- package/dist/chunk-MGFBLVR7.js +103 -0
- package/dist/chunk-MGFBLVR7.js.map +1 -0
- package/dist/chunk-MQJ2ECQT.js +228 -0
- package/dist/chunk-MQJ2ECQT.js.map +1 -0
- package/dist/{chunk-F6QUZQGI.js → chunk-MXAPLSJ5.js} +2 -2
- package/dist/{chunk-GK3E2I7A.js → chunk-NHMBTUMW.js} +2 -2
- package/dist/chunk-NPRTSZIF.js +131 -0
- package/dist/chunk-NPRTSZIF.js.map +1 -0
- package/dist/chunk-O7IH7JTI.js +1898 -0
- package/dist/chunk-O7IH7JTI.js.map +1 -0
- package/dist/chunk-OCVQGBJK.js +293 -0
- package/dist/chunk-OCVQGBJK.js.map +1 -0
- package/dist/chunk-P6QINGFL.js +332 -0
- package/dist/chunk-P6QINGFL.js.map +1 -0
- package/dist/chunk-PHDZKPNE.js +91 -0
- package/dist/chunk-PHDZKPNE.js.map +1 -0
- package/dist/chunk-PLDDJCW6.js +49 -0
- package/dist/chunk-PTGTGXV2.js +164 -0
- package/dist/chunk-PTGTGXV2.js.map +1 -0
- package/dist/chunk-REMIY4U2.js +171 -0
- package/dist/chunk-REMIY4U2.js.map +1 -0
- package/dist/chunk-RZ4YESBG.js +141 -0
- package/dist/chunk-RZ4YESBG.js.map +1 -0
- package/dist/chunk-SAX5MHK4.js +111 -0
- package/dist/chunk-SAX5MHK4.js.map +1 -0
- package/dist/{chunk-GVJVEWHI.js → chunk-SJSUSJ47.js} +2 -2
- package/dist/chunk-SPPMCAKG.js +777 -0
- package/dist/chunk-SPPMCAKG.js.map +1 -0
- package/dist/chunk-SVAPX2XN.js +2441 -0
- package/dist/chunk-SVAPX2XN.js.map +1 -0
- package/dist/chunk-TVEWKIK3.js +452 -0
- package/dist/chunk-TVEWKIK3.js.map +1 -0
- package/dist/{chunk-HH2HBTQM.js → chunk-TYAGMJNV.js} +5 -5
- package/dist/{chunk-JXUP2X7V.js → chunk-VEHFVBLI.js} +2 -2
- package/dist/chunk-VNX5GMTN.js +128 -0
- package/dist/chunk-VNX5GMTN.js.map +1 -0
- package/dist/chunk-VRD5CYRL.js +1568 -0
- package/dist/chunk-VRD5CYRL.js.map +1 -0
- package/dist/chunk-WLUHNG6X.js +122 -0
- package/dist/chunk-WLUHNG6X.js.map +1 -0
- package/dist/chunk-WRAKK6K6.js +265 -0
- package/dist/chunk-WRAKK6K6.js.map +1 -0
- package/dist/chunk-XKYRH4FM.js +681 -0
- package/dist/chunk-XKYRH4FM.js.map +1 -0
- package/dist/{chunk-GUBEEYDW.js → chunk-XMCVRVTF.js} +2 -2
- package/dist/{chunk-GUBEEYDW.js.map → chunk-XMCVRVTF.js.map} +1 -1
- package/dist/chunk-ZLZKF2PM.js +310 -0
- package/dist/chunk-ZLZKF2PM.js.map +1 -0
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-ZQSFPMOB.js +21 -0
- package/dist/clipboard-manager-TEO2GEDN.js +24 -0
- package/dist/commands/setup.js +3 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.js +3 -3
- package/dist/commands/status.js +2 -2
- package/dist/commands/stop.js +2 -2
- package/dist/commands/utils.js +2 -2
- package/dist/cron-explain-HHQKPD3M.js +16 -0
- package/dist/crypto-4AP47IKC.js +14 -0
- package/dist/crypto-4AP47IKC.js.map +1 -0
- package/dist/databases-37X4CI2Y.js +21 -0
- package/dist/databases-37X4CI2Y.js.map +1 -0
- package/dist/discord-B3HUPGQ6.js +70 -0
- package/dist/discord-B3HUPGQ6.js.map +1 -0
- package/dist/dist-UISMLMFN.js +21847 -0
- package/dist/dist-UISMLMFN.js.map +1 -0
- package/dist/email-K7LO2IPB.js +268 -0
- package/dist/email-K7LO2IPB.js.map +1 -0
- package/dist/enhanced-retrieval-DNLLEM4Z.js +753 -0
- package/dist/enhanced-retrieval-DNLLEM4Z.js.map +1 -0
- package/dist/enrichment-pipeline-MNHNW65K.js +13 -0
- package/dist/enrichment-pipeline-MNHNW65K.js.map +1 -0
- package/dist/entity-resolution-Y3IUWEAT.js +24 -0
- package/dist/entity-resolution-Y3IUWEAT.js.map +1 -0
- package/dist/env-IWXUVTCB.js +12 -0
- package/dist/env-IWXUVTCB.js.map +1 -0
- package/dist/google-workspace-DKWUVNGC.js +169 -0
- package/dist/google-workspace-DKWUVNGC.js.map +1 -0
- package/dist/hash-tool-ULQYD7B5.js +22 -0
- package/dist/hash-tool-ULQYD7B5.js.map +1 -0
- package/dist/heartbeat-monitor-GCISLXI3.js +22 -0
- package/dist/heartbeat-monitor-GCISLXI3.js.map +1 -0
- package/dist/image-generation-OSU7FP6F.js +486 -0
- package/dist/image-generation-OSU7FP6F.js.map +1 -0
- package/dist/imessage-NGA2XF2V.js +35 -0
- package/dist/imessage-NGA2XF2V.js.map +1 -0
- package/dist/inbox-summarizer-NRI4S7IF.js +47 -0
- package/dist/inbox-summarizer-NRI4S7IF.js.map +1 -0
- package/dist/incident-response-C5J7Q6DT.js +244 -0
- package/dist/incident-response-C5J7Q6DT.js.map +1 -0
- package/dist/inventory-manager-352OHXWD.js +24 -0
- package/dist/inventory-manager-352OHXWD.js.map +1 -0
- package/dist/jira-GSGDBMIG.js +199 -0
- package/dist/jira-GSGDBMIG.js.map +1 -0
- package/dist/json-tool-QE2SYHEG.js +26 -0
- package/dist/json-tool-QE2SYHEG.js.map +1 -0
- package/dist/key-rotation-DPHU4ZTB.js +18 -0
- package/dist/key-rotation-DPHU4ZTB.js.map +1 -0
- package/dist/lib.d.ts +603 -11
- package/dist/lib.js +161 -35
- package/dist/lib.js.map +1 -1
- package/dist/mailchimp-KKNF6QJ7.js +152 -0
- package/dist/mailchimp-KKNF6QJ7.js.map +1 -0
- package/dist/matrix-QVHG76I7.js +279 -0
- package/dist/matrix-QVHG76I7.js.map +1 -0
- package/dist/{mcp-LS7Q3Z5W.js → mcp-3JI6W7ZE.js} +3 -3
- package/dist/mcp-3JI6W7ZE.js.map +1 -0
- package/dist/microsoft365-UCBKJHNX.js +164 -0
- package/dist/microsoft365-UCBKJHNX.js.map +1 -0
- package/dist/ocr-AC7NPX33.js +22 -0
- package/dist/ocr-AC7NPX33.js.map +1 -0
- package/dist/ollama-BOAMSPLJ.js +8 -0
- package/dist/ollama-BOAMSPLJ.js.map +1 -0
- package/dist/pages-MI523RB7.js +26 -0
- package/dist/pages-MI523RB7.js.map +1 -0
- package/dist/pair-JDFTERIK.js +24 -0
- package/dist/pair-JDFTERIK.js.map +1 -0
- package/dist/pairing-IFQYCPNS.js +10 -0
- package/dist/pairing-IFQYCPNS.js.map +1 -0
- package/dist/pdf-ALQVOEJR.js +17 -0
- package/dist/pdf-ALQVOEJR.js.map +1 -0
- package/dist/presentations-DSV5IHG5.js +1002 -0
- package/dist/presentations-DSV5IHG5.js.map +1 -0
- package/dist/prometheus-JNT2BD4L.js +10 -0
- package/dist/prometheus-JNT2BD4L.js.map +1 -0
- package/dist/providers-J4LYPHDR.js +19 -0
- package/dist/providers-J4LYPHDR.js.map +1 -0
- package/dist/qr-code-WIX4PB4U.js +16 -0
- package/dist/qr-code-WIX4PB4U.js.map +1 -0
- package/dist/quickbooks-XB4NII2S.js +190 -0
- package/dist/quickbooks-XB4NII2S.js.map +1 -0
- package/dist/regex-tool-W4ABRKGK.js +24 -0
- package/dist/regex-tool-W4ABRKGK.js.map +1 -0
- package/dist/scheduler-VK4WFERV.js +63 -0
- package/dist/scheduler-VK4WFERV.js.map +1 -0
- package/dist/search-BCLBO5E3.js +25 -0
- package/dist/search-BCLBO5E3.js.map +1 -0
- package/dist/sendgrid-RNXCAFKM.js +152 -0
- package/dist/sendgrid-RNXCAFKM.js.map +1 -0
- package/dist/shopify-NCXYJB4R.js +171 -0
- package/dist/shopify-NCXYJB4R.js.map +1 -0
- package/dist/signal-6CGDFYL2.js +35 -0
- package/dist/signal-6CGDFYL2.js.map +1 -0
- package/dist/slack-IZQWIKOH.js +75 -0
- package/dist/slack-IZQWIKOH.js.map +1 -0
- package/dist/sms-M3JIOTCW.js +23 -0
- package/dist/sms-M3JIOTCW.js.map +1 -0
- package/dist/{src-K7GASHRH.js → src-VYUE6LRA.js} +138 -32
- package/dist/src-VYUE6LRA.js.map +1 -0
- package/dist/stocks-XXWBPOCU.js +14 -0
- package/dist/stocks-XXWBPOCU.js.map +1 -0
- package/dist/text-transform-6SGUA5Z4.js +22 -0
- package/dist/text-transform-6SGUA5Z4.js.map +1 -0
- package/dist/tools-2RLEI2N6.js +38 -0
- package/dist/tools-2RLEI2N6.js.map +1 -0
- package/dist/tunnel-IWMXUML4.js +301 -0
- package/dist/tunnel-IWMXUML4.js.map +1 -0
- package/dist/twilio-53GEW5JT.js +139 -0
- package/dist/twilio-53GEW5JT.js.map +1 -0
- package/dist/unit-converter-ZYXMEZOE.js +14 -0
- package/dist/unit-converter-ZYXMEZOE.js.map +1 -0
- package/dist/whatsapp-LFX6YKCM.js +35 -0
- package/dist/whatsapp-LFX6YKCM.js.map +1 -0
- package/dist/word-document-7B6SJMAY.js +902 -0
- package/dist/word-document-7B6SJMAY.js.map +1 -0
- package/dist/xero-QYO66D45.js +162 -0
- package/dist/xero-QYO66D45.js.map +1 -0
- package/dist/zapier-webhook-TBZ5YF2A.js +106 -0
- package/dist/zapier-webhook-TBZ5YF2A.js.map +1 -0
- package/drizzle/0002_mushy_master_mold.sql +140 -0
- package/drizzle/meta/0002_snapshot.json +3637 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +100 -98
- package/dist/bot-KJ26BG56.js +0 -15
- package/dist/chunk-4LVWXUNC.js.map +0 -1
- package/dist/chunk-4TG2IG5K.js +0 -5249
- package/dist/chunk-4TG2IG5K.js.map +0 -1
- package/dist/chunk-6DRDKB45.js.map +0 -1
- package/dist/chunk-CI6Q63MM.js +0 -1613
- package/dist/chunk-CI6Q63MM.js.map +0 -1
- package/dist/chunk-KHNYJY2Z.js +0 -178
- package/dist/chunk-KHNYJY2Z.js.map +0 -1
- package/dist/chunk-NSBPE2FW.js +0 -17
- package/dist/discord-ZOJFTVTB.js +0 -49
- package/dist/imessage-JFRB6EJ7.js +0 -14
- package/dist/scheduler-EZ7CZMCS.js +0 -42
- package/dist/signal-T3MCSULM.js +0 -14
- package/dist/slack-N2M4FHAJ.js +0 -54
- package/dist/src-K7GASHRH.js.map +0 -1
- package/dist/tools-24GZHYRF.js +0 -16
- package/dist/whatsapp-VCRUPAO5.js +0 -14
- /package/dist/{bot-KJ26BG56.js.map → audit-logger-OBPR7CRO.js.map} +0 -0
- /package/dist/{chunk-NSBPE2FW.js.map → auth-UOX5K2BE.js.map} +0 -0
- /package/dist/{discord-ZOJFTVTB.js.map → backup-restore-PZ7CYYB7.js.map} +0 -0
- /package/dist/{imessage-JFRB6EJ7.js.map → blocks-R3PODY47.js.map} +0 -0
- /package/dist/{mcp-LS7Q3Z5W.js.map → bot-QRARP4UN.js.map} +0 -0
- /package/dist/{scheduler-EZ7CZMCS.js.map → brain-7XLLM3KC.js.map} +0 -0
- /package/dist/{charts-MMXM6BWW.js.map → charts-V7ARZNKF.js.map} +0 -0
- /package/dist/{chunk-L3PDU3XN.js.map → chunk-4UOE5TUZ.js.map} +0 -0
- /package/dist/{chunk-6SNHU3CY.js.map → chunk-66OJ3WB4.js.map} +0 -0
- /package/dist/{chunk-F6QUZQGI.js.map → chunk-MXAPLSJ5.js.map} +0 -0
- /package/dist/{chunk-GK3E2I7A.js.map → chunk-NHMBTUMW.js.map} +0 -0
- /package/dist/{signal-T3MCSULM.js.map → chunk-PLDDJCW6.js.map} +0 -0
- /package/dist/{chunk-GVJVEWHI.js.map → chunk-SJSUSJ47.js.map} +0 -0
- /package/dist/{chunk-HH2HBTQM.js.map → chunk-TYAGMJNV.js.map} +0 -0
- /package/dist/{chunk-JXUP2X7V.js.map → chunk-VEHFVBLI.js.map} +0 -0
- /package/dist/{slack-N2M4FHAJ.js.map → client-ZQSFPMOB.js.map} +0 -0
- /package/dist/{tools-24GZHYRF.js.map → clipboard-manager-TEO2GEDN.js.map} +0 -0
- /package/dist/{whatsapp-VCRUPAO5.js.map → cron-explain-HHQKPD3M.js.map} +0 -0
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chat
|
|
3
|
+
} from "./chunk-766ASQWE.js";
|
|
4
|
+
|
|
5
|
+
// src/integrations/email/email-parser.ts
|
|
6
|
+
function parseEmail(email) {
|
|
7
|
+
const isReply = email.subject.toLowerCase().startsWith("re:");
|
|
8
|
+
const isForward = email.subject.toLowerCase().startsWith("fwd:") || email.subject.toLowerCase().startsWith("fw:");
|
|
9
|
+
const attachments = email.attachments.map((att) => parseAttachment(att));
|
|
10
|
+
const totalAttachmentSize = attachments.reduce((sum, att) => sum + att.size, 0);
|
|
11
|
+
let importance = "normal";
|
|
12
|
+
const importanceHeader = email.headers.get("importance") || email.headers.get("x-priority");
|
|
13
|
+
if (importanceHeader) {
|
|
14
|
+
const lowerHeader = importanceHeader.toLowerCase();
|
|
15
|
+
if (lowerHeader.includes("high") || lowerHeader === "1" || lowerHeader === "2") {
|
|
16
|
+
importance = "high";
|
|
17
|
+
} else if (lowerHeader.includes("low") || lowerHeader === "5") {
|
|
18
|
+
importance = "low";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
id: email.id,
|
|
23
|
+
subject: email.subject,
|
|
24
|
+
from: email.from,
|
|
25
|
+
to: email.to,
|
|
26
|
+
cc: email.cc,
|
|
27
|
+
date: email.date,
|
|
28
|
+
body: {
|
|
29
|
+
text: email.text,
|
|
30
|
+
html: email.html,
|
|
31
|
+
snippet: email.snippet || email.text.substring(0, 200).replace(/\s+/g, " ").trim()
|
|
32
|
+
},
|
|
33
|
+
thread: {
|
|
34
|
+
id: email.threadId || email.messageId,
|
|
35
|
+
messageId: email.messageId,
|
|
36
|
+
inReplyTo: email.inReplyTo,
|
|
37
|
+
references: email.references,
|
|
38
|
+
position: email.references.length,
|
|
39
|
+
isReply,
|
|
40
|
+
isForward
|
|
41
|
+
},
|
|
42
|
+
attachments,
|
|
43
|
+
metadata: {
|
|
44
|
+
isRead: email.flags.includes("\\Seen"),
|
|
45
|
+
isFlagged: email.flags.includes("\\Flagged"),
|
|
46
|
+
isSpam: email.labels.includes("Spam") || email.labels.includes("Junk"),
|
|
47
|
+
isDraft: email.flags.includes("\\Draft"),
|
|
48
|
+
labels: email.labels,
|
|
49
|
+
importance,
|
|
50
|
+
hasAttachments: attachments.length > 0,
|
|
51
|
+
attachmentCount: attachments.length,
|
|
52
|
+
totalAttachmentSize
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function parseAttachment(attachment) {
|
|
57
|
+
const extension = attachment.filename.includes(".") ? attachment.filename.split(".").pop()?.toLowerCase() || "" : "";
|
|
58
|
+
return {
|
|
59
|
+
filename: attachment.filename,
|
|
60
|
+
contentType: attachment.contentType,
|
|
61
|
+
size: attachment.size,
|
|
62
|
+
isInline: !!attachment.contentId,
|
|
63
|
+
cid: attachment.contentId,
|
|
64
|
+
content: attachment.content,
|
|
65
|
+
extension,
|
|
66
|
+
category: categorizeAttachment(attachment.contentType, extension)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function categorizeAttachment(contentType, extension) {
|
|
70
|
+
const lowerContentType = contentType.toLowerCase();
|
|
71
|
+
if (lowerContentType.startsWith("image/")) {
|
|
72
|
+
return "image";
|
|
73
|
+
}
|
|
74
|
+
const docExtensions = ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "rtf", "odt", "ods", "odp", "csv"];
|
|
75
|
+
const docMimeTypes = [
|
|
76
|
+
"application/pdf",
|
|
77
|
+
"application/msword",
|
|
78
|
+
"application/vnd.openxmlformats",
|
|
79
|
+
"application/vnd.ms-",
|
|
80
|
+
"text/plain",
|
|
81
|
+
"text/csv",
|
|
82
|
+
"application/vnd.oasis.opendocument"
|
|
83
|
+
];
|
|
84
|
+
if (docExtensions.includes(extension) || docMimeTypes.some((m) => lowerContentType.includes(m))) {
|
|
85
|
+
return "document";
|
|
86
|
+
}
|
|
87
|
+
const archiveExtensions = ["zip", "rar", "7z", "tar", "gz", "bz2"];
|
|
88
|
+
const archiveMimeTypes = ["application/zip", "application/x-rar", "application/x-7z", "application/gzip"];
|
|
89
|
+
if (archiveExtensions.includes(extension) || archiveMimeTypes.some((m) => lowerContentType.includes(m))) {
|
|
90
|
+
return "archive";
|
|
91
|
+
}
|
|
92
|
+
if (lowerContentType.startsWith("audio/") || lowerContentType.startsWith("video/")) {
|
|
93
|
+
return "media";
|
|
94
|
+
}
|
|
95
|
+
return "other";
|
|
96
|
+
}
|
|
97
|
+
function groupIntoThreads(emails) {
|
|
98
|
+
const threadMap = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const email of emails) {
|
|
100
|
+
let threadId = email.threadId || email.messageId;
|
|
101
|
+
if (email.references.length > 0) {
|
|
102
|
+
threadId = email.references[0];
|
|
103
|
+
}
|
|
104
|
+
if (!threadMap.has(threadId)) {
|
|
105
|
+
threadMap.set(threadId, []);
|
|
106
|
+
}
|
|
107
|
+
threadMap.get(threadId).push(email);
|
|
108
|
+
}
|
|
109
|
+
const threads = [];
|
|
110
|
+
for (const [threadId, messages] of threadMap) {
|
|
111
|
+
messages.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
112
|
+
const participantMap = /* @__PURE__ */ new Map();
|
|
113
|
+
for (const msg of messages) {
|
|
114
|
+
for (const addr of [...msg.from, ...msg.to, ...msg.cc]) {
|
|
115
|
+
if (!participantMap.has(addr.address)) {
|
|
116
|
+
participantMap.set(addr.address, addr);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const unreadCount = messages.filter((m) => !m.flags.includes("\\Seen")).length;
|
|
121
|
+
let subject = messages[0].subject;
|
|
122
|
+
for (const msg of messages) {
|
|
123
|
+
const cleaned = cleanSubject(msg.subject);
|
|
124
|
+
if (cleaned.length > 0 && !cleaned.toLowerCase().startsWith("re:")) {
|
|
125
|
+
subject = msg.subject;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
threads.push({
|
|
130
|
+
id: threadId,
|
|
131
|
+
subject: cleanSubject(subject),
|
|
132
|
+
participants: Array.from(participantMap.values()),
|
|
133
|
+
messageCount: messages.length,
|
|
134
|
+
unreadCount,
|
|
135
|
+
lastMessageDate: messages[messages.length - 1].date,
|
|
136
|
+
firstMessageDate: messages[0].date,
|
|
137
|
+
messages: messages.map(parseEmail),
|
|
138
|
+
snippet: messages[messages.length - 1].snippet || messages[messages.length - 1].text.substring(0, 200).replace(/\s+/g, " ").trim()
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
threads.sort((a, b) => b.lastMessageDate.getTime() - a.lastMessageDate.getTime());
|
|
142
|
+
return threads;
|
|
143
|
+
}
|
|
144
|
+
function cleanSubject(subject) {
|
|
145
|
+
return subject.replace(/^(Re|Fwd|Fw):\s*/gi, "").replace(/^\[.*?\]\s*/, "").trim();
|
|
146
|
+
}
|
|
147
|
+
function parseEmailBody(text) {
|
|
148
|
+
const lines = text.split("\n");
|
|
149
|
+
const newLines = [];
|
|
150
|
+
const quotedSections = [];
|
|
151
|
+
let currentQuote = null;
|
|
152
|
+
let signature;
|
|
153
|
+
let inSignature = false;
|
|
154
|
+
for (let i = 0; i < lines.length; i++) {
|
|
155
|
+
const line = lines[i];
|
|
156
|
+
if (line.trim() === "--" || line.trim() === "-- ") {
|
|
157
|
+
inSignature = true;
|
|
158
|
+
signature = lines.slice(i + 1).join("\n").trim();
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
const quoteMatch = line.match(/^(>+)\s?/);
|
|
162
|
+
if (quoteMatch) {
|
|
163
|
+
const level = quoteMatch[1].length;
|
|
164
|
+
const content = line.substring(quoteMatch[0].length);
|
|
165
|
+
if (!currentQuote || currentQuote.level !== level) {
|
|
166
|
+
if (currentQuote) {
|
|
167
|
+
quotedSections.push({
|
|
168
|
+
level: currentQuote.level,
|
|
169
|
+
content: currentQuote.lines.join("\n"),
|
|
170
|
+
attribution: currentQuote.attribution
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
currentQuote = { level, lines: [content] };
|
|
174
|
+
} else {
|
|
175
|
+
currentQuote.lines.push(content);
|
|
176
|
+
}
|
|
177
|
+
} else if (isAttributionLine(line)) {
|
|
178
|
+
if (currentQuote) {
|
|
179
|
+
currentQuote.attribution = line;
|
|
180
|
+
} else {
|
|
181
|
+
currentQuote = { level: 1, lines: [], attribution: line };
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
if (currentQuote) {
|
|
185
|
+
quotedSections.push({
|
|
186
|
+
level: currentQuote.level,
|
|
187
|
+
content: currentQuote.lines.join("\n"),
|
|
188
|
+
attribution: currentQuote.attribution
|
|
189
|
+
});
|
|
190
|
+
currentQuote = null;
|
|
191
|
+
}
|
|
192
|
+
newLines.push(line);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (currentQuote) {
|
|
196
|
+
quotedSections.push({
|
|
197
|
+
level: currentQuote.level,
|
|
198
|
+
content: currentQuote.lines.join("\n"),
|
|
199
|
+
attribution: currentQuote.attribution
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
newContent: newLines.join("\n").trim(),
|
|
204
|
+
quotedContent: quotedSections,
|
|
205
|
+
signature
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function isAttributionLine(line) {
|
|
209
|
+
const attributionPatterns = [
|
|
210
|
+
/^On .+ wrote:$/i,
|
|
211
|
+
/^On .+, .+ wrote:$/i,
|
|
212
|
+
/^.+ wrote:$/i,
|
|
213
|
+
/^-{3,}\s*Original Message\s*-{3,}$/i,
|
|
214
|
+
/^-{3,}\s*Forwarded message\s*-{3,}$/i,
|
|
215
|
+
/^From:\s*.+$/i,
|
|
216
|
+
/^Sent:\s*.+$/i
|
|
217
|
+
];
|
|
218
|
+
return attributionPatterns.some((pattern) => pattern.test(line.trim()));
|
|
219
|
+
}
|
|
220
|
+
function extractEmailAddresses(text) {
|
|
221
|
+
const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
222
|
+
const matches = text.match(emailRegex) || [];
|
|
223
|
+
return [...new Set(matches)];
|
|
224
|
+
}
|
|
225
|
+
function extractUrls(text) {
|
|
226
|
+
const urlRegex = /https?:\/\/[^\s<>"\)]+/gi;
|
|
227
|
+
const matches = text.match(urlRegex) || [];
|
|
228
|
+
return [...new Set(matches)];
|
|
229
|
+
}
|
|
230
|
+
function extractPhoneNumbers(text) {
|
|
231
|
+
const phoneRegex = /(?:\+\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g;
|
|
232
|
+
const matches = text.match(phoneRegex) || [];
|
|
233
|
+
return [...new Set(matches)];
|
|
234
|
+
}
|
|
235
|
+
function getEmailSummary(email) {
|
|
236
|
+
const fromAddr = email.from[0];
|
|
237
|
+
const fromDisplay = fromAddr ? fromAddr.name || fromAddr.address : "Unknown";
|
|
238
|
+
const snippet = "body" in email ? email.body.snippet : email.snippet || email.text.substring(0, 100);
|
|
239
|
+
const attachmentCount = "metadata" in email ? email.metadata.attachmentCount : email.attachments.length;
|
|
240
|
+
return {
|
|
241
|
+
from: fromDisplay,
|
|
242
|
+
subject: email.subject,
|
|
243
|
+
snippet,
|
|
244
|
+
date: formatEmailDate(email.date),
|
|
245
|
+
attachmentCount
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function formatEmailDate(date) {
|
|
249
|
+
const now = /* @__PURE__ */ new Date();
|
|
250
|
+
const diff = now.getTime() - date.getTime();
|
|
251
|
+
const days = Math.floor(diff / (1e3 * 60 * 60 * 24));
|
|
252
|
+
if (days === 0) {
|
|
253
|
+
return date.toLocaleTimeString("en-US", {
|
|
254
|
+
hour: "numeric",
|
|
255
|
+
minute: "2-digit",
|
|
256
|
+
hour12: true
|
|
257
|
+
});
|
|
258
|
+
} else if (days === 1) {
|
|
259
|
+
return "Yesterday";
|
|
260
|
+
} else if (days < 7) {
|
|
261
|
+
return date.toLocaleDateString("en-US", { weekday: "short" });
|
|
262
|
+
} else if (date.getFullYear() === now.getFullYear()) {
|
|
263
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
264
|
+
} else {
|
|
265
|
+
return date.toLocaleDateString("en-US", {
|
|
266
|
+
month: "short",
|
|
267
|
+
day: "numeric",
|
|
268
|
+
year: "numeric"
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function calculateEmailSize(email) {
|
|
273
|
+
let size = 0;
|
|
274
|
+
size += new TextEncoder().encode(email.text).length;
|
|
275
|
+
size += new TextEncoder().encode(email.html).length;
|
|
276
|
+
size += new TextEncoder().encode(email.subject).length;
|
|
277
|
+
size += email.from.reduce((s, a) => s + (a.name?.length || 0) + a.address.length + 10, 0);
|
|
278
|
+
size += email.to.reduce((s, a) => s + (a.name?.length || 0) + a.address.length + 10, 0);
|
|
279
|
+
size += email.attachments.reduce((s, a) => s + a.size, 0);
|
|
280
|
+
return size;
|
|
281
|
+
}
|
|
282
|
+
function matchesFilter(email, filter) {
|
|
283
|
+
const parsed = "metadata" in email ? email : parseEmail(email);
|
|
284
|
+
if (filter.from) {
|
|
285
|
+
const fromMatch = parsed.from.some((addr) => {
|
|
286
|
+
const str = `${addr.name} ${addr.address}`;
|
|
287
|
+
return filter.from instanceof RegExp ? filter.from.test(str) : str.toLowerCase().includes(filter.from.toLowerCase());
|
|
288
|
+
});
|
|
289
|
+
if (!fromMatch) return false;
|
|
290
|
+
}
|
|
291
|
+
if (filter.to) {
|
|
292
|
+
const toMatch = parsed.to.some((addr) => {
|
|
293
|
+
const str = `${addr.name} ${addr.address}`;
|
|
294
|
+
return filter.to instanceof RegExp ? filter.to.test(str) : str.toLowerCase().includes(filter.to.toLowerCase());
|
|
295
|
+
});
|
|
296
|
+
if (!toMatch) return false;
|
|
297
|
+
}
|
|
298
|
+
if (filter.subject) {
|
|
299
|
+
const subjectMatch = filter.subject instanceof RegExp ? filter.subject.test(parsed.subject) : parsed.subject.toLowerCase().includes(filter.subject.toLowerCase());
|
|
300
|
+
if (!subjectMatch) return false;
|
|
301
|
+
}
|
|
302
|
+
if (filter.body) {
|
|
303
|
+
const bodyText = parsed.body.text;
|
|
304
|
+
const bodyMatch = filter.body instanceof RegExp ? filter.body.test(bodyText) : bodyText.toLowerCase().includes(filter.body.toLowerCase());
|
|
305
|
+
if (!bodyMatch) return false;
|
|
306
|
+
}
|
|
307
|
+
if (filter.hasAttachments !== void 0) {
|
|
308
|
+
if (parsed.metadata.hasAttachments !== filter.hasAttachments) return false;
|
|
309
|
+
}
|
|
310
|
+
if (filter.isUnread !== void 0) {
|
|
311
|
+
if (parsed.metadata.isRead === filter.isUnread) return false;
|
|
312
|
+
}
|
|
313
|
+
if (filter.isFlagged !== void 0) {
|
|
314
|
+
if (parsed.metadata.isFlagged !== filter.isFlagged) return false;
|
|
315
|
+
}
|
|
316
|
+
if (filter.dateAfter && parsed.date < filter.dateAfter) return false;
|
|
317
|
+
if (filter.dateBefore && parsed.date > filter.dateBefore) return false;
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/integrations/email/inbox-summarizer.ts
|
|
322
|
+
var CATEGORIZATION_PROMPT = `You are an email categorization assistant. Analyze the following email and categorize it.
|
|
323
|
+
|
|
324
|
+
Categories:
|
|
325
|
+
- urgent: Time-sensitive, requires immediate attention
|
|
326
|
+
- action_required: Requires a response or action from the user
|
|
327
|
+
- informational: FYI, newsletters, updates that don't require action
|
|
328
|
+
- meeting: Calendar invites, meeting-related emails
|
|
329
|
+
- financial: Invoices, receipts, financial statements
|
|
330
|
+
- social: Personal emails, social notifications
|
|
331
|
+
- marketing: Promotional emails, newsletters
|
|
332
|
+
- support: Customer support, help desk emails
|
|
333
|
+
- work: Work-related emails that aren't urgent
|
|
334
|
+
- spam: Potential spam or unwanted emails
|
|
335
|
+
|
|
336
|
+
Email:
|
|
337
|
+
From: {{from}}
|
|
338
|
+
Subject: {{subject}}
|
|
339
|
+
Date: {{date}}
|
|
340
|
+
Snippet: {{snippet}}
|
|
341
|
+
|
|
342
|
+
Respond in JSON format:
|
|
343
|
+
{
|
|
344
|
+
"category": "category_name",
|
|
345
|
+
"confidence": 0.0-1.0,
|
|
346
|
+
"reason": "brief explanation"
|
|
347
|
+
}`;
|
|
348
|
+
var ACTION_EXTRACTION_PROMPT = `You are an email assistant that extracts action items. Analyze the following email and identify any actions the recipient needs to take.
|
|
349
|
+
|
|
350
|
+
Email:
|
|
351
|
+
From: {{from}}
|
|
352
|
+
Subject: {{subject}}
|
|
353
|
+
Date: {{date}}
|
|
354
|
+
Body: {{body}}
|
|
355
|
+
|
|
356
|
+
For each action item found, provide:
|
|
357
|
+
- action: A clear, concise description of what needs to be done
|
|
358
|
+
- priority: high (urgent/deadline), medium (important but not urgent), or low (when convenient)
|
|
359
|
+
- dueDate: If a specific date/time is mentioned (ISO format or null)
|
|
360
|
+
- context: Brief context from the email
|
|
361
|
+
|
|
362
|
+
Respond in JSON format:
|
|
363
|
+
{
|
|
364
|
+
"actions": [
|
|
365
|
+
{
|
|
366
|
+
"action": "description",
|
|
367
|
+
"priority": "high|medium|low",
|
|
368
|
+
"dueDate": "ISO date or null",
|
|
369
|
+
"context": "brief context"
|
|
370
|
+
}
|
|
371
|
+
]
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
If no actions are required, return: { "actions": [] }`;
|
|
375
|
+
var INBOX_SUMMARY_PROMPT = `You are an email assistant providing a daily inbox summary. Based on the following email statistics and categories, provide a brief, helpful summary.
|
|
376
|
+
|
|
377
|
+
Statistics:
|
|
378
|
+
- Total emails: {{totalEmails}}
|
|
379
|
+
- Unread: {{unreadCount}}
|
|
380
|
+
- Important: {{importantCount}}
|
|
381
|
+
|
|
382
|
+
Categories:
|
|
383
|
+
{{categories}}
|
|
384
|
+
|
|
385
|
+
Urgent items:
|
|
386
|
+
{{urgentItems}}
|
|
387
|
+
|
|
388
|
+
Provide a 2-3 sentence summary that:
|
|
389
|
+
1. Highlights the most important items requiring attention
|
|
390
|
+
2. Notes any patterns or notable senders
|
|
391
|
+
3. Suggests prioritization if there are many items
|
|
392
|
+
|
|
393
|
+
Keep it concise and actionable.`;
|
|
394
|
+
var THREAD_SUMMARY_PROMPT = `You are an email assistant that summarizes email threads. Analyze the following email thread and provide a comprehensive summary.
|
|
395
|
+
|
|
396
|
+
Thread: {{subject}}
|
|
397
|
+
Participants: {{participants}}
|
|
398
|
+
Messages: {{messageCount}}
|
|
399
|
+
|
|
400
|
+
Messages (oldest to newest):
|
|
401
|
+
{{messages}}
|
|
402
|
+
|
|
403
|
+
Provide a summary including:
|
|
404
|
+
1. Brief summary of the entire conversation (2-3 sentences)
|
|
405
|
+
2. Current status of the discussion
|
|
406
|
+
3. Key decisions made (if any)
|
|
407
|
+
4. Open questions that need resolution
|
|
408
|
+
5. Suggested next steps
|
|
409
|
+
|
|
410
|
+
Respond in JSON format:
|
|
411
|
+
{
|
|
412
|
+
"summary": "conversation summary",
|
|
413
|
+
"currentStatus": "status description",
|
|
414
|
+
"keyDecisions": ["decision 1", "decision 2"],
|
|
415
|
+
"openQuestions": ["question 1", "question 2"],
|
|
416
|
+
"nextSteps": ["step 1", "step 2"]
|
|
417
|
+
}`;
|
|
418
|
+
async function categorizeEmail(email) {
|
|
419
|
+
const parsed = "metadata" in email ? email : parseEmail(email);
|
|
420
|
+
const fromDisplay = parsed.from[0] ? `${parsed.from[0].name || ""} <${parsed.from[0].address}>`.trim() : "Unknown";
|
|
421
|
+
const prompt = CATEGORIZATION_PROMPT.replace("{{from}}", fromDisplay).replace("{{subject}}", parsed.subject).replace("{{date}}", parsed.date.toISOString()).replace("{{snippet}}", parsed.body.snippet);
|
|
422
|
+
try {
|
|
423
|
+
const response = await chat(
|
|
424
|
+
[{ role: "user", content: prompt }],
|
|
425
|
+
"You are a helpful email categorization assistant. Always respond with valid JSON."
|
|
426
|
+
);
|
|
427
|
+
const result = JSON.parse(response.content);
|
|
428
|
+
return {
|
|
429
|
+
category: result.category || "informational",
|
|
430
|
+
confidence: result.confidence || 0.5,
|
|
431
|
+
reason: result.reason || ""
|
|
432
|
+
};
|
|
433
|
+
} catch (err) {
|
|
434
|
+
console.error("Failed to categorize email:", err);
|
|
435
|
+
return {
|
|
436
|
+
category: "informational",
|
|
437
|
+
confidence: 0.5,
|
|
438
|
+
reason: "Failed to categorize"
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async function extractActionItems(email) {
|
|
443
|
+
const parsed = "metadata" in email ? email : parseEmail(email);
|
|
444
|
+
const fromDisplay = parsed.from[0] ? `${parsed.from[0].name || ""} <${parsed.from[0].address}>`.trim() : "Unknown";
|
|
445
|
+
const bodyParts = parseEmailBody(parsed.body.text);
|
|
446
|
+
const relevantBody = bodyParts.newContent || parsed.body.text.substring(0, 2e3);
|
|
447
|
+
const prompt = ACTION_EXTRACTION_PROMPT.replace("{{from}}", fromDisplay).replace("{{subject}}", parsed.subject).replace("{{date}}", parsed.date.toISOString()).replace("{{body}}", relevantBody);
|
|
448
|
+
try {
|
|
449
|
+
const response = await chat(
|
|
450
|
+
[{ role: "user", content: prompt }],
|
|
451
|
+
"You are a helpful email assistant that extracts action items. Always respond with valid JSON."
|
|
452
|
+
);
|
|
453
|
+
const result = JSON.parse(response.content);
|
|
454
|
+
return (result.actions || []).map((action) => ({
|
|
455
|
+
emailId: parsed.id,
|
|
456
|
+
subject: parsed.subject,
|
|
457
|
+
from: fromDisplay,
|
|
458
|
+
action: action.action,
|
|
459
|
+
priority: ["high", "medium", "low"].includes(action.priority) ? action.priority : "medium",
|
|
460
|
+
dueDate: action.dueDate ? new Date(action.dueDate) : void 0,
|
|
461
|
+
context: action.context
|
|
462
|
+
}));
|
|
463
|
+
} catch (err) {
|
|
464
|
+
console.error("Failed to extract action items:", err);
|
|
465
|
+
return [];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async function summarizeThread(thread) {
|
|
469
|
+
const participants = thread.participants.map((p) => p.name || p.address).slice(0, 10).join(", ");
|
|
470
|
+
const messageContent = thread.messages.slice(-10).map((msg, idx) => {
|
|
471
|
+
const from = msg.from[0]?.name || msg.from[0]?.address || "Unknown";
|
|
472
|
+
const bodyParts = parseEmailBody(msg.body.text);
|
|
473
|
+
const content = (bodyParts.newContent || msg.body.snippet).substring(0, 500);
|
|
474
|
+
return `[${idx + 1}] From: ${from}
|
|
475
|
+
Date: ${msg.date.toISOString()}
|
|
476
|
+
${content}`;
|
|
477
|
+
}).join("\n\n---\n\n");
|
|
478
|
+
const prompt = THREAD_SUMMARY_PROMPT.replace("{{subject}}", thread.subject).replace("{{participants}}", participants).replace("{{messageCount}}", String(thread.messageCount)).replace("{{messages}}", messageContent);
|
|
479
|
+
try {
|
|
480
|
+
const response = await chat(
|
|
481
|
+
[{ role: "user", content: prompt }],
|
|
482
|
+
"You are a helpful email assistant that summarizes email threads. Always respond with valid JSON."
|
|
483
|
+
);
|
|
484
|
+
const result = JSON.parse(response.content);
|
|
485
|
+
return {
|
|
486
|
+
threadId: thread.id,
|
|
487
|
+
subject: thread.subject,
|
|
488
|
+
participantCount: thread.participants.length,
|
|
489
|
+
messageCount: thread.messageCount,
|
|
490
|
+
summary: result.summary || "No summary available",
|
|
491
|
+
currentStatus: result.currentStatus || "Unknown",
|
|
492
|
+
nextSteps: result.nextSteps || [],
|
|
493
|
+
keyDecisions: result.keyDecisions || [],
|
|
494
|
+
openQuestions: result.openQuestions || []
|
|
495
|
+
};
|
|
496
|
+
} catch (err) {
|
|
497
|
+
console.error("Failed to summarize thread:", err);
|
|
498
|
+
return {
|
|
499
|
+
threadId: thread.id,
|
|
500
|
+
subject: thread.subject,
|
|
501
|
+
participantCount: thread.participants.length,
|
|
502
|
+
messageCount: thread.messageCount,
|
|
503
|
+
summary: "Failed to generate summary",
|
|
504
|
+
currentStatus: "Unknown",
|
|
505
|
+
nextSteps: [],
|
|
506
|
+
keyDecisions: [],
|
|
507
|
+
openQuestions: []
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async function summarizeInbox(emails) {
|
|
512
|
+
const parsedEmails = emails.map(parseEmail);
|
|
513
|
+
const categorizations = await Promise.all(
|
|
514
|
+
parsedEmails.slice(0, 100).map(async (email) => ({
|
|
515
|
+
email,
|
|
516
|
+
categorization: await categorizeEmail(email)
|
|
517
|
+
}))
|
|
518
|
+
);
|
|
519
|
+
const totalEmails = emails.length;
|
|
520
|
+
const unreadCount = parsedEmails.filter((e) => !e.metadata.isRead).length;
|
|
521
|
+
const importantCount = categorizations.filter(
|
|
522
|
+
(c) => c.categorization.category === "urgent" || c.categorization.category === "action_required"
|
|
523
|
+
).length;
|
|
524
|
+
const categoryMap = /* @__PURE__ */ new Map();
|
|
525
|
+
for (const { email, categorization } of categorizations) {
|
|
526
|
+
const cat = categorization.category;
|
|
527
|
+
if (!categoryMap.has(cat)) {
|
|
528
|
+
categoryMap.set(cat, { emails: [], senders: /* @__PURE__ */ new Set() });
|
|
529
|
+
}
|
|
530
|
+
const entry = categoryMap.get(cat);
|
|
531
|
+
entry.emails.push(email);
|
|
532
|
+
if (email.from[0]) {
|
|
533
|
+
entry.senders.add(email.from[0].name || email.from[0].address);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const categories = [];
|
|
537
|
+
for (const [name, data] of categoryMap) {
|
|
538
|
+
categories.push({
|
|
539
|
+
name,
|
|
540
|
+
count: data.emails.length,
|
|
541
|
+
unreadCount: data.emails.filter((e) => !e.metadata.isRead).length,
|
|
542
|
+
topSenders: Array.from(data.senders).slice(0, 5),
|
|
543
|
+
description: getCategoryDescription(name)
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
categories.sort((a, b) => b.count - a.count);
|
|
547
|
+
const urgentItems = [];
|
|
548
|
+
for (const { email, categorization } of categorizations) {
|
|
549
|
+
if (categorization.category === "urgent") {
|
|
550
|
+
urgentItems.push({
|
|
551
|
+
emailId: email.id,
|
|
552
|
+
subject: email.subject,
|
|
553
|
+
from: email.from[0]?.name || email.from[0]?.address || "Unknown",
|
|
554
|
+
reason: categorization.reason,
|
|
555
|
+
suggestedAction: "Review and respond as soon as possible"
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const actionItems = [];
|
|
560
|
+
const actionRequiredEmails = categorizations.filter((c) => c.categorization.category === "action_required").slice(0, 10);
|
|
561
|
+
for (const { email } of actionRequiredEmails) {
|
|
562
|
+
const items = await extractActionItems(email);
|
|
563
|
+
actionItems.push(...items);
|
|
564
|
+
}
|
|
565
|
+
actionItems.sort((a, b) => {
|
|
566
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
567
|
+
return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
568
|
+
});
|
|
569
|
+
const categoriesText = categories.map((c) => `- ${c.name}: ${c.count} emails (${c.unreadCount} unread)`).join("\n");
|
|
570
|
+
const urgentText = urgentItems.length > 0 ? urgentItems.map((u) => `- "${u.subject}" from ${u.from}`).join("\n") : "None";
|
|
571
|
+
const summaryPrompt = INBOX_SUMMARY_PROMPT.replace("{{totalEmails}}", String(totalEmails)).replace("{{unreadCount}}", String(unreadCount)).replace("{{importantCount}}", String(importantCount)).replace("{{categories}}", categoriesText).replace("{{urgentItems}}", urgentText);
|
|
572
|
+
let summary = "";
|
|
573
|
+
try {
|
|
574
|
+
const response = await chat(
|
|
575
|
+
[{ role: "user", content: summaryPrompt }],
|
|
576
|
+
"You are a helpful email assistant providing inbox summaries. Be concise and actionable."
|
|
577
|
+
);
|
|
578
|
+
summary = response.content;
|
|
579
|
+
} catch (err) {
|
|
580
|
+
console.error("Failed to generate summary:", err);
|
|
581
|
+
summary = `You have ${totalEmails} emails, ${unreadCount} unread. ${importantCount} require attention.`;
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
totalEmails,
|
|
585
|
+
unreadCount,
|
|
586
|
+
importantCount,
|
|
587
|
+
categories,
|
|
588
|
+
urgentItems,
|
|
589
|
+
actionItems: actionItems.slice(0, 20),
|
|
590
|
+
summary,
|
|
591
|
+
generatedAt: /* @__PURE__ */ new Date()
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
async function generateDailyDigest(emails) {
|
|
595
|
+
const summary = await summarizeInbox(emails);
|
|
596
|
+
let digest = `# Daily Email Digest
|
|
597
|
+
|
|
598
|
+
`;
|
|
599
|
+
digest += `*Generated: ${summary.generatedAt.toLocaleString()}*
|
|
600
|
+
|
|
601
|
+
`;
|
|
602
|
+
digest += `## Overview
|
|
603
|
+
|
|
604
|
+
`;
|
|
605
|
+
digest += `${summary.summary}
|
|
606
|
+
|
|
607
|
+
`;
|
|
608
|
+
digest += `- **Total Emails:** ${summary.totalEmails}
|
|
609
|
+
`;
|
|
610
|
+
digest += `- **Unread:** ${summary.unreadCount}
|
|
611
|
+
`;
|
|
612
|
+
digest += `- **Needs Attention:** ${summary.importantCount}
|
|
613
|
+
|
|
614
|
+
`;
|
|
615
|
+
if (summary.urgentItems.length > 0) {
|
|
616
|
+
digest += `## Urgent
|
|
617
|
+
|
|
618
|
+
`;
|
|
619
|
+
for (const item of summary.urgentItems) {
|
|
620
|
+
digest += `- **${item.subject}** from ${item.from}
|
|
621
|
+
`;
|
|
622
|
+
digest += ` *${item.reason}*
|
|
623
|
+
|
|
624
|
+
`;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (summary.actionItems.length > 0) {
|
|
628
|
+
digest += `## Action Items
|
|
629
|
+
|
|
630
|
+
`;
|
|
631
|
+
for (const item of summary.actionItems) {
|
|
632
|
+
const priority = item.priority === "high" ? "!!!" : item.priority === "medium" ? "!!" : "!";
|
|
633
|
+
digest += `- [${priority}] **${item.action}**
|
|
634
|
+
`;
|
|
635
|
+
digest += ` From: ${item.from} | ${item.context}
|
|
636
|
+
`;
|
|
637
|
+
if (item.dueDate) {
|
|
638
|
+
digest += ` Due: ${item.dueDate.toLocaleDateString()}
|
|
639
|
+
`;
|
|
640
|
+
}
|
|
641
|
+
digest += "\n";
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
digest += `## By Category
|
|
645
|
+
|
|
646
|
+
`;
|
|
647
|
+
for (const cat of summary.categories.slice(0, 8)) {
|
|
648
|
+
digest += `- **${cat.name}:** ${cat.count} emails`;
|
|
649
|
+
if (cat.unreadCount > 0) {
|
|
650
|
+
digest += ` (${cat.unreadCount} unread)`;
|
|
651
|
+
}
|
|
652
|
+
digest += "\n";
|
|
653
|
+
if (cat.topSenders.length > 0) {
|
|
654
|
+
digest += ` Top senders: ${cat.topSenders.slice(0, 3).join(", ")}
|
|
655
|
+
`;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return digest;
|
|
659
|
+
}
|
|
660
|
+
async function suggestReplies(email) {
|
|
661
|
+
const parsed = "metadata" in email ? email : parseEmail(email);
|
|
662
|
+
const fromDisplay = parsed.from[0] ? `${parsed.from[0].name || ""} <${parsed.from[0].address}>`.trim() : "Unknown";
|
|
663
|
+
const bodyParts = parseEmailBody(parsed.body.text);
|
|
664
|
+
const relevantBody = (bodyParts.newContent || parsed.body.text).substring(0, 1500);
|
|
665
|
+
const prompt = `You are an email assistant suggesting reply options. Based on the following email, suggest 3 brief reply options ranging from formal to casual.
|
|
666
|
+
|
|
667
|
+
Email:
|
|
668
|
+
From: ${fromDisplay}
|
|
669
|
+
Subject: ${parsed.subject}
|
|
670
|
+
Body: ${relevantBody}
|
|
671
|
+
|
|
672
|
+
Provide 3 reply suggestions:
|
|
673
|
+
1. A brief, professional response
|
|
674
|
+
2. A friendly but complete response
|
|
675
|
+
3. A quick, informal response
|
|
676
|
+
|
|
677
|
+
Respond in JSON format:
|
|
678
|
+
{
|
|
679
|
+
"replies": ["reply 1", "reply 2", "reply 3"]
|
|
680
|
+
}`;
|
|
681
|
+
try {
|
|
682
|
+
const response = await chat(
|
|
683
|
+
[{ role: "user", content: prompt }],
|
|
684
|
+
"You are a helpful email assistant. Always respond with valid JSON."
|
|
685
|
+
);
|
|
686
|
+
const result = JSON.parse(response.content);
|
|
687
|
+
return result.replies || [];
|
|
688
|
+
} catch (err) {
|
|
689
|
+
console.error("Failed to suggest replies:", err);
|
|
690
|
+
return [];
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async function analyzeSentiment(email) {
|
|
694
|
+
const parsed = "metadata" in email ? email : parseEmail(email);
|
|
695
|
+
const bodyParts = parseEmailBody(parsed.body.text);
|
|
696
|
+
const relevantBody = (bodyParts.newContent || parsed.body.text).substring(0, 1500);
|
|
697
|
+
const prompt = `Analyze the sentiment and tone of this email:
|
|
698
|
+
|
|
699
|
+
Subject: ${parsed.subject}
|
|
700
|
+
Body: ${relevantBody}
|
|
701
|
+
|
|
702
|
+
Respond in JSON format:
|
|
703
|
+
{
|
|
704
|
+
"sentiment": "positive|negative|neutral",
|
|
705
|
+
"confidence": 0.0-1.0,
|
|
706
|
+
"tone": "brief description of tone (e.g., 'urgent', 'friendly', 'formal', 'frustrated')",
|
|
707
|
+
"keyPhrases": ["phrase1", "phrase2", "phrase3"]
|
|
708
|
+
}`;
|
|
709
|
+
try {
|
|
710
|
+
const response = await chat(
|
|
711
|
+
[{ role: "user", content: prompt }],
|
|
712
|
+
"You are a helpful sentiment analysis assistant. Always respond with valid JSON."
|
|
713
|
+
);
|
|
714
|
+
const result = JSON.parse(response.content);
|
|
715
|
+
return {
|
|
716
|
+
sentiment: result.sentiment || "neutral",
|
|
717
|
+
confidence: result.confidence || 0.5,
|
|
718
|
+
tone: result.tone || "neutral",
|
|
719
|
+
keyPhrases: result.keyPhrases || []
|
|
720
|
+
};
|
|
721
|
+
} catch (err) {
|
|
722
|
+
console.error("Failed to analyze sentiment:", err);
|
|
723
|
+
return {
|
|
724
|
+
sentiment: "neutral",
|
|
725
|
+
confidence: 0.5,
|
|
726
|
+
tone: "neutral",
|
|
727
|
+
keyPhrases: []
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function getCategoryDescription(category) {
|
|
732
|
+
const descriptions = {
|
|
733
|
+
urgent: "Time-sensitive emails requiring immediate attention",
|
|
734
|
+
action_required: "Emails that need your response or action",
|
|
735
|
+
informational: "FYI emails and updates",
|
|
736
|
+
meeting: "Calendar invites and meeting-related emails",
|
|
737
|
+
financial: "Invoices, receipts, and financial communications",
|
|
738
|
+
social: "Personal and social notifications",
|
|
739
|
+
marketing: "Promotional emails and newsletters",
|
|
740
|
+
support: "Customer support and help desk communications",
|
|
741
|
+
work: "Work-related emails",
|
|
742
|
+
spam: "Potentially unwanted emails"
|
|
743
|
+
};
|
|
744
|
+
return descriptions[category] || "Other emails";
|
|
745
|
+
}
|
|
746
|
+
var inbox_summarizer_default = {
|
|
747
|
+
categorizeEmail,
|
|
748
|
+
extractActionItems,
|
|
749
|
+
summarizeThread,
|
|
750
|
+
summarizeInbox,
|
|
751
|
+
generateDailyDigest,
|
|
752
|
+
suggestReplies,
|
|
753
|
+
analyzeSentiment
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
export {
|
|
757
|
+
parseEmail,
|
|
758
|
+
parseAttachment,
|
|
759
|
+
groupIntoThreads,
|
|
760
|
+
cleanSubject,
|
|
761
|
+
parseEmailBody,
|
|
762
|
+
extractEmailAddresses,
|
|
763
|
+
extractUrls,
|
|
764
|
+
extractPhoneNumbers,
|
|
765
|
+
getEmailSummary,
|
|
766
|
+
calculateEmailSize,
|
|
767
|
+
matchesFilter,
|
|
768
|
+
categorizeEmail,
|
|
769
|
+
extractActionItems,
|
|
770
|
+
summarizeThread,
|
|
771
|
+
summarizeInbox,
|
|
772
|
+
generateDailyDigest,
|
|
773
|
+
suggestReplies,
|
|
774
|
+
analyzeSentiment,
|
|
775
|
+
inbox_summarizer_default
|
|
776
|
+
};
|
|
777
|
+
//# sourceMappingURL=chunk-SPPMCAKG.js.map
|