Boopster commited on
Commit
f9b250f
·
1 Parent(s): cd73917

feat: Add PDF export functionality to report preview by integrating html2pdf.js and updating the UI.

Browse files
frontend/package-lock.json CHANGED
@@ -11,6 +11,7 @@
11
  "@langchain/core": "^1.1.18",
12
  "@langchain/langgraph": "^1.1.2",
13
  "@langchain/langgraph-sdk": "^1.5.5",
 
14
  "lucide-react": "^0.563.0",
15
  "next": "16.1.6",
16
  "react": "19.2.3",
@@ -233,6 +234,15 @@
233
  "node": ">=6.0.0"
234
  }
235
  },
 
 
 
 
 
 
 
 
 
236
  "node_modules/@babel/template": {
237
  "version": "7.28.6",
238
  "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -1786,6 +1796,19 @@
1786
  "undici-types": "~6.21.0"
1787
  }
1788
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
1789
  "node_modules/@types/react": {
1790
  "version": "19.2.10",
1791
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
@@ -1805,6 +1828,13 @@
1805
  "@types/react": "^19.2.0"
1806
  }
1807
  },
 
 
 
 
 
 
 
1808
  "node_modules/@types/unist": {
1809
  "version": "3.0.3",
1810
  "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -2663,6 +2693,15 @@
2663
  "dev": true,
2664
  "license": "MIT"
2665
  },
 
 
 
 
 
 
 
 
 
2666
  "node_modules/base64-js": {
2667
  "version": "1.5.1",
2668
  "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -2842,6 +2881,26 @@
2842
  ],
2843
  "license": "CC-BY-4.0"
2844
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2845
  "node_modules/ccount": {
2846
  "version": "2.0.1",
2847
  "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
@@ -2965,6 +3024,18 @@
2965
  "dev": true,
2966
  "license": "MIT"
2967
  },
 
 
 
 
 
 
 
 
 
 
 
 
2968
  "node_modules/cross-spawn": {
2969
  "version": "7.0.6",
2970
  "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2980,6 +3051,15 @@
2980
  "node": ">= 8"
2981
  }
2982
  },
 
 
 
 
 
 
 
 
 
2983
  "node_modules/csstype": {
2984
  "version": "3.2.3",
2985
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -3174,6 +3254,15 @@
3174
  "node": ">=0.10.0"
3175
  }
3176
  },
 
 
 
 
 
 
 
 
 
3177
  "node_modules/dunder-proto": {
3178
  "version": "1.0.1",
3179
  "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -3914,6 +4003,17 @@
3914
  "dev": true,
3915
  "license": "MIT"
3916
  },
 
 
 
 
 
 
 
 
 
 
 
3917
  "node_modules/fastq": {
3918
  "version": "1.20.1",
3919
  "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
@@ -3924,6 +4024,12 @@
3924
  "reusify": "^1.0.4"
3925
  }
3926
  },
 
 
 
 
 
 
3927
  "node_modules/file-entry-cache": {
3928
  "version": "8.0.0",
3929
  "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4358,6 +4464,30 @@
4358
  "url": "https://opencollective.com/unified"
4359
  }
4360
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4361
  "node_modules/ignore": {
4362
  "version": "5.3.2",
4363
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4416,6 +4546,12 @@
4416
  "node": ">= 0.4"
4417
  }
4418
  },
 
 
 
 
 
 
4419
  "node_modules/is-alphabetical": {
4420
  "version": "2.0.1",
4421
  "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
@@ -5017,6 +5153,23 @@
5017
  "node": ">=6"
5018
  }
5019
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5020
  "node_modules/jsx-ast-utils": {
5021
  "version": "3.3.5",
5022
  "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -6545,6 +6698,12 @@
6545
  "url": "https://github.com/sponsors/sindresorhus"
6546
  }
6547
  },
 
 
 
 
 
 
6548
  "node_modules/parent-module": {
6549
  "version": "1.0.1",
6550
  "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -6610,6 +6769,13 @@
6610
  "dev": true,
6611
  "license": "MIT"
6612
  },
 
 
 
 
 
 
 
6613
  "node_modules/picocolors": {
6614
  "version": "1.1.1",
6615
  "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -6731,6 +6897,16 @@
6731
  ],
6732
  "license": "MIT"
6733
  },
 
 
 
 
 
 
 
 
 
 
6734
  "node_modules/react": {
6735
  "version": "19.2.3",
6736
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
@@ -6809,6 +6985,13 @@
6809
  "url": "https://github.com/sponsors/ljharb"
6810
  }
6811
  },
 
 
 
 
 
 
 
6812
  "node_modules/regexp.prototype.flags": {
6813
  "version": "1.5.4",
6814
  "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -6915,6 +7098,16 @@
6915
  "node": ">=0.10.0"
6916
  }
6917
  },
 
 
 
 
 
 
 
 
 
 
6918
  "node_modules/run-parallel": {
6919
  "version": "1.2.0",
6920
  "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -7248,6 +7441,16 @@
7248
  "dev": true,
7249
  "license": "MIT"
7250
  },
 
 
 
 
 
 
 
 
 
 
7251
  "node_modules/stop-iteration-iterator": {
7252
  "version": "1.1.0",
7253
  "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -7478,6 +7681,16 @@
7478
  "url": "https://github.com/sponsors/ljharb"
7479
  }
7480
  },
 
 
 
 
 
 
 
 
 
 
7481
  "node_modules/tailwindcss": {
7482
  "version": "4.1.18",
7483
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
@@ -7499,6 +7712,15 @@
7499
  "url": "https://opencollective.com/webpack"
7500
  }
7501
  },
 
 
 
 
 
 
 
 
 
7502
  "node_modules/tinyglobby": {
7503
  "version": "0.2.15",
7504
  "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -7943,6 +8165,15 @@
7943
  "punycode": "^2.1.0"
7944
  }
7945
  },
 
 
 
 
 
 
 
 
 
7946
  "node_modules/uuid": {
7947
  "version": "13.0.0",
7948
  "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
 
11
  "@langchain/core": "^1.1.18",
12
  "@langchain/langgraph": "^1.1.2",
13
  "@langchain/langgraph-sdk": "^1.5.5",
14
+ "html2pdf.js": "^0.14.0",
15
  "lucide-react": "^0.563.0",
16
  "next": "16.1.6",
17
  "react": "19.2.3",
 
234
  "node": ">=6.0.0"
235
  }
236
  },
237
+ "node_modules/@babel/runtime": {
238
+ "version": "7.28.6",
239
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
240
+ "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
241
+ "license": "MIT",
242
+ "engines": {
243
+ "node": ">=6.9.0"
244
+ }
245
+ },
246
  "node_modules/@babel/template": {
247
  "version": "7.28.6",
248
  "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
 
1796
  "undici-types": "~6.21.0"
1797
  }
1798
  },
1799
+ "node_modules/@types/pako": {
1800
+ "version": "2.0.4",
1801
+ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
1802
+ "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
1803
+ "license": "MIT"
1804
+ },
1805
+ "node_modules/@types/raf": {
1806
+ "version": "3.4.3",
1807
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
1808
+ "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
1809
+ "license": "MIT",
1810
+ "optional": true
1811
+ },
1812
  "node_modules/@types/react": {
1813
  "version": "19.2.10",
1814
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
 
1828
  "@types/react": "^19.2.0"
1829
  }
1830
  },
1831
+ "node_modules/@types/trusted-types": {
1832
+ "version": "2.0.7",
1833
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
1834
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
1835
+ "license": "MIT",
1836
+ "optional": true
1837
+ },
1838
  "node_modules/@types/unist": {
1839
  "version": "3.0.3",
1840
  "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
 
2693
  "dev": true,
2694
  "license": "MIT"
2695
  },
2696
+ "node_modules/base64-arraybuffer": {
2697
+ "version": "1.0.2",
2698
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
2699
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
2700
+ "license": "MIT",
2701
+ "engines": {
2702
+ "node": ">= 0.6.0"
2703
+ }
2704
+ },
2705
  "node_modules/base64-js": {
2706
  "version": "1.5.1",
2707
  "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
 
2881
  ],
2882
  "license": "CC-BY-4.0"
2883
  },
2884
+ "node_modules/canvg": {
2885
+ "version": "3.0.11",
2886
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
2887
+ "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
2888
+ "license": "MIT",
2889
+ "optional": true,
2890
+ "dependencies": {
2891
+ "@babel/runtime": "^7.12.5",
2892
+ "@types/raf": "^3.4.0",
2893
+ "core-js": "^3.8.3",
2894
+ "raf": "^3.4.1",
2895
+ "regenerator-runtime": "^0.13.7",
2896
+ "rgbcolor": "^1.0.1",
2897
+ "stackblur-canvas": "^2.0.0",
2898
+ "svg-pathdata": "^6.0.3"
2899
+ },
2900
+ "engines": {
2901
+ "node": ">=10.0.0"
2902
+ }
2903
+ },
2904
  "node_modules/ccount": {
2905
  "version": "2.0.1",
2906
  "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
 
3024
  "dev": true,
3025
  "license": "MIT"
3026
  },
3027
+ "node_modules/core-js": {
3028
+ "version": "3.48.0",
3029
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz",
3030
+ "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==",
3031
+ "hasInstallScript": true,
3032
+ "license": "MIT",
3033
+ "optional": true,
3034
+ "funding": {
3035
+ "type": "opencollective",
3036
+ "url": "https://opencollective.com/core-js"
3037
+ }
3038
+ },
3039
  "node_modules/cross-spawn": {
3040
  "version": "7.0.6",
3041
  "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
 
3051
  "node": ">= 8"
3052
  }
3053
  },
3054
+ "node_modules/css-line-break": {
3055
+ "version": "2.1.0",
3056
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
3057
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
3058
+ "license": "MIT",
3059
+ "dependencies": {
3060
+ "utrie": "^1.0.2"
3061
+ }
3062
+ },
3063
  "node_modules/csstype": {
3064
  "version": "3.2.3",
3065
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
 
3254
  "node": ">=0.10.0"
3255
  }
3256
  },
3257
+ "node_modules/dompurify": {
3258
+ "version": "3.3.1",
3259
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
3260
+ "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==",
3261
+ "license": "(MPL-2.0 OR Apache-2.0)",
3262
+ "optionalDependencies": {
3263
+ "@types/trusted-types": "^2.0.7"
3264
+ }
3265
+ },
3266
  "node_modules/dunder-proto": {
3267
  "version": "1.0.1",
3268
  "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
 
4003
  "dev": true,
4004
  "license": "MIT"
4005
  },
4006
+ "node_modules/fast-png": {
4007
+ "version": "6.4.0",
4008
+ "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
4009
+ "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
4010
+ "license": "MIT",
4011
+ "dependencies": {
4012
+ "@types/pako": "^2.0.3",
4013
+ "iobuffer": "^5.3.2",
4014
+ "pako": "^2.1.0"
4015
+ }
4016
+ },
4017
  "node_modules/fastq": {
4018
  "version": "1.20.1",
4019
  "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
 
4024
  "reusify": "^1.0.4"
4025
  }
4026
  },
4027
+ "node_modules/fflate": {
4028
+ "version": "0.8.2",
4029
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
4030
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
4031
+ "license": "MIT"
4032
+ },
4033
  "node_modules/file-entry-cache": {
4034
  "version": "8.0.0",
4035
  "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
 
4464
  "url": "https://opencollective.com/unified"
4465
  }
4466
  },
4467
+ "node_modules/html2canvas": {
4468
+ "version": "1.4.1",
4469
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
4470
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
4471
+ "license": "MIT",
4472
+ "dependencies": {
4473
+ "css-line-break": "^2.1.0",
4474
+ "text-segmentation": "^1.0.3"
4475
+ },
4476
+ "engines": {
4477
+ "node": ">=8.0.0"
4478
+ }
4479
+ },
4480
+ "node_modules/html2pdf.js": {
4481
+ "version": "0.14.0",
4482
+ "resolved": "https://registry.npmjs.org/html2pdf.js/-/html2pdf.js-0.14.0.tgz",
4483
+ "integrity": "sha512-yvNJgE/8yru2UeGflkPdjW8YEY+nDH5X7/2WG4uiuSCwYiCp8PZ8EKNiTAa6HxJ1NjC51fZSIEq6xld5CADKBQ==",
4484
+ "license": "MIT",
4485
+ "dependencies": {
4486
+ "dompurify": "^3.3.1",
4487
+ "html2canvas": "^1.0.0",
4488
+ "jspdf": "^4.0.0"
4489
+ }
4490
+ },
4491
  "node_modules/ignore": {
4492
  "version": "5.3.2",
4493
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
 
4546
  "node": ">= 0.4"
4547
  }
4548
  },
4549
+ "node_modules/iobuffer": {
4550
+ "version": "5.4.0",
4551
+ "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
4552
+ "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
4553
+ "license": "MIT"
4554
+ },
4555
  "node_modules/is-alphabetical": {
4556
  "version": "2.0.1",
4557
  "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
 
5153
  "node": ">=6"
5154
  }
5155
  },
5156
+ "node_modules/jspdf": {
5157
+ "version": "4.1.0",
5158
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.1.0.tgz",
5159
+ "integrity": "sha512-xd1d/XRkwqnsq6FP3zH1Q+Ejqn2ULIJeDZ+FTKpaabVpZREjsJKRJwuokTNgdqOU+fl55KgbvgZ1pRTSWCP2kQ==",
5160
+ "license": "MIT",
5161
+ "dependencies": {
5162
+ "@babel/runtime": "^7.28.4",
5163
+ "fast-png": "^6.2.0",
5164
+ "fflate": "^0.8.1"
5165
+ },
5166
+ "optionalDependencies": {
5167
+ "canvg": "^3.0.11",
5168
+ "core-js": "^3.6.0",
5169
+ "dompurify": "^3.3.1",
5170
+ "html2canvas": "^1.0.0-rc.5"
5171
+ }
5172
+ },
5173
  "node_modules/jsx-ast-utils": {
5174
  "version": "3.3.5",
5175
  "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
 
6698
  "url": "https://github.com/sponsors/sindresorhus"
6699
  }
6700
  },
6701
+ "node_modules/pako": {
6702
+ "version": "2.1.0",
6703
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
6704
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
6705
+ "license": "(MIT AND Zlib)"
6706
+ },
6707
  "node_modules/parent-module": {
6708
  "version": "1.0.1",
6709
  "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
 
6769
  "dev": true,
6770
  "license": "MIT"
6771
  },
6772
+ "node_modules/performance-now": {
6773
+ "version": "2.1.0",
6774
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
6775
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
6776
+ "license": "MIT",
6777
+ "optional": true
6778
+ },
6779
  "node_modules/picocolors": {
6780
  "version": "1.1.1",
6781
  "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
 
6897
  ],
6898
  "license": "MIT"
6899
  },
6900
+ "node_modules/raf": {
6901
+ "version": "3.4.1",
6902
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
6903
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
6904
+ "license": "MIT",
6905
+ "optional": true,
6906
+ "dependencies": {
6907
+ "performance-now": "^2.1.0"
6908
+ }
6909
+ },
6910
  "node_modules/react": {
6911
  "version": "19.2.3",
6912
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
 
6985
  "url": "https://github.com/sponsors/ljharb"
6986
  }
6987
  },
6988
+ "node_modules/regenerator-runtime": {
6989
+ "version": "0.13.11",
6990
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
6991
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
6992
+ "license": "MIT",
6993
+ "optional": true
6994
+ },
6995
  "node_modules/regexp.prototype.flags": {
6996
  "version": "1.5.4",
6997
  "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
 
7098
  "node": ">=0.10.0"
7099
  }
7100
  },
7101
+ "node_modules/rgbcolor": {
7102
+ "version": "1.0.1",
7103
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
7104
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
7105
+ "license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
7106
+ "optional": true,
7107
+ "engines": {
7108
+ "node": ">= 0.8.15"
7109
+ }
7110
+ },
7111
  "node_modules/run-parallel": {
7112
  "version": "1.2.0",
7113
  "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
 
7441
  "dev": true,
7442
  "license": "MIT"
7443
  },
7444
+ "node_modules/stackblur-canvas": {
7445
+ "version": "2.7.0",
7446
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
7447
+ "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
7448
+ "license": "MIT",
7449
+ "optional": true,
7450
+ "engines": {
7451
+ "node": ">=0.1.14"
7452
+ }
7453
+ },
7454
  "node_modules/stop-iteration-iterator": {
7455
  "version": "1.1.0",
7456
  "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
 
7681
  "url": "https://github.com/sponsors/ljharb"
7682
  }
7683
  },
7684
+ "node_modules/svg-pathdata": {
7685
+ "version": "6.0.3",
7686
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
7687
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
7688
+ "license": "MIT",
7689
+ "optional": true,
7690
+ "engines": {
7691
+ "node": ">=12.0.0"
7692
+ }
7693
+ },
7694
  "node_modules/tailwindcss": {
7695
  "version": "4.1.18",
7696
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
 
7712
  "url": "https://opencollective.com/webpack"
7713
  }
7714
  },
7715
+ "node_modules/text-segmentation": {
7716
+ "version": "1.0.3",
7717
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
7718
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
7719
+ "license": "MIT",
7720
+ "dependencies": {
7721
+ "utrie": "^1.0.2"
7722
+ }
7723
+ },
7724
  "node_modules/tinyglobby": {
7725
  "version": "0.2.15",
7726
  "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
 
8165
  "punycode": "^2.1.0"
8166
  }
8167
  },
8168
+ "node_modules/utrie": {
8169
+ "version": "1.0.2",
8170
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
8171
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
8172
+ "license": "MIT",
8173
+ "dependencies": {
8174
+ "base64-arraybuffer": "^1.0.2"
8175
+ }
8176
+ },
8177
  "node_modules/uuid": {
8178
  "version": "13.0.0",
8179
  "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
frontend/package.json CHANGED
@@ -12,6 +12,7 @@
12
  "@langchain/core": "^1.1.18",
13
  "@langchain/langgraph": "^1.1.2",
14
  "@langchain/langgraph-sdk": "^1.5.5",
 
15
  "lucide-react": "^0.563.0",
16
  "next": "16.1.6",
17
  "react": "19.2.3",
 
12
  "@langchain/core": "^1.1.18",
13
  "@langchain/langgraph": "^1.1.2",
14
  "@langchain/langgraph-sdk": "^1.5.5",
15
+ "html2pdf.js": "^0.14.0",
16
  "lucide-react": "^0.563.0",
17
  "next": "16.1.6",
18
  "react": "19.2.3",
frontend/src/registry/ReportPreview.tsx CHANGED
@@ -1,7 +1,8 @@
1
  "use client";
2
 
 
3
  import ReactMarkdown from "react-markdown";
4
- import { FileText, Download, Send, Loader2, AlertCircle, CheckCircle } from "lucide-react";
5
 
6
  interface ReportPreviewProps {
7
  id?: string;
@@ -17,9 +18,53 @@ export function ReportPreview({ title, content, status, doctorName, doctorEmail,
17
  const isComplete = status === "complete";
18
  const isStreaming = status === "streaming" || status === "loading";
19
  const canSend = isComplete && !!doctorEmail;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  return (
22
- <div className="bg-surface-elevated border border-surface-overlay rounded-2xl overflow-hidden flex flex-col max-h-[550px] shadow-2xl group/report relative">
23
  <div className="absolute top-0 right-0 p-4 opacity-5 pointer-events-none group-hover:opacity-10 transition-opacity text-cta">
24
  <FileText className="w-24 h-24" />
25
  </div>
@@ -69,11 +114,12 @@ export function ReportPreview({ title, content, status, doctorName, doctorEmail,
69
  <div className="px-6 py-5 bg-surface-subtle border-t border-surface-overlay flex flex-col gap-4">
70
  <div className="flex items-center gap-4">
71
  <button
72
- disabled={!isComplete}
 
73
  className="flex-1 flex items-center justify-center gap-2 px-4 py-3 bg-surface-elevated border border-surface-overlay hover:border-text-muted disabled:opacity-30 rounded-xl text-[11px] font-bold text-secondary transition-all"
74
  >
75
- <Download className="w-4 h-4" />
76
- Export PDF
77
  </button>
78
  <button
79
  onClick={onSend}
 
1
  "use client";
2
 
3
+ import React from "react";
4
  import ReactMarkdown from "react-markdown";
5
+ import { FileText, Download, Send, Loader2, AlertCircle } from "lucide-react";
6
 
7
  interface ReportPreviewProps {
8
  id?: string;
 
18
  const isComplete = status === "complete";
19
  const isStreaming = status === "streaming" || status === "loading";
20
  const canSend = isComplete && !!doctorEmail;
21
+ const [isExporting, setIsExporting] = React.useState(false);
22
+
23
+ const handleExportPDF = async () => {
24
+ if (!content || isExporting) return;
25
+ setIsExporting(true);
26
+ try {
27
+ const html2pdf = (await import("html2pdf.js")).default;
28
+ const dateStr = new Date().toLocaleDateString("en-GB", {
29
+ day: "numeric",
30
+ month: "long",
31
+ year: "numeric",
32
+ });
33
+
34
+ const container = document.createElement("div");
35
+ container.innerHTML = `
36
+ <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 700px; margin: 0 auto; padding: 32px; color: #1a1a1a; line-height: 1.8;">
37
+ <h1 style="font-size: 1.5rem; border-bottom: 2px solid #b39cd0; padding-bottom: 8px; margin-bottom: 4px;">${title}</h1>
38
+ <p style="color: #666; font-size: 0.85rem; margin-bottom: 24px;">Generated ${dateStr}${doctorName ? ` · For ${doctorName}` : ""}</p>
39
+ ${content
40
+ .replace(/### (.*)/g, '<h3 style="font-size: 1rem; text-transform: uppercase; letter-spacing: 0.05em; color: #555; margin-top: 1.5rem;">$1</h3>')
41
+ .replace(/## (.*)/g, '<h2 style="font-size: 1.2rem; margin-top: 2rem; color: #333;">$1</h2>')
42
+ .replace(/\n\n/g, "</p><p style='margin-bottom: 1rem;'>")
43
+ .replace(/\n/g, "<br>")}
44
+ </div>
45
+ `;
46
+
47
+ const filename = `${title.replace(/\s+/g, "_")}_${new Date().toISOString().slice(0, 10)}.pdf`;
48
+
49
+ await html2pdf()
50
+ .set({
51
+ margin: [10, 10, 10, 10],
52
+ filename,
53
+ image: { type: "jpeg", quality: 0.98 },
54
+ html2canvas: { scale: 2, useCORS: true },
55
+ jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
56
+ })
57
+ .from(container)
58
+ .save();
59
+ } catch (err) {
60
+ console.error("PDF export failed:", err);
61
+ } finally {
62
+ setIsExporting(false);
63
+ }
64
+ };
65
 
66
  return (
67
+ <div className="bg-surface-elevated border border-surface-overlay rounded-2xl overflow-hidden flex flex-col shadow-2xl group/report relative">
68
  <div className="absolute top-0 right-0 p-4 opacity-5 pointer-events-none group-hover:opacity-10 transition-opacity text-cta">
69
  <FileText className="w-24 h-24" />
70
  </div>
 
114
  <div className="px-6 py-5 bg-surface-subtle border-t border-surface-overlay flex flex-col gap-4">
115
  <div className="flex items-center gap-4">
116
  <button
117
+ onClick={handleExportPDF}
118
+ disabled={!isComplete || isExporting}
119
  className="flex-1 flex items-center justify-center gap-2 px-4 py-3 bg-surface-elevated border border-surface-overlay hover:border-text-muted disabled:opacity-30 rounded-xl text-[11px] font-bold text-secondary transition-all"
120
  >
121
+ {isExporting ? <Loader2 className="w-4 h-4 animate-spin" /> : <Download className="w-4 h-4" />}
122
+ {isExporting ? "Generating..." : "Export PDF"}
123
  </button>
124
  <button
125
  onClick={onSend}