From ba0bfe0af51693fd7fe2090234fe4c70c300a69d Mon Sep 17 00:00:00 2001 From: Chris OConnor Date: Wed, 10 Dec 2025 17:05:39 +1100 Subject: [PATCH 1/2] include EML and MSG --- app/frontend/package-lock.json | 110 +++++++ app/frontend/package.json | 5 +- .../AnalysisPanel/AnalysisPanel.module.css | 7 + .../AnalysisPanel/AnalysisPanel.tsx | 11 +- .../EmailViewer/EmailViewer.module.css | 50 +++ .../components/EmailViewer/EmailViewer.tsx | 309 ++++++++++++++++++ .../src/components/EmailViewer/index.ts | 2 + app/frontend/src/index.tsx | 7 + app/frontend/src/pages/ask/Ask.module.css | 4 +- app/frontend/src/pages/chat/Chat.module.css | 1 - app/frontend/src/vite-env.d.ts | 11 + app/frontend/vite.config.ts | 21 +- 12 files changed, 531 insertions(+), 7 deletions(-) create mode 100644 app/frontend/src/components/EmailViewer/EmailViewer.module.css create mode 100644 app/frontend/src/components/EmailViewer/EmailViewer.tsx create mode 100644 app/frontend/src/components/EmailViewer/index.ts diff --git a/app/frontend/package-lock.json b/app/frontend/package-lock.json index aacaf655cf..320d3c5318 100644 --- a/app/frontend/package-lock.json +++ b/app/frontend/package-lock.json @@ -13,13 +13,16 @@ "@fluentui/react": "^8.112.5", "@fluentui/react-components": "^9.56.2", "@fluentui/react-icons": "^2.0.265", + "@kenjiuno/msgreader": "^1.27.0-alpha.3", "@react-spring/web": "^9.7.5", + "buffer": "^6.0.3", "dompurify": "^3.2.4", "i18next": "^24.2.0", "i18next-browser-languagedetector": "^8.0.2", "i18next-http-backend": "^3.0.1", "idb": "^8.0.0", "ndjson-readablestream": "^1.2.0", + "postal-mime": "^2.6.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-helmet-async": "^2.0.5", @@ -2509,6 +2512,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kenjiuno/decompressrtf": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@kenjiuno/decompressrtf/-/decompressrtf-0.1.4.tgz", + "integrity": "sha512-v9c/iFz17jRWyd2cRnrvJg4VOg/4I/VCk+bG8JnoX2gJ9sAesPzo3uTqcmlVXdpasTI8hChpBVw00pghKe3qTQ==", + "license": "BSD-2-Clause" + }, + "node_modules/@kenjiuno/msgreader": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@kenjiuno/msgreader/-/msgreader-1.27.0.tgz", + "integrity": "sha512-gElRDjxQGZQBVAqQYfZ4g82DqBQQubg9pg+nz6DsWG7tTx0TozNqaglGovT6tz3vqNE07u/wu88w8ymU1BIxYw==", + "license": "Apache-2.0", + "dependencies": { + "@kenjiuno/decompressrtf": "^0.1.3", + "iconv-lite": "^0.6.3" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/@microsoft/load-themed-styles": { "version": "1.10.295", "license": "MIT" @@ -3080,6 +3102,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/browserslist": { "version": "4.25.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", @@ -3113,6 +3155,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001727", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", @@ -3761,11 +3827,43 @@ "cross-fetch": "4.0.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/idb": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz", "integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==" }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/inline-style-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", @@ -4958,6 +5056,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postal-mime": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.6.1.tgz", + "integrity": "sha512-YnNYvLTsWzk1Mpf1drc7XZPR1c2XInI94H5RY/hZ9vzTSYjLw9zuebdi/K0yfR3PVnedQZUn2umTyxfw9yOWBA==", + "license": "MIT-0" + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -5399,6 +5503,12 @@ "@babel/runtime": "^7.1.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.20.2", "license": "MIT", diff --git a/app/frontend/package.json b/app/frontend/package.json index 23cc9f3e02..66bead7624 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -17,6 +17,7 @@ "@fluentui/react": "^8.112.5", "@fluentui/react-components": "^9.56.2", "@fluentui/react-icons": "^2.0.265", + "@kenjiuno/msgreader": "^1.27.0-alpha.3", "@react-spring/web": "^9.7.5", "dompurify": "^3.2.4", "i18next": "^24.2.0", @@ -24,6 +25,7 @@ "i18next-http-backend": "^3.0.1", "idb": "^8.0.0", "ndjson-readablestream": "^1.2.0", + "postal-mime": "^2.6.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-helmet-async": "^2.0.5", @@ -33,7 +35,8 @@ "react-syntax-highlighter": "^16.1.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", - "scheduler": "^0.20.2" + "scheduler": "^0.20.2", + "buffer": "^6.0.3" }, "devDependencies": { "@types/dom-speech-recognition": "^0.0.7", diff --git a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css index fd39388d25..e5ff0462a9 100644 --- a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css +++ b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css @@ -82,6 +82,13 @@ object-fit: contain; } +.citationContainer { + width: 100%; + overflow-x: hidden; + overflow-y: auto; + box-sizing: border-box; +} + .header { color: #123bb6; position: relative; diff --git a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx index bcbe227640..c45791038f 100644 --- a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx +++ b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next"; import { ChatAppResponse, getHeaders } from "../../api"; import { getToken, useLogin } from "../../authConfig"; import { MarkdownViewer } from "../MarkdownViewer"; +import { EmailViewer } from "../EmailViewer"; import { SupportingContent } from "../SupportingContent"; import styles from "./AnalysisPanel.module.css"; import { AnalysisPanelTabs } from "./AnalysisPanelTabs"; @@ -44,7 +45,8 @@ export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeigh if (activeCitation) { // Get hash from the URL as it may contain #page=N // which helps browser PDF renderer jump to correct page N - const originalHash = activeCitation.indexOf("#") ? activeCitation.split("#")[1] : ""; + const hashIndex = activeCitation.indexOf("#"); + const originalHash = hashIndex >= 0 ? activeCitation.split("#")[1] : ""; const response = await fetch(activeCitation, { method: "GET", headers: await getHeaders(token) @@ -73,6 +75,9 @@ export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeigh return Citation Image; case "md": return ; + case "eml": + case "msg": + return ; default: return