@adeu/mcp-server 1.19.1 → 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.
@@ -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, "&amp;")
422
- .replace(/</g, "&lt;")
423
- .replace(/>/g, "&gt;")
424
- .replace(/"/g, "&quot;")
425
- .replace(/'/g, "&#039;");
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)} &lt;${escapeHtml(p.sender_email)}&gt;</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">&lt;${escapeHtml(email.sender_email)}&gt;</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">&lt;${escapeHtml(histMsg.sender_email)}&gt;</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&hellip;</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>