@adeu/mcp-server 1.20.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +49 -1062
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +46 -236
- package/src/response-builders.ts +5 -0
- package/src/shared.ts +0 -5
- package/dist/templates/email_ui.html +0 -770
- package/src/desktop-auth.ts +0 -127
- package/src/mcp.cloud.test.ts +0 -152
- package/src/templates/email_ui.html +0 -770
- package/src/tools/auth.ts +0 -57
- package/src/tools/email.test.ts +0 -697
- package/src/tools/email.ts +0 -925
|
@@ -1,770 +0,0 @@
|
|
|
1
|
-
<!-- FILE: src/adeu/templates/email_ui.html -->
|
|
2
|
-
<!DOCTYPE html>
|
|
3
|
-
<html>
|
|
4
|
-
|
|
5
|
-
<head>
|
|
6
|
-
<meta charset="utf-8">
|
|
7
|
-
<title>Adeu Inbox</title>
|
|
8
|
-
<meta name="color-scheme" content="light dark">
|
|
9
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
-
<link
|
|
12
|
-
href="https://fonts.googleapis.com/css2?family=Jost:wght@400;500;600&family=Lora:ital,wght@0,600;1,600&display=swap"
|
|
13
|
-
rel="stylesheet">
|
|
14
|
-
<style>
|
|
15
|
-
:root {
|
|
16
|
-
--brand-sand: #e8c0a1;
|
|
17
|
-
--brand-navy: #1a2a2c;
|
|
18
|
-
--brand-navy-light: #2a3a3c;
|
|
19
|
-
--neutral-50: #fafafa;
|
|
20
|
-
--neutral-100: #f5f5f5;
|
|
21
|
-
--neutral-200: #e5e5e5;
|
|
22
|
-
--neutral-300: #d9d9d9;
|
|
23
|
-
--neutral-600: #555969;
|
|
24
|
-
--neutral-800: #1e2028;
|
|
25
|
-
--neutral-900: #171717;
|
|
26
|
-
--font-lora: 'Lora', Georgia, serif;
|
|
27
|
-
--font-jost: 'Jost', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
28
|
-
--bg-body: var(--neutral-50);
|
|
29
|
-
--bg-card: #ffffff;
|
|
30
|
-
--border-card: var(--neutral-200);
|
|
31
|
-
--text-main: var(--neutral-600);
|
|
32
|
-
--text-heading: var(--brand-navy);
|
|
33
|
-
--bg-hover: var(--neutral-100);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@media (prefers-color-scheme: dark) {
|
|
37
|
-
:root {
|
|
38
|
-
--bg-body: var(--neutral-900);
|
|
39
|
-
--bg-card: var(--brand-navy);
|
|
40
|
-
--border-card: var(--brand-navy-light);
|
|
41
|
-
--text-main: rgba(255, 255, 255, 0.85);
|
|
42
|
-
--text-heading: #ffffff;
|
|
43
|
-
--bg-hover: var(--brand-navy-light);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
html,
|
|
48
|
-
body {
|
|
49
|
-
margin: 0;
|
|
50
|
-
padding: 0;
|
|
51
|
-
overflow: hidden !important;
|
|
52
|
-
box-sizing: border-box;
|
|
53
|
-
background-color: transparent;
|
|
54
|
-
height: 100%;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
body {
|
|
58
|
-
font-family: var(--font-jost);
|
|
59
|
-
color: var(--text-main);
|
|
60
|
-
padding: 16px;
|
|
61
|
-
display: flex;
|
|
62
|
-
flex-direction: column;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.adeu-card {
|
|
66
|
-
background-color: var(--bg-card);
|
|
67
|
-
border: 1px solid var(--border-card);
|
|
68
|
-
border-radius: 12px;
|
|
69
|
-
overflow: hidden;
|
|
70
|
-
display: flex;
|
|
71
|
-
flex-direction: column;
|
|
72
|
-
max-height: 100%;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.navbar {
|
|
76
|
-
flex-shrink: 0;
|
|
77
|
-
background-color: var(--bg-card);
|
|
78
|
-
padding: 16px 32px;
|
|
79
|
-
border-bottom: 1px solid var(--border-card);
|
|
80
|
-
display: flex;
|
|
81
|
-
align-items: center;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.logo-container svg {
|
|
85
|
-
max-width: 80px;
|
|
86
|
-
height: auto;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@media (prefers-color-scheme: dark) {
|
|
90
|
-
|
|
91
|
-
.logo-container svg path,
|
|
92
|
-
.logo-container svg rect,
|
|
93
|
-
.logo-container svg polygon,
|
|
94
|
-
.logo-container svg circle {
|
|
95
|
-
fill: #ffffff !important;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.content-wrapper {
|
|
100
|
-
position: relative;
|
|
101
|
-
flex: 1 1 auto;
|
|
102
|
-
overflow-y: auto;
|
|
103
|
-
padding: 24px 32px;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.content-wrapper::-webkit-scrollbar {
|
|
107
|
-
width: 6px;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.content-wrapper::-webkit-scrollbar-thumb {
|
|
111
|
-
background-color: var(--border-card);
|
|
112
|
-
border-radius: 4px;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/* --- PREVIEW LIST VIEW --- */
|
|
116
|
-
.preview-list {
|
|
117
|
-
display: flex;
|
|
118
|
-
flex-direction: column;
|
|
119
|
-
gap: 12px;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.preview-item {
|
|
123
|
-
padding: 16px;
|
|
124
|
-
border: 1px solid var(--border-card);
|
|
125
|
-
border-radius: 8px;
|
|
126
|
-
background: var(--bg-card);
|
|
127
|
-
transition: border-color 0.2s ease, background 0.2s ease;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.preview-item:hover {
|
|
131
|
-
border-color: var(--brand-sand);
|
|
132
|
-
background: var(--bg-hover);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.preview-header {
|
|
136
|
-
display: flex;
|
|
137
|
-
justify-content: space-between;
|
|
138
|
-
align-items: flex-start;
|
|
139
|
-
margin-bottom: 8px;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.preview-subject {
|
|
143
|
-
font-family: var(--font-jost);
|
|
144
|
-
font-size: 1rem;
|
|
145
|
-
color: var(--text-heading);
|
|
146
|
-
font-weight: 600;
|
|
147
|
-
margin: 0;
|
|
148
|
-
display: flex;
|
|
149
|
-
align-items: center;
|
|
150
|
-
gap: 8px;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.preview-date {
|
|
154
|
-
font-size: 0.8rem;
|
|
155
|
-
color: var(--text-main);
|
|
156
|
-
opacity: 0.8;
|
|
157
|
-
white-space: nowrap;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.preview-sender {
|
|
161
|
-
font-size: 0.85rem;
|
|
162
|
-
font-weight: 500;
|
|
163
|
-
color: var(--text-main);
|
|
164
|
-
margin-bottom: 8px;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.preview-snippet {
|
|
168
|
-
font-size: 0.85rem;
|
|
169
|
-
color: var(--text-main);
|
|
170
|
-
opacity: 0.8;
|
|
171
|
-
display: -webkit-box;
|
|
172
|
-
-webkit-line-clamp: 2;
|
|
173
|
-
-webkit-box-orient: vertical;
|
|
174
|
-
overflow: hidden;
|
|
175
|
-
text-overflow: ellipsis;
|
|
176
|
-
line-height: 1.4;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/* --- FULL EMAIL VIEW --- */
|
|
180
|
-
.email-thread {
|
|
181
|
-
display: flex;
|
|
182
|
-
flex-direction: column;
|
|
183
|
-
gap: 24px;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.email-message {
|
|
187
|
-
border: 1px solid var(--border-card);
|
|
188
|
-
border-radius: 8px;
|
|
189
|
-
background: var(--bg-card);
|
|
190
|
-
overflow: hidden;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.email-header {
|
|
194
|
-
padding: 16px;
|
|
195
|
-
border-bottom: 1px solid var(--border-card);
|
|
196
|
-
background: var(--bg-hover);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
.email-subject {
|
|
200
|
-
font-family: var(--font-lora);
|
|
201
|
-
font-size: 1.25rem;
|
|
202
|
-
color: var(--text-heading);
|
|
203
|
-
margin: 0 0 8px 0;
|
|
204
|
-
font-weight: 600;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
.email-meta {
|
|
208
|
-
display: flex;
|
|
209
|
-
justify-content: space-between;
|
|
210
|
-
align-items: flex-start;
|
|
211
|
-
font-size: 0.9rem;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.email-sender {
|
|
215
|
-
font-weight: 600;
|
|
216
|
-
color: var(--text-heading);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.email-address {
|
|
220
|
-
color: var(--text-main);
|
|
221
|
-
opacity: 0.8;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
.email-date {
|
|
225
|
-
font-size: 0.85rem;
|
|
226
|
-
color: var(--text-main);
|
|
227
|
-
opacity: 0.8;
|
|
228
|
-
white-space: nowrap;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.email-body-container {
|
|
232
|
-
padding: 24px 16px;
|
|
233
|
-
color: var(--text-main);
|
|
234
|
-
overflow-x: auto;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.attachments-section {
|
|
238
|
-
padding: 16px;
|
|
239
|
-
border-top: 1px dashed var(--border-card);
|
|
240
|
-
background: rgba(0, 0, 0, 0.02);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.attachments-title {
|
|
244
|
-
font-size: 0.85rem;
|
|
245
|
-
font-weight: 600;
|
|
246
|
-
text-transform: uppercase;
|
|
247
|
-
letter-spacing: 0.05em;
|
|
248
|
-
margin-bottom: 12px;
|
|
249
|
-
display: flex;
|
|
250
|
-
align-items: center;
|
|
251
|
-
gap: 6px;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
.attachment-list {
|
|
255
|
-
display: flex;
|
|
256
|
-
flex-direction: column;
|
|
257
|
-
gap: 8px;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.brief-section {
|
|
261
|
-
padding: 16px;
|
|
262
|
-
border-top: 2px solid var(--brand-sand);
|
|
263
|
-
background: rgba(232, 192, 161, 0.06);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
.brief-title {
|
|
267
|
-
font-size: 0.85rem;
|
|
268
|
-
font-weight: 600;
|
|
269
|
-
text-transform: uppercase;
|
|
270
|
-
letter-spacing: 0.05em;
|
|
271
|
-
margin-bottom: 12px;
|
|
272
|
-
display: flex;
|
|
273
|
-
align-items: center;
|
|
274
|
-
gap: 6px;
|
|
275
|
-
color: var(--brand-sand);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.brief-body-container {
|
|
279
|
-
padding: 12px;
|
|
280
|
-
background: var(--bg-card);
|
|
281
|
-
border: 1px solid var(--border-card);
|
|
282
|
-
border-radius: 6px;
|
|
283
|
-
font-size: 0.9em;
|
|
284
|
-
color: var(--text-main);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.attachment-item {
|
|
288
|
-
display: flex;
|
|
289
|
-
align-items: center;
|
|
290
|
-
gap: 12px;
|
|
291
|
-
padding: 10px 12px;
|
|
292
|
-
background: var(--bg-card);
|
|
293
|
-
border: 1px solid var(--border-card);
|
|
294
|
-
border-radius: 6px;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
.attachment-icon {
|
|
298
|
-
color: var(--brand-sand);
|
|
299
|
-
flex-shrink: 0;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.attachment-info {
|
|
303
|
-
display: flex;
|
|
304
|
-
flex-direction: column;
|
|
305
|
-
overflow: hidden;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
.attachment-name {
|
|
309
|
-
font-weight: 500;
|
|
310
|
-
font-size: 0.95rem;
|
|
311
|
-
color: var(--text-heading);
|
|
312
|
-
white-space: nowrap;
|
|
313
|
-
overflow: hidden;
|
|
314
|
-
text-overflow: ellipsis;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
.attachment-path {
|
|
318
|
-
font-family: ui-monospace, Consolas, monospace;
|
|
319
|
-
font-size: 0.75rem;
|
|
320
|
-
color: var(--text-main);
|
|
321
|
-
opacity: 0.7;
|
|
322
|
-
white-space: nowrap;
|
|
323
|
-
overflow: hidden;
|
|
324
|
-
text-overflow: ellipsis;
|
|
325
|
-
margin-top: 2px;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
.empty-state {
|
|
329
|
-
text-align: center;
|
|
330
|
-
padding: 40px 20px;
|
|
331
|
-
color: var(--text-main);
|
|
332
|
-
font-style: italic;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
.skeleton {
|
|
336
|
-
background: linear-gradient(90deg, var(--neutral-100) 25%, var(--border-card) 50%, var(--neutral-100) 75%);
|
|
337
|
-
background-size: 200% 100%;
|
|
338
|
-
animation: skeleton 1.5s ease-in-out infinite;
|
|
339
|
-
border-radius: 4px;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
@keyframes skeleton {
|
|
343
|
-
0% {
|
|
344
|
-
background-position: 200% 0;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
100% {
|
|
348
|
-
background-position: -200% 0;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/* --- ASYNC / PENDING STATE --- */
|
|
353
|
-
.pending-state {
|
|
354
|
-
display: flex;
|
|
355
|
-
align-items: center;
|
|
356
|
-
gap: 16px;
|
|
357
|
-
padding: 24px;
|
|
358
|
-
border: 1px solid var(--border-card);
|
|
359
|
-
border-radius: 8px;
|
|
360
|
-
background: var(--bg-card);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
.pending-spinner {
|
|
364
|
-
width: 28px;
|
|
365
|
-
height: 28px;
|
|
366
|
-
flex-shrink: 0;
|
|
367
|
-
border-radius: 50%;
|
|
368
|
-
border: 3px solid var(--border-card);
|
|
369
|
-
border-top-color: var(--brand-sand);
|
|
370
|
-
animation: pending-spin 0.9s linear infinite;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
@keyframes pending-spin {
|
|
374
|
-
to {
|
|
375
|
-
transform: rotate(360deg);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
.pending-title {
|
|
380
|
-
font-weight: 600;
|
|
381
|
-
color: var(--text-heading);
|
|
382
|
-
margin-bottom: 4px;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
.pending-message {
|
|
386
|
-
font-size: 0.9rem;
|
|
387
|
-
opacity: 0.85;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
.pending-task {
|
|
391
|
-
margin-top: 6px;
|
|
392
|
-
font-size: 0.8rem;
|
|
393
|
-
opacity: 0.7;
|
|
394
|
-
font-family: ui-monospace, Consolas, monospace;
|
|
395
|
-
}
|
|
396
|
-
</style>
|
|
397
|
-
</head>
|
|
398
|
-
|
|
399
|
-
<body>
|
|
400
|
-
<div class="adeu-card">
|
|
401
|
-
<nav class="navbar">
|
|
402
|
-
<div class="logo-container">[[ adeu_svg_code ]]</div>
|
|
403
|
-
</nav>
|
|
404
|
-
|
|
405
|
-
<div class="content-wrapper" id="content-wrapper">
|
|
406
|
-
<div id="app">
|
|
407
|
-
<div class="skeleton" style="height: 24px; width: 60%; margin-bottom: 12px;"></div>
|
|
408
|
-
<div class="skeleton" style="height: 16px; width: 40%; margin-bottom: 24px;"></div>
|
|
409
|
-
<div class="skeleton" style="height: 60px; width: 100%; margin-bottom: 12px;"></div>
|
|
410
|
-
<div class="skeleton" style="height: 60px; width: 100%;"></div>
|
|
411
|
-
</div>
|
|
412
|
-
</div>
|
|
413
|
-
</div>
|
|
414
|
-
|
|
415
|
-
<script>
|
|
416
|
-
const appDiv = document.getElementById('app');
|
|
417
|
-
const INIT_ID = 1;
|
|
418
|
-
|
|
419
|
-
function escapeHtml(unsafe) {
|
|
420
|
-
return (unsafe || "").toString()
|
|
421
|
-
.replace(/&/g, "&")
|
|
422
|
-
.replace(/</g, "<")
|
|
423
|
-
.replace(/>/g, ">")
|
|
424
|
-
.replace(/"/g, """)
|
|
425
|
-
.replace(/'/g, "'");
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function formatBytes(bytes) {
|
|
429
|
-
if (bytes === 0 || !bytes) return '0 Bytes';
|
|
430
|
-
const k = 1024;
|
|
431
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
432
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
433
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function triggerResize() {
|
|
437
|
-
const nav = document.querySelector('.navbar');
|
|
438
|
-
const wrapper = document.getElementById('content-wrapper');
|
|
439
|
-
const desiredHeight = nav.offsetHeight + wrapper.scrollHeight + 34; // 34 is chrome padding
|
|
440
|
-
const requestedHeight = Math.min(desiredHeight, 800); // Max height 800px
|
|
441
|
-
|
|
442
|
-
window.parent.postMessage({
|
|
443
|
-
jsonrpc: "2.0",
|
|
444
|
-
method: "ui/notifications/size-changed",
|
|
445
|
-
params: { height: Math.ceil(requestedHeight), width: 650 }
|
|
446
|
-
}, "*");
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const PAPERCLIP_SVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--brand-sand)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>`;
|
|
450
|
-
|
|
451
|
-
function renderPreviews(previews) {
|
|
452
|
-
if (!previews || previews.length === 0) {
|
|
453
|
-
return `<div class="empty-state">No emails found matching the search criteria.</div>`;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
let html = `<div class="preview-list">`;
|
|
457
|
-
previews.forEach(p => {
|
|
458
|
-
const dateStr = p.received_datetime ? new Date(p.received_datetime).toLocaleString() : '';
|
|
459
|
-
const attIcon = p.has_attachments ? PAPERCLIP_SVG : '';
|
|
460
|
-
|
|
461
|
-
const unreadDot = p.is_read === false ? `<span style="color: #3b82f6; margin-right: 6px; font-size: 1.2em; vertical-align: middle;">•</span>` : '';
|
|
462
|
-
|
|
463
|
-
html += `
|
|
464
|
-
<div class="preview-item">
|
|
465
|
-
<div class="preview-header">
|
|
466
|
-
<h4 class="preview-subject">${unreadDot}${escapeHtml(p.subject || 'No Subject')} ${attIcon}</h4>
|
|
467
|
-
<span class="preview-date">${escapeHtml(dateStr)}</span>
|
|
468
|
-
</div>
|
|
469
|
-
<div class="preview-sender">${escapeHtml(p.sender_name)} <${escapeHtml(p.sender_email)}></div>
|
|
470
|
-
<div class="preview-snippet">${escapeHtml(p.preview_text || 'No preview available')}</div>
|
|
471
|
-
</div>
|
|
472
|
-
`;
|
|
473
|
-
});
|
|
474
|
-
html += `</div>`;
|
|
475
|
-
return html;
|
|
476
|
-
}
|
|
477
|
-
function renderAttachmentsList(attachments) {
|
|
478
|
-
if (!attachments || attachments.length === 0) return '';
|
|
479
|
-
|
|
480
|
-
const itemsHtml = attachments.map(att => `
|
|
481
|
-
<div class="attachment-item">
|
|
482
|
-
${PAPERCLIP_SVG}
|
|
483
|
-
<div class="attachment-info">
|
|
484
|
-
<span class="attachment-name" title="${escapeHtml(att.filename)}">${escapeHtml(att.filename)} <span style="opacity:0.6; font-size:0.8em;">(${formatBytes(att.size_bytes)})</span></span>
|
|
485
|
-
${att.local_path ? `<span class="attachment-path" title="${escapeHtml(att.local_path)}">Local: ${escapeHtml(att.local_path)}</span>` : ''}
|
|
486
|
-
</div>
|
|
487
|
-
</div>
|
|
488
|
-
`).join('');
|
|
489
|
-
|
|
490
|
-
return `
|
|
491
|
-
<div class="attachments-section">
|
|
492
|
-
<div class="attachments-title">
|
|
493
|
-
${PAPERCLIP_SVG} Attachments (${attachments.length})
|
|
494
|
-
</div>
|
|
495
|
-
<div class="attachment-list">
|
|
496
|
-
${itemsHtml}
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
`;
|
|
500
|
-
}
|
|
501
|
-
function cleanEmailHTML(htmlString) {
|
|
502
|
-
if (!htmlString) return '';
|
|
503
|
-
const parser = new DOMParser();
|
|
504
|
-
const doc = parser.parseFromString(htmlString, 'text/html');
|
|
505
|
-
|
|
506
|
-
// Prune standard nested quotes to keep the UI clean
|
|
507
|
-
const selectorsToPrune = [
|
|
508
|
-
'.gmail_quote', // Gmail
|
|
509
|
-
'blockquote[type="cite"]', // Apple Mail
|
|
510
|
-
'#divRplyFwdMsg', // Outlook Desktop
|
|
511
|
-
'#appendonsend', // Outlook Web
|
|
512
|
-
'.ydp_quote' // Yahoo
|
|
513
|
-
];
|
|
514
|
-
|
|
515
|
-
selectorsToPrune.forEach(selector => {
|
|
516
|
-
doc.querySelectorAll(selector).forEach(el => el.remove());
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
// Handle Outlook's <hr tabindex="-1"> pattern
|
|
520
|
-
const hrs = doc.querySelectorAll('hr');
|
|
521
|
-
hrs.forEach(hr => {
|
|
522
|
-
if (hr.getAttribute('tabindex') === '-1' || hr.style.display === 'inline-block') {
|
|
523
|
-
// Outlook often puts the quote text right after this HR.
|
|
524
|
-
// We remove all siblings after this hr.
|
|
525
|
-
let next = hr.nextSibling;
|
|
526
|
-
while (next) {
|
|
527
|
-
const toRemove = next;
|
|
528
|
-
next = next.nextSibling;
|
|
529
|
-
toRemove.remove();
|
|
530
|
-
}
|
|
531
|
-
hr.remove();
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
return doc.body.innerHTML;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function renderFullEmail(email) {
|
|
539
|
-
const dateStr = email.received_datetime ? new Date(email.received_datetime).toLocaleString() : '';
|
|
540
|
-
|
|
541
|
-
const attachmentsHtml = renderAttachmentsList(email.attachments);
|
|
542
|
-
const wrapper = document.createElement('div');
|
|
543
|
-
wrapper.className = 'email-thread';
|
|
544
|
-
|
|
545
|
-
let threadHtml = '';
|
|
546
|
-
|
|
547
|
-
// 1. Render Target Email FIRST
|
|
548
|
-
let briefHtml = '';
|
|
549
|
-
if (email.brief_content) {
|
|
550
|
-
briefHtml = `
|
|
551
|
-
<div class="brief-section">
|
|
552
|
-
<div class="brief-title">
|
|
553
|
-
🧠 AI Strategy Brief
|
|
554
|
-
</div>
|
|
555
|
-
<div class="brief-body-container" id="brief-${email.id}"></div>
|
|
556
|
-
</div>
|
|
557
|
-
`;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
threadHtml += `
|
|
561
|
-
<div class="email-message" style="box-shadow: 0 4px 12px rgba(0,0,0,0.05); border-color: var(--brand-sand);">
|
|
562
|
-
<div class="email-header">
|
|
563
|
-
<h3 class="email-subject">${escapeHtml(email.subject || 'No Subject')}</h3>
|
|
564
|
-
<div class="email-meta">
|
|
565
|
-
<div>
|
|
566
|
-
<span class="email-sender">${escapeHtml(email.sender_name || 'Unknown')}</span>
|
|
567
|
-
<span class="email-address"><${escapeHtml(email.sender_email)}></span>
|
|
568
|
-
</div>
|
|
569
|
-
<div class="email-date">${escapeHtml(dateStr)}</div>
|
|
570
|
-
</div>
|
|
571
|
-
</div>
|
|
572
|
-
<div class="email-body-container" id="body-${email.id}"></div>
|
|
573
|
-
${attachmentsHtml}
|
|
574
|
-
${briefHtml}
|
|
575
|
-
</div>
|
|
576
|
-
`;
|
|
577
|
-
|
|
578
|
-
// 2. Render Thread History BELOW target email
|
|
579
|
-
if (email.is_thread && email.messages && email.messages.length > 0) {
|
|
580
|
-
threadHtml += `<div style="margin-top: 16px; margin-bottom: -12px; font-weight: 600; color: var(--text-main); font-size: 0.9em; text-transform: uppercase; letter-spacing: 0.05em; padding-left: 8px;">Previous Messages</div>`;
|
|
581
|
-
|
|
582
|
-
email.messages.forEach((histMsg, index) => {
|
|
583
|
-
const histDate = histMsg.received_datetime ? new Date(histMsg.received_datetime).toLocaleString() : '';
|
|
584
|
-
const histAttachmentsHtml = renderAttachmentsList(histMsg.attachments);
|
|
585
|
-
|
|
586
|
-
threadHtml += `
|
|
587
|
-
<div class="email-message" style="opacity: 0.85; transform: scale(0.98); transform-origin: top;">
|
|
588
|
-
<div class="email-header" style="padding: 12px 16px; background: rgba(0,0,0,0.02);">
|
|
589
|
-
<div class="email-meta">
|
|
590
|
-
<div>
|
|
591
|
-
<span class="email-sender">${escapeHtml(histMsg.sender_name || 'Unknown')}</span>
|
|
592
|
-
<span class="email-address"><${escapeHtml(histMsg.sender_email)}></span>
|
|
593
|
-
</div>
|
|
594
|
-
<div class="email-date">${escapeHtml(histDate)}</div>
|
|
595
|
-
</div>
|
|
596
|
-
</div>
|
|
597
|
-
<div class="email-body-container" id="history-body-${index}-${email.id}" style="max-height: 250px; overflow-y: auto; padding: 16px;"></div>
|
|
598
|
-
${histAttachmentsHtml}
|
|
599
|
-
</div>
|
|
600
|
-
`;
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
wrapper.innerHTML = threadHtml;
|
|
605
|
-
return wrapper;
|
|
606
|
-
}
|
|
607
|
-
function renderPending(data) {
|
|
608
|
-
const message = data.message || 'The mailbox search is still running on the server. The assistant will check progress again shortly.';
|
|
609
|
-
const taskLine = data.task_id
|
|
610
|
-
? `<div class="pending-task">Task: ${escapeHtml(data.task_id)}</div>`
|
|
611
|
-
: '';
|
|
612
|
-
return `
|
|
613
|
-
<div class="pending-state">
|
|
614
|
-
<div class="pending-spinner"></div>
|
|
615
|
-
<div class="pending-info">
|
|
616
|
-
<div class="pending-title">Still processing…</div>
|
|
617
|
-
<div class="pending-message">${escapeHtml(message)}</div>
|
|
618
|
-
${taskLine}
|
|
619
|
-
</div>
|
|
620
|
-
</div>
|
|
621
|
-
`;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Large mailboxes can keep the backend polling for a minute or more before
|
|
625
|
-
// the first tool result arrives. Tell the human the search is still running
|
|
626
|
-
// rather than leaving a bare skeleton with no sign of progress.
|
|
627
|
-
let slowLoadStage2 = null;
|
|
628
|
-
const slowLoadNotice = setTimeout(() => {
|
|
629
|
-
if (appDiv.querySelector('.skeleton')) {
|
|
630
|
-
const notice = document.createElement('div');
|
|
631
|
-
notice.className = 'empty-state';
|
|
632
|
-
notice.textContent = 'Still working — large mailbox searches can take a minute or two…';
|
|
633
|
-
appDiv.appendChild(notice);
|
|
634
|
-
triggerResize();
|
|
635
|
-
// If no result ever arrives (e.g. the tool call errored and the host
|
|
636
|
-
// never forwards a tool-result), stop implying progress.
|
|
637
|
-
slowLoadStage2 = setTimeout(() => {
|
|
638
|
-
if (appDiv.querySelector('.skeleton')) {
|
|
639
|
-
notice.textContent = 'This is taking longer than expected — the search may have failed. Check the chat for details.';
|
|
640
|
-
triggerResize();
|
|
641
|
-
}
|
|
642
|
-
}, 150000);
|
|
643
|
-
}
|
|
644
|
-
}, 20000);
|
|
645
|
-
|
|
646
|
-
window.addEventListener("message", (event) => {
|
|
647
|
-
try {
|
|
648
|
-
if (!event.data || event.data.jsonrpc !== "2.0") return;
|
|
649
|
-
const msg = event.data;
|
|
650
|
-
|
|
651
|
-
if (msg.id === INIT_ID) {
|
|
652
|
-
window.parent.postMessage({ jsonrpc: "2.0", method: "ui/notifications/initialized", params: {} }, "*");
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (msg.method === "ui/notifications/tool-result") {
|
|
657
|
-
clearTimeout(slowLoadNotice);
|
|
658
|
-
if (slowLoadStage2) clearTimeout(slowLoadStage2);
|
|
659
|
-
const result = msg.params;
|
|
660
|
-
// The MCP wire format is camelCase `structuredContent`, but tolerate
|
|
661
|
-
// snake_case in case a host forwards the Python server's payload unnormalized.
|
|
662
|
-
const data = result ? (result.structuredContent || result.structured_content) : null;
|
|
663
|
-
|
|
664
|
-
if (!data) {
|
|
665
|
-
// Error results and text-only results carry no structured payload.
|
|
666
|
-
// Never leave the skeleton spinning — surface whatever text we have.
|
|
667
|
-
const textParts = (result && Array.isArray(result.content))
|
|
668
|
-
? result.content.filter(c => c && c.type === "text" && c.text).map(c => c.text)
|
|
669
|
-
: [];
|
|
670
|
-
const fallbackText = textParts.join("\n").trim();
|
|
671
|
-
appDiv.innerHTML = `<div class="empty-state" style="white-space: pre-wrap;">${escapeHtml(fallbackText || 'The email tool returned no displayable data. Check the chat for details.')}</div>`;
|
|
672
|
-
triggerResize();
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
appDiv.innerHTML = ''; // Clear skeleton
|
|
677
|
-
|
|
678
|
-
if (data.status === "pending") {
|
|
679
|
-
appDiv.innerHTML = renderPending(data);
|
|
680
|
-
setTimeout(triggerResize, 50);
|
|
681
|
-
}
|
|
682
|
-
else if (data.type === "previews") {
|
|
683
|
-
appDiv.innerHTML = renderPreviews(data.previews);
|
|
684
|
-
setTimeout(triggerResize, 50);
|
|
685
|
-
}
|
|
686
|
-
else if (data.type === "full_email" && data.full_email) {
|
|
687
|
-
const email = data.full_email;
|
|
688
|
-
const emailEl = renderFullEmail(email);
|
|
689
|
-
appDiv.appendChild(emailEl);
|
|
690
|
-
|
|
691
|
-
// Inject Target Message Body
|
|
692
|
-
const bodyContainer = document.getElementById(`body-${email.id}`);
|
|
693
|
-
if (bodyContainer) {
|
|
694
|
-
const cleanedBody = cleanEmailHTML(email.body_html);
|
|
695
|
-
const shadow = bodyContainer.attachShadow({ mode: 'open' });
|
|
696
|
-
shadow.innerHTML = `
|
|
697
|
-
<style>
|
|
698
|
-
:host { all: initial; display: block; font-family: system-ui, sans-serif; line-height: 1.5; color: var(--text-main, #333); }
|
|
699
|
-
@media (prefers-color-scheme: dark) { :host { color: #e5e5e5; } a { color: #60a5fa; } }
|
|
700
|
-
img { max-width: 100%; height: auto; }
|
|
701
|
-
a { target-name: new; target-new: window; }
|
|
702
|
-
</style>
|
|
703
|
-
<div>${cleanedBody || '<em>Empty Message</em>'}</div>
|
|
704
|
-
`;
|
|
705
|
-
}
|
|
706
|
-
// Inject Brief Content (styled HTML from DB)
|
|
707
|
-
if (email.brief_content) {
|
|
708
|
-
const briefContainer = document.getElementById(`brief-${email.id}`);
|
|
709
|
-
if (briefContainer) {
|
|
710
|
-
const shadow = briefContainer.attachShadow({ mode: 'open' });
|
|
711
|
-
shadow.innerHTML = `
|
|
712
|
-
<style>
|
|
713
|
-
:host { all: initial; display: block; font-family: system-ui, sans-serif; line-height: 1.6; color: var(--text-main, #333); }
|
|
714
|
-
@media (prefers-color-scheme: dark) { :host { color: #e5e5e5; } a { color: #60a5fa; } }
|
|
715
|
-
img { max-width: 100%; height: auto; }
|
|
716
|
-
table { width: 100%; border-collapse: collapse; }
|
|
717
|
-
td, th { padding: 4px 8px; }
|
|
718
|
-
</style>
|
|
719
|
-
<div>${email.brief_content}</div>
|
|
720
|
-
`;
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
// Inject Thread History Bodies
|
|
724
|
-
if (email.is_thread && email.messages) {
|
|
725
|
-
email.messages.forEach((histMsg, index) => {
|
|
726
|
-
const histContainer = document.getElementById(`history-body-${index}-${email.id}`);
|
|
727
|
-
if (histContainer) {
|
|
728
|
-
const cleanedHist = cleanEmailHTML(histMsg.body_html);
|
|
729
|
-
const shadow = histContainer.attachShadow({ mode: 'open' });
|
|
730
|
-
shadow.innerHTML = `
|
|
731
|
-
<style>
|
|
732
|
-
:host { all: initial; display: block; font-family: system-ui, sans-serif; line-height: 1.5; color: var(--text-main, #333); font-size: 0.9em; }
|
|
733
|
-
@media (prefers-color-scheme: dark) { :host { color: #e5e5e5; } a { color: #60a5fa; } }
|
|
734
|
-
img { max-width: 100%; height: auto; }
|
|
735
|
-
a { target-name: new; target-new: window; }
|
|
736
|
-
</style>
|
|
737
|
-
<div>${cleanedHist || '<em>Empty Message</em>'}</div>
|
|
738
|
-
`;
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
setTimeout(triggerResize, 100);
|
|
744
|
-
}
|
|
745
|
-
else {
|
|
746
|
-
appDiv.innerHTML = `<div class="empty-state">No email data found.</div>`;
|
|
747
|
-
triggerResize();
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
} catch (error) {
|
|
751
|
-
console.error("UI Render Error:", error);
|
|
752
|
-
appDiv.innerHTML = `<div class="empty-state" style="color: red;">Error rendering UI: ${error.toString()}</div>`;
|
|
753
|
-
triggerResize();
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
const observer = new ResizeObserver(() => triggerResize());
|
|
758
|
-
observer.observe(document.getElementById('content-wrapper'));
|
|
759
|
-
|
|
760
|
-
window.parent.postMessage({
|
|
761
|
-
jsonrpc: "2.0", id: INIT_ID, method: "ui/initialize",
|
|
762
|
-
params: {
|
|
763
|
-
appInfo: { name: "Adeu Email Inbox", version: "1.0.0" },
|
|
764
|
-
appCapabilities: {}, protocolVersion: "2025-11-21"
|
|
765
|
-
}
|
|
766
|
-
}, "*");
|
|
767
|
-
</script>
|
|
768
|
-
</body>
|
|
769
|
-
|
|
770
|
-
</html>
|