From fb61bb4a63cd74369af25163f288b124838ab1f4 Mon Sep 17 00:00:00 2001 From: obitton Date: Mon, 16 Mar 2026 18:08:40 -0400 Subject: [PATCH] fix: resolve multi-page PDF crashes and Gemini API ingestion errors (#2781) * fix: resolve multi-page PDF crashes and Gemini API ingestion errors * fix type errors * refactor: address PR review feedback and prevent call stack recursion in regex payload scanner --------- Co-authored-by: Ofir Co-authored-by: Amruth Pillai --- package.json | 18 +- pnpm-lock.yaml | 709 +++++++++++++----- src/dialogs/resume/import.tsx | 27 +- src/integrations/orpc/router/ai.ts | 22 +- src/integrations/orpc/services/ai.server.ts | 14 + src/integrations/orpc/services/ai.ts | 267 +++++-- .../$resumeId/-sidebar/right/index.tsx | 29 +- src/utils/sanitize.ts | 10 + 8 files changed, 818 insertions(+), 278 deletions(-) create mode 100644 src/integrations/orpc/services/ai.server.ts diff --git a/package.json b/package.json index a20d70ed4..c18f2c6c7 100644 --- a/package.json +++ b/package.json @@ -62,12 +62,12 @@ "@tanstack/react-router-ssr-query": "^1.166.2", "@tanstack/react-start": "^1.166.2", "@tanstack/zod-adapter": "^1.166.2", - "@tiptap/extension-highlight": "^3.20.0", - "@tiptap/extension-table": "^3.20.0", - "@tiptap/extension-text-align": "^3.20.0", - "@tiptap/pm": "^3.20.0", - "@tiptap/react": "^3.20.0", - "@tiptap/starter-kit": "^3.20.0", + "@tiptap/extension-highlight": "^3.20.1", + "@tiptap/extension-table": "^3.20.1", + "@tiptap/extension-text-align": "^3.20.1", + "@tiptap/pm": "^3.20.1", + "@tiptap/react": "^3.20.1", + "@tiptap/starter-kit": "^3.20.1", "ai": "^6.0.116", "ai-sdk-ollama": "^3.8.0", "bcrypt": "^6.0.0", @@ -85,9 +85,12 @@ "immer": "^11.1.4", "input-otp": "^1.4.2", "js-cookie": "^3.0.5", + "jsonrepair": "^3.13.2", + "mammoth": "^1.11.0", "monaco-editor": "^0.55.1", "motion": "^12.35.0", "nodemailer": "^8.0.1", + "pdf-parse": "^2.4.5", "pg": "^8.20.0", "puppeteer-core": "^24.38.0", "qrcode.react": "^4.2.0", @@ -156,7 +159,8 @@ "onlyBuiltDependencies": [ "bcrypt", "esbuild", - "sharp" + "sharp", + "pdf-parse" ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc329142d..9c5f4133e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,23 +115,23 @@ importers: specifier: ^1.166.2 version: 1.166.2(@tanstack/react-router@1.166.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6) '@tiptap/extension-highlight': - specifier: ^3.20.0 - version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) + specifier: ^3.20.1 + version: 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) '@tiptap/extension-table': - specifier: ^3.20.0 - version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) + specifier: ^3.20.1 + version: 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) '@tiptap/extension-text-align': - specifier: ^3.20.0 - version: 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) + specifier: ^3.20.1 + version: 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) '@tiptap/pm': - specifier: ^3.20.0 - version: 3.20.0 + specifier: ^3.20.1 + version: 3.20.1 '@tiptap/react': - specifier: ^3.20.0 - version: 3.20.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^3.20.1 + version: 3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tiptap/starter-kit': - specifier: ^3.20.0 - version: 3.20.0 + specifier: ^3.20.1 + version: 3.20.1 ai: specifier: ^6.0.116 version: 6.0.116(zod@4.3.6) @@ -183,6 +183,12 @@ importers: js-cookie: specifier: ^3.0.5 version: 3.0.5 + jsonrepair: + specifier: ^3.13.2 + version: 3.13.2 + mammoth: + specifier: ^1.11.0 + version: 1.11.0 monaco-editor: specifier: ^0.55.1 version: 0.55.1 @@ -192,6 +198,9 @@ importers: nodemailer: specifier: ^8.0.1 version: 8.0.1 + pdf-parse: + specifier: ^2.4.5 + version: 2.4.5 pg: specifier: ^8.20.0 version: 8.20.0 @@ -1966,6 +1975,75 @@ packages: resolution: {integrity: sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==} engines: {node: '>=16'} + '@napi-rs/canvas-android-arm64@0.1.80': + resolution: {integrity: sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/canvas-darwin-arm64@0.1.80': + resolution: {integrity: sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/canvas-darwin-x64@0.1.80': + resolution: {integrity: sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.80': + resolution: {integrity: sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/canvas-linux-arm64-gnu@0.1.80': + resolution: {integrity: sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-arm64-musl@0.1.80': + resolution: {integrity: sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.80': + resolution: {integrity: sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-x64-gnu@0.1.80': + resolution: {integrity: sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-x64-musl@0.1.80': + resolution: {integrity: sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/canvas-win32-x64-msvc@0.1.80': + resolution: {integrity: sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/canvas@0.1.80': + resolution: {integrity: sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==} + engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} @@ -3863,170 +3941,170 @@ packages: '@tediousjs/connection-string@0.5.0': resolution: {integrity: sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==} - '@tiptap/core@3.20.0': - resolution: {integrity: sha512-aC9aROgia/SpJqhsXFiX9TsligL8d+oeoI8W3u00WI45s0VfsqjgeKQLDLF7Tu7hC+7F02teC84SAHuup003VQ==} + '@tiptap/core@3.20.1': + resolution: {integrity: sha512-SwkPEWIfaDEZjC8SEIi4kZjqIYUbRgLUHUuQezo5GbphUNC8kM1pi3C3EtoOPtxXrEbY6e4pWEzW54Pcrd+rVA==} peerDependencies: - '@tiptap/pm': ^3.20.0 + '@tiptap/pm': ^3.20.1 - '@tiptap/extension-blockquote@3.20.0': - resolution: {integrity: sha512-LQzn6aGtL4WXz2+rYshl/7/VnP2qJTpD7fWL96GXAzhqviPEY1bJES7poqJb3MU/gzl8VJUVzVzU1VoVfUKlbA==} + '@tiptap/extension-blockquote@3.20.1': + resolution: {integrity: sha512-WzNXk/63PQI2fav4Ta6P0GmYRyu8Gap1pV3VUqaVK829iJ6Zt1T21xayATHEHWMK27VT1GLPJkx9Ycr2jfDyQw==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-bold@3.20.0': - resolution: {integrity: sha512-sQklEWiyf58yDjiHtm5vmkVjfIc/cBuSusmCsQ0q9vGYnEF1iOHKhGpvnCeEXNeqF3fiJQRlquzt/6ymle3Iwg==} + '@tiptap/extension-bold@3.20.1': + resolution: {integrity: sha512-fz++Qv6Rk/Hov0IYG/r7TJ1Y4zWkuGONe0UN5g0KY32NIMg3HeOHicbi4xsNWTm9uAOl3eawWDkezEMrleObMw==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-bubble-menu@3.20.0': - resolution: {integrity: sha512-MDosUfs8Tj+nwg8RC+wTMWGkLJORXmbR6YZgbiX4hrc7G90Gopdd6kj6ht5/T8t7dLLaX7N0+DEHdUEPGED7dw==} + '@tiptap/extension-bubble-menu@3.20.1': + resolution: {integrity: sha512-XaPvO6aCoWdFnCBus0s88lnj17NR/OopV79i8Qhgz3WMR0vrsL5zsd45l0lZuu9pSvm5VW47SoxakkJiZC1suw==} peerDependencies: - '@tiptap/core': ^3.20.0 - '@tiptap/pm': ^3.20.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 - '@tiptap/extension-bullet-list@3.20.0': - resolution: {integrity: sha512-OcKMeopBbqWzhSi6o8nNz0aayogg1sfOAhto3NxJu3Ya32dwBFqmHXSYM6uW4jOphNvVPyjiq9aNRh3qTdd1dw==} + '@tiptap/extension-bullet-list@3.20.1': + resolution: {integrity: sha512-mbrlvOZo5OF3vLhp+3fk9KuL/6J/wsN0QxF6ZFRAHzQ9NkJdtdfARcBeBnkWXGN8inB6YxbTGY1/E4lmBkOpOw==} peerDependencies: - '@tiptap/extension-list': ^3.20.0 + '@tiptap/extension-list': ^3.20.1 - '@tiptap/extension-code-block@3.20.0': - resolution: {integrity: sha512-lBbmNek14aCjrHcBcq3PRqWfNLvC6bcRa2Osc6e/LtmXlcpype4f6n+Yx+WZ+f2uUh0UmDRCz7BEyUETEsDmlQ==} + '@tiptap/extension-code-block@3.20.1': + resolution: {integrity: sha512-vKejwBq+Nlj4Ybd3qOyDxIQKzYymdNH+8eXkKwGShk2nfLJIxq69DCyGvmuHgipIO1qcYPJ149UNpGN+YGcdmA==} peerDependencies: - '@tiptap/core': ^3.20.0 - '@tiptap/pm': ^3.20.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 - '@tiptap/extension-code@3.20.0': - resolution: {integrity: sha512-TYDWFeSQ9umiyrqsT6VecbuhL8XIHkUhO+gEk0sVvH67ZLwjFDhAIIgWIr1/dbIGPcvMZM19E7xUUhAdIaXaOQ==} + '@tiptap/extension-code@3.20.1': + resolution: {integrity: sha512-509DHINIA/Gg+eTG7TEkfsS8RUiPLH5xZNyLRT0A1oaoaJmECKfrV6aAm05IdfTyqDqz6LW5pbnX6DdUC4keug==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-document@3.20.0': - resolution: {integrity: sha512-oJfLIG3vAtZo/wg29WiBcyWt22KUgddpP8wqtCE+kY5Dw8znLR9ehNmVWlSWJA5OJUMO0ntAHx4bBT+I2MBd5w==} + '@tiptap/extension-document@3.20.1': + resolution: {integrity: sha512-9vrqdGmRV7bQCSY3NLgu7UhIwgOCDp4sKqMNsoNRX0aZ021QQMTvBQDPkiRkCf7MNsnWrNNnr52PVnULEn3vFQ==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-dropcursor@3.20.0': - resolution: {integrity: sha512-d+cxplRlktVgZPwatnc34IArlppM0IFKS1J5wLk+ba1jidizsbMVh45tP/BTK2flhyfRqcNoB5R0TArhUpbkNQ==} + '@tiptap/extension-dropcursor@3.20.1': + resolution: {integrity: sha512-K18L9FX4znn+ViPSIbTLOGcIaXMx/gLNwAPE8wPLwswbHhQqdiY1zzdBw6drgOc1Hicvebo2dIoUlSXOZsOEcw==} peerDependencies: - '@tiptap/extensions': ^3.20.0 + '@tiptap/extensions': ^3.20.1 - '@tiptap/extension-floating-menu@3.20.0': - resolution: {integrity: sha512-rYs4Bv5pVjqZ/2vvR6oe7ammZapkAwN51As/WDbemvYDjfOGRqK58qGauUjYZiDzPOEIzI2mxGwsZ4eJhPW4Ig==} + '@tiptap/extension-floating-menu@3.20.1': + resolution: {integrity: sha512-BeDC6nfOesIMn5pFuUnkEjOxGv80sOJ8uk1mdt9/3Fkvra8cB9NIYYCVtd6PU8oQFmJ8vFqPrRkUWrG5tbqnOg==} peerDependencies: '@floating-ui/dom': ^1.0.0 - '@tiptap/core': ^3.20.0 - '@tiptap/pm': ^3.20.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 - '@tiptap/extension-gapcursor@3.20.0': - resolution: {integrity: sha512-P/LasfvG9/qFq43ZAlNbAnPnXC+/RJf49buTrhtFvI9Zg0+Lbpjx1oh6oMHB19T88Y28KtrckfFZ8aTSUWDq6w==} + '@tiptap/extension-gapcursor@3.20.1': + resolution: {integrity: sha512-kZOtttV6Ai8VUAgEng3h4WKFbtdSNJ6ps7r0cRPY+FctWhVmgNb/JJwwyC+vSilR7nRENAhrA/Cv/RxVlvLw+g==} peerDependencies: - '@tiptap/extensions': ^3.20.0 + '@tiptap/extensions': ^3.20.1 - '@tiptap/extension-hard-break@3.20.0': - resolution: {integrity: sha512-rqvhMOw4f+XQmEthncbvDjgLH6fz8L9splnKZC7OeS0eX8b0qd7+xI1u5kyxF3KA2Z0BnigES++jjWuecqV6mA==} + '@tiptap/extension-hard-break@3.20.1': + resolution: {integrity: sha512-9sKpmg/IIdlLXimYWUZ3PplIRcehv4Oc7V1miTqlnAthMzjMqigDkjjgte4JZV67RdnDJTQkRw8TklCAU28Emg==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-heading@3.20.0': - resolution: {integrity: sha512-JgJhurnCe3eN6a0lEsNQM/46R1bcwzwWWZEFDSb1P9dR8+t1/5v7cMZWsSInpD7R4/74iJn0+M5hcXLwCmBmYA==} + '@tiptap/extension-heading@3.20.1': + resolution: {integrity: sha512-unudyfQP6FxnyWinxvPqe/51DG91J6AaJm666RnAubgYMCgym+33kBftx4j4A6qf+ddWYbD00thMNKOnVLjAEQ==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-highlight@3.20.0': - resolution: {integrity: sha512-ANL1wFz0s1ScNR4uBfO0s6Sz+qqGp2u6ynrCVk6TCT3d10CQ+gD1gSDTrVRC3CtlMKtHHH4fYrFAq284+J0gKA==} + '@tiptap/extension-highlight@3.20.1': + resolution: {integrity: sha512-Ufv1eNsQBR7NNSxQIvHaI9Bm3/MS8GW9uCXR/20WJ2r0GhDA6vwnS5MHAhP5JUXK6KOsd0Rvz2cfvYB7f3okbQ==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-horizontal-rule@3.20.0': - resolution: {integrity: sha512-6uvcutFMv+9wPZgptDkbRDjAm3YVxlibmkhWD5GuaWwS9L/yUtobpI3GycujRSUZ8D3q6Q9J7LqpmQtQRTalWA==} + '@tiptap/extension-horizontal-rule@3.20.1': + resolution: {integrity: sha512-rjFKFXNntdl0jay8oIGFvvykHlpyQTLmrH3Ag2fj3i8yh6MVvqhtaDomYQbw5sxECd5hBkL+T4n2d2DRuVw/QQ==} peerDependencies: - '@tiptap/core': ^3.20.0 - '@tiptap/pm': ^3.20.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 - '@tiptap/extension-italic@3.20.0': - resolution: {integrity: sha512-/DhnKQF8yN8RxtuL8abZ28wd5281EaGoE2Oha35zXSOF1vNYnbyt8Ymkv/7u1BcWEWTvRPgaju0YCGXisPRLYw==} + '@tiptap/extension-italic@3.20.1': + resolution: {integrity: sha512-ZYRX13Kt8tR8JOzSXirH3pRpi8x30o7LHxZY58uXBdUvr3tFzOkh03qbN523+diidSVeHP/aMd/+IrplHRkQug==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-link@3.20.0': - resolution: {integrity: sha512-qI/5A+R0ZWBxo/8HxSn1uOyr7odr3xHBZ/gzOR1GUJaZqjlJxkWFX0RtXMbLKEGEvT25o345cF7b0wFznEh8qA==} + '@tiptap/extension-link@3.20.1': + resolution: {integrity: sha512-oYTTIgsQMqpkSnJAuAc+UtIKMuI4lv9e1y4LfI1iYm6NkEUHhONppU59smhxHLzb3Ww7YpDffbp5IgDTAiJztA==} peerDependencies: - '@tiptap/core': ^3.20.0 - '@tiptap/pm': ^3.20.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 - '@tiptap/extension-list-item@3.20.0': - resolution: {integrity: sha512-qEtjaaGPuqaFB4VpLrGDoIe9RHnckxPfu6d3rc22ap6TAHCDyRv05CEyJogqccnFceG/v5WN4znUBER8RWnWHA==} + '@tiptap/extension-list-item@3.20.1': + resolution: {integrity: sha512-tzgnyTW82lYJkUnadYbatwkI9dLz/OWRSWuFpQPRje/ItmFMWuQ9c9NDD8qLbXPdEYnvrgSAA+ipCD/1G0qA0Q==} peerDependencies: - '@tiptap/extension-list': ^3.20.0 + '@tiptap/extension-list': ^3.20.1 - '@tiptap/extension-list-keymap@3.20.0': - resolution: {integrity: sha512-Z4GvKy04Ms4cLFN+CY6wXswd36xYsT2p/YL0V89LYFMZTerOeTjFYlndzn6svqL8NV1PRT5Diw4WTTxJSmcJPA==} + '@tiptap/extension-list-keymap@3.20.1': + resolution: {integrity: sha512-Dr0xsQKx0XPOgDg7xqoWwfv7FFwZ3WeF3eOjqh3rDXlNHMj1v+UW5cj1HLphrsAZHTrVTn2C+VWPJkMZrSbpvQ==} peerDependencies: - '@tiptap/extension-list': ^3.20.0 + '@tiptap/extension-list': ^3.20.1 - '@tiptap/extension-list@3.20.0': - resolution: {integrity: sha512-+V0/gsVWAv+7vcY0MAe6D52LYTIicMSHw00wz3ISZgprSb2yQhJ4+4gurOnUrQ4Du3AnRQvxPROaofwxIQ66WQ==} + '@tiptap/extension-list@3.20.1': + resolution: {integrity: sha512-euBRAn0mkV7R2VEE+AuOt3R0j9RHEMFXamPFmtvTo8IInxDClusrm6mJoDjS8gCGAXsQCRiAe1SCQBPgGbOOwg==} peerDependencies: - '@tiptap/core': ^3.20.0 - '@tiptap/pm': ^3.20.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 - '@tiptap/extension-ordered-list@3.20.0': - resolution: {integrity: sha512-jVKnJvrizLk7etwBMfyoj6H2GE4M+PD4k7Bwp6Bh1ohBWtfIA1TlngdS842Mx5i1VB2e3UWIwr8ZH46gl6cwMA==} + '@tiptap/extension-ordered-list@3.20.1': + resolution: {integrity: sha512-Y+3Ad7OwAdagqdYwCnbqf7/to5ypD4NnUNHA0TXRCs7cAHRA8AdgPoIcGFpaaSpV86oosNU3yfeJouYeroffog==} peerDependencies: - '@tiptap/extension-list': ^3.20.0 + '@tiptap/extension-list': ^3.20.1 - '@tiptap/extension-paragraph@3.20.0': - resolution: {integrity: sha512-mM99zK4+RnEXIMCv6akfNATAs0Iija6FgyFA9J9NZ6N4o8y9QiNLLa6HjLpAC+W+VoCgQIekyoF/Q9ftxmAYDQ==} + '@tiptap/extension-paragraph@3.20.1': + resolution: {integrity: sha512-QFrAtXNyv7JSnomMQc1nx5AnG9mMznfbYJAbdOQYVdbLtAzTfiTuNPNbQrufy5ZGtGaHxDCoaygu2QEfzaKG+Q==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-strike@3.20.0': - resolution: {integrity: sha512-0vcTZRRAiDfon3VM1mHBr9EFmTkkUXMhm0Xtdtn0bGe+sIqufyi+hUYTEw93EQOD9XNsPkrud6jzQNYpX2H3AQ==} + '@tiptap/extension-strike@3.20.1': + resolution: {integrity: sha512-EYgyma10lpsY+rwbVQL9u+gA7hBlKLSMFH7Zgd37FSxukOjr+HE8iKPQQ+SwbGejyDsPlLT8Z5Jnuxo5Ng90Pg==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-table@3.20.0': - resolution: {integrity: sha512-vaaMtQ2KnSSr8WVwgWf7NYNzPwrHx/6T0ekA5CxV8qNUEpXIaLXa5+tE7tJHWEdNR2KY3gUJ46D3lfOkxyFrBQ==} + '@tiptap/extension-table@3.20.1': + resolution: {integrity: sha512-y2o1De3P/FH80nst1D9pYSwtRvxF87WkNAYGurqCkZ6SjwFq1Ifeh9eY+o79R6SCwcfpvQRytCYd+Yw9o5XAtA==} peerDependencies: - '@tiptap/core': ^3.20.0 - '@tiptap/pm': ^3.20.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 - '@tiptap/extension-text-align@3.20.0': - resolution: {integrity: sha512-4s0r+bovtH6yeGDUD+Ui8j5WOV5koB5P6AuzOMqoLwaFGRSkKf64ly6DXjjmjIgnYCLZN/XO6llaQKVVdvad2g==} + '@tiptap/extension-text-align@3.20.1': + resolution: {integrity: sha512-9816EFiTO7BvACGomYNI0pZIu5EOBVHrgqO33+sFjseQCvq+5FD6OPNLdHSkPzAmRrE0LUBvFZ93sjO4N+kJPg==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-text@3.20.0': - resolution: {integrity: sha512-tf8bE8tSaOEWabCzPm71xwiUhyMFKqY9jkP5af3Kr1/F45jzZFIQAYZooHI/+zCHRrgJ99MQHKHe1ZNvODrKHQ==} + '@tiptap/extension-text@3.20.1': + resolution: {integrity: sha512-7PlIbYW8UenV6NPOXHmv8IcmPGlGx6HFq66RmkJAOJRPXPkTLAiX0N8rQtzUJ6jDEHqoJpaHFEHJw0xzW1yF+A==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extension-underline@3.20.0': - resolution: {integrity: sha512-LzNXuy2jwR/y+ymoUqC72TiGzbOCjioIjsDu0MNYpHuHqTWPK5aV9Mh0nbZcYFy/7fPlV1q0W139EbJeYBZEAQ==} + '@tiptap/extension-underline@3.20.1': + resolution: {integrity: sha512-fmHvDKzwCgnZUwRreq8tYkb1YyEwgzZ6QQkAQ0CsCRtvRMqzerr3Duz0Als4i8voZTuGDEL3VR6nAJbLAb/wPg==} peerDependencies: - '@tiptap/core': ^3.20.0 + '@tiptap/core': ^3.20.1 - '@tiptap/extensions@3.20.0': - resolution: {integrity: sha512-HIsXX942w3nbxEQBlMAAR/aa6qiMBEP7CsSMxaxmTIVAmW35p6yUASw6GdV1u0o3lCZjXq2OSRMTskzIqi5uLg==} + '@tiptap/extensions@3.20.1': + resolution: {integrity: sha512-JRc/v+OBH0qLTdvQ7HvHWTxGJH73QOf1MC0R8NhOX2QnAbg2mPFv1h+FjGa2gfLGuCXBdWQomjekWkUKbC4e5A==} peerDependencies: - '@tiptap/core': ^3.20.0 - '@tiptap/pm': ^3.20.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 - '@tiptap/pm@3.20.0': - resolution: {integrity: sha512-jn+2KnQZn+b+VXr8EFOJKsnjVNaA4diAEr6FOazupMt8W8ro1hfpYtZ25JL87Kao/WbMze55sd8M8BDXLUKu1A==} + '@tiptap/pm@3.20.1': + resolution: {integrity: sha512-6kCiGLvpES4AxcEuOhb7HR7/xIeJWMjZlb6J7e8zpiIh5BoQc7NoRdctsnmFEjZvC19bIasccshHQ7H2zchWqw==} - '@tiptap/react@3.20.0': - resolution: {integrity: sha512-jFLNzkmn18zqefJwPje0PPd9VhZ7Oy28YHiSvSc7YpBnQIbuN/HIxZ2lrOsKyEHta0WjRZjfU5X1pGxlbcGwOA==} + '@tiptap/react@3.20.1': + resolution: {integrity: sha512-UH1NpVpCaZBGB3Yr5N6aTS+rsCMDl9wHfrt/w+6+Gz4KHFZ2OILA82hELxZzhNc1Lmjz8vgCArKcsYql9gbzJA==} peerDependencies: - '@tiptap/core': ^3.20.0 - '@tiptap/pm': ^3.20.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tiptap/starter-kit@3.20.0': - resolution: {integrity: sha512-W4+1re35pDNY/7rpXVg+OKo/Fa4Gfrn08Bq3E3fzlJw6gjE3tYU8dY9x9vC2rK9pd9NOp7Af11qCFDaWpohXkw==} + '@tiptap/starter-kit@3.20.1': + resolution: {integrity: sha512-opqWxL/4OTEiqmVC0wsU4o3JhAf6LycJ2G/gRIZVAIFLljI9uHfpPMTFGxZ5w9IVVJaP5PJysfwW/635kKqkrw==} '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -4140,6 +4218,10 @@ packages: peerDependencies: vite: ^8.0.0-beta.16 + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + engines: {node: '>=10.0.0'} + abbrev@4.0.0: resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} engines: {node: ^20.17.0 || >=22.9.0} @@ -4204,6 +4286,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -4414,6 +4499,9 @@ packages: bl@6.1.6: resolution: {integrity: sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==} + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} @@ -4635,6 +4723,9 @@ packages: core-js-compat@3.48.0: resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} @@ -4799,6 +4890,9 @@ packages: resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} engines: {node: '>=0.3.1'} + dingbat-to-unicode@1.0.1: + resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -4954,6 +5048,9 @@ packages: drizzle-orm: '>=0.36.0' zod: ^3.25.0 || ^4.0.0 + duck@0.1.12: + resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -5423,6 +5520,9 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immer@11.1.4: resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} @@ -5617,6 +5717,9 @@ packages: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -5712,6 +5815,9 @@ packages: resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} engines: {node: '>=12', npm: '>=6'} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + jwa@2.0.1: resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} @@ -5734,6 +5840,9 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lightningcss-android-arm64@1.31.1: resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} engines: {node: '>= 12.0.0'} @@ -5858,6 +5967,9 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + lop@0.4.2: + resolution: {integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==} + lru-cache@11.2.6: resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} @@ -5883,6 +5995,11 @@ packages: resolution: {integrity: sha512-vM2sG+wbVeVGYcCm16mM3d5fuem9oC28n436HjsGO3LcxoTI8LNVa4rwZDn3f76+cWyT4GGJDxjTYU1I2nr6zw==} engines: {node: ^20.17.0 || >=22.9.0} + mammoth@1.11.0: + resolution: {integrity: sha512-BcEqqY/BOwIcI1iR5tqyVlqc3KIaMRa4egSoK83YAVrBf6+yqdAAbtUcFDCWX8Zef8/fgNZ6rl4VUv+vVX8ddQ==} + engines: {node: '>=12.0.0'} + hasBin: true + markdown-it@14.1.1: resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} hasBin: true @@ -6178,6 +6295,9 @@ packages: openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + option@0.2.4: + resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==} + ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -6215,6 +6335,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -6236,6 +6359,10 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -6257,6 +6384,15 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pdf-parse@2.4.5: + resolution: {integrity: sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==} + engines: {node: '>=20.16.0 <21 || >=22.3.0'} + hasBin: true + + pdfjs-dist@5.4.296: + resolution: {integrity: sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==} + engines: {node: '>=20.16.0 || >=22.3.0'} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -6383,6 +6519,9 @@ packages: resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} engines: {node: ^20.17.0 || >=22.9.0} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -6603,6 +6742,9 @@ packages: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -6729,6 +6871,9 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -6795,6 +6940,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -6890,6 +7038,9 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} @@ -6948,6 +7099,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -7137,6 +7291,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + underscore@1.13.8: + resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} @@ -7539,6 +7696,10 @@ packages: resolution: {integrity: sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==} engines: {node: '>=20.0'} + xmlbuilder@10.1.1: + resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==} + engines: {node: '>=4.0'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -9582,6 +9743,49 @@ snapshots: chevrotain: 10.5.0 lilconfig: 2.1.0 + '@napi-rs/canvas-android-arm64@0.1.80': + optional: true + + '@napi-rs/canvas-darwin-arm64@0.1.80': + optional: true + + '@napi-rs/canvas-darwin-x64@0.1.80': + optional: true + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.80': + optional: true + + '@napi-rs/canvas-linux-arm64-gnu@0.1.80': + optional: true + + '@napi-rs/canvas-linux-arm64-musl@0.1.80': + optional: true + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.80': + optional: true + + '@napi-rs/canvas-linux-x64-gnu@0.1.80': + optional: true + + '@napi-rs/canvas-linux-x64-musl@0.1.80': + optional: true + + '@napi-rs/canvas-win32-x64-msvc@0.1.80': + optional: true + + '@napi-rs/canvas@0.1.80': + optionalDependencies: + '@napi-rs/canvas-android-arm64': 0.1.80 + '@napi-rs/canvas-darwin-arm64': 0.1.80 + '@napi-rs/canvas-darwin-x64': 0.1.80 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.80 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.80 + '@napi-rs/canvas-linux-arm64-musl': 0.1.80 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.80 + '@napi-rs/canvas-linux-x64-gnu': 0.1.80 + '@napi-rs/canvas-linux-x64-musl': 0.1.80 + '@napi-rs/canvas-win32-x64-msvc': 0.1.80 + '@napi-rs/wasm-runtime@1.1.1': dependencies: '@emnapi/core': 1.8.1 @@ -11588,132 +11792,132 @@ snapshots: '@tediousjs/connection-string@0.5.0': {} - '@tiptap/core@3.20.0(@tiptap/pm@3.20.0)': + '@tiptap/core@3.20.1(@tiptap/pm@3.20.1)': dependencies: - '@tiptap/pm': 3.20.0 + '@tiptap/pm': 3.20.1 - '@tiptap/extension-blockquote@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-blockquote@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-bold@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-bold@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-bubble-menu@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)': + '@tiptap/extension-bubble-menu@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': dependencies: '@floating-ui/dom': 1.7.6 - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 optional: true - '@tiptap/extension-bullet-list@3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))': + '@tiptap/extension-bullet-list@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) - '@tiptap/extension-code-block@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)': + '@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 - '@tiptap/extension-code@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-code@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-document@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-document@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-dropcursor@3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))': + '@tiptap/extension-dropcursor@3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/extensions': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) + '@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) - '@tiptap/extension-floating-menu@3.20.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)': + '@tiptap/extension-floating-menu@3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': dependencies: '@floating-ui/dom': 1.7.6 - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 optional: true - '@tiptap/extension-gapcursor@3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))': + '@tiptap/extension-gapcursor@3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/extensions': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) + '@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) - '@tiptap/extension-hard-break@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-hard-break@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-heading@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-heading@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-highlight@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-highlight@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-horizontal-rule@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)': + '@tiptap/extension-horizontal-rule@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 - '@tiptap/extension-italic@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-italic@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-link@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)': + '@tiptap/extension-link@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 linkifyjs: 4.3.2 - '@tiptap/extension-list-item@3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))': + '@tiptap/extension-list-item@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) - '@tiptap/extension-list-keymap@3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))': + '@tiptap/extension-list-keymap@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) - '@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)': + '@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 - '@tiptap/extension-ordered-list@3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))': + '@tiptap/extension-ordered-list@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) - '@tiptap/extension-paragraph@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-paragraph@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-strike@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-strike@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-table@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)': + '@tiptap/extension-table@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 - '@tiptap/extension-text-align@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-text-align@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-text@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-text@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extension-underline@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))': + '@tiptap/extension-underline@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) - '@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)': + '@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 - '@tiptap/pm@3.20.0': + '@tiptap/pm@3.20.1': dependencies: prosemirror-changeset: 2.4.0 prosemirror-collab: 1.3.1 @@ -11734,10 +11938,10 @@ snapshots: prosemirror-transform: 1.11.0 prosemirror-view: 1.41.6 - '@tiptap/react@3.20.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tiptap/react@3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) '@types/use-sync-external-store': 0.0.6 @@ -11746,37 +11950,37 @@ snapshots: react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: - '@tiptap/extension-bubble-menu': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) - '@tiptap/extension-floating-menu': 3.20.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) + '@tiptap/extension-bubble-menu': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-floating-menu': 3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) transitivePeerDependencies: - '@floating-ui/dom' - '@tiptap/starter-kit@3.20.0': + '@tiptap/starter-kit@3.20.1': dependencies: - '@tiptap/core': 3.20.0(@tiptap/pm@3.20.0) - '@tiptap/extension-blockquote': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-bold': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-bullet-list': 3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)) - '@tiptap/extension-code': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-code-block': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) - '@tiptap/extension-document': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-dropcursor': 3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)) - '@tiptap/extension-gapcursor': 3.20.0(@tiptap/extensions@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)) - '@tiptap/extension-hard-break': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-heading': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-horizontal-rule': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) - '@tiptap/extension-italic': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-link': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) - '@tiptap/extension-list': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) - '@tiptap/extension-list-item': 3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)) - '@tiptap/extension-list-keymap': 3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)) - '@tiptap/extension-ordered-list': 3.20.0(@tiptap/extension-list@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)) - '@tiptap/extension-paragraph': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-strike': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-text': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extension-underline': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)) - '@tiptap/extensions': 3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0) - '@tiptap/pm': 3.20.0 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/extension-blockquote': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-bold': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-bullet-list': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-code': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-code-block': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-document': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-dropcursor': 3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-gapcursor': 3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-hard-break': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-heading': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-horizontal-rule': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-italic': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-link': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-list-item': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-list-keymap': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-ordered-list': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-paragraph': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-strike': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-text': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-underline': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 '@tootallnate/quickjs-emscripten@0.23.0': {} @@ -11917,6 +12121,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@xmldom/xmldom@0.8.11': {} + abbrev@4.0.0: {} abort-controller@3.0.0: @@ -11976,6 +12182,10 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -12161,6 +12371,8 @@ snapshots: inherits: 2.0.4 readable-stream: 4.7.0 + bluebird@3.4.7: {} + body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -12424,6 +12636,8 @@ snapshots: dependencies: browserslist: 4.28.1 + core-util-is@1.0.3: {} + cors@2.8.6: dependencies: object-assign: 4.1.1 @@ -12557,6 +12771,8 @@ snapshots: diff@8.0.3: {} + dingbat-to-unicode@1.0.1: {} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -12613,6 +12829,10 @@ snapshots: drizzle-orm: 1.0.0-beta.16-ea816b6(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.18.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6) zod: 4.3.6 + duck@0.1.12: + dependencies: + underscore: 1.13.8 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -13199,6 +13419,8 @@ snapshots: ieee754@1.2.1: {} + immediate@3.0.6: {} + immer@11.1.4: {} import-fresh@3.3.1: @@ -13371,6 +13593,8 @@ snapshots: dependencies: is-inside-container: 1.0.0 + isarray@1.0.0: {} + isarray@2.0.5: {} isbot@5.1.35: {} @@ -13453,6 +13677,13 @@ snapshots: ms: 2.1.3 semver: 7.7.4 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + jwa@2.0.1: dependencies: buffer-equal-constant-time: 1.0.1 @@ -13485,6 +13716,10 @@ snapshots: leven@3.1.0: {} + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lightningcss-android-arm64@1.31.1: optional: true @@ -13571,6 +13806,12 @@ snapshots: long@5.3.2: {} + lop@0.4.2: + dependencies: + duck: 0.1.12 + option: 0.2.4 + underscore: 1.13.8 + lru-cache@11.2.6: {} lru-cache@5.1.1: @@ -13605,6 +13846,19 @@ snapshots: transitivePeerDependencies: - supports-color + mammoth@1.11.0: + dependencies: + '@xmldom/xmldom': 0.8.11 + argparse: 1.0.10 + base64-js: 1.5.1 + bluebird: 3.4.7 + dingbat-to-unicode: 1.0.1 + jszip: 3.10.1 + lop: 0.4.2 + path-is-absolute: 1.0.1 + underscore: 1.13.8 + xmlbuilder: 10.1.1 + markdown-it@14.1.1: dependencies: argparse: 2.0.1 @@ -13897,6 +14151,8 @@ snapshots: openapi-types@12.1.3: {} + option@0.2.4: {} + ora@5.4.1: dependencies: bl: 4.1.0 @@ -14008,6 +14264,8 @@ snapshots: package-json-from-dist@1.0.1: {} + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -14034,6 +14292,8 @@ snapshots: parseurl@1.3.3: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -14049,6 +14309,15 @@ snapshots: pathe@2.0.3: {} + pdf-parse@2.4.5: + dependencies: + '@napi-rs/canvas': 0.1.80 + pdfjs-dist: 5.4.296 + + pdfjs-dist@5.4.296: + optionalDependencies: + '@napi-rs/canvas': 0.1.80 + pend@1.2.0: {} perfect-debounce@1.0.0: {} @@ -14156,6 +14425,8 @@ snapshots: proc-log@6.1.0: {} + process-nextick-args@2.0.1: {} + process@0.11.10: {} progress@2.0.3: {} @@ -14472,6 +14743,16 @@ snapshots: react@19.2.4: {} + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -14627,6 +14908,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safe-push-apply@1.0.0: @@ -14707,6 +14990,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} sharp@0.34.5: @@ -14825,6 +15110,8 @@ snapshots: split2@4.2.0: {} + sprintf-js@1.0.3: {} + sprintf-js@1.1.3: {} sqlstring@2.3.3: {} @@ -14902,6 +15189,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -15152,6 +15443,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + underscore@1.13.8: {} + undici-types@7.18.2: {} undici@7.22.0: {} @@ -15500,6 +15793,8 @@ snapshots: '@oozcitak/util': 10.0.0 js-yaml: 4.1.1 + xmlbuilder@10.1.1: {} + xtend@4.0.2: {} y18n@5.0.8: {} diff --git a/src/dialogs/resume/import.tsx b/src/dialogs/resume/import.tsx index 0be0a16ab..c8a81fc56 100644 --- a/src/dialogs/resume/import.tsx +++ b/src/dialogs/resume/import.tsx @@ -3,6 +3,7 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { DownloadSimpleIcon, FileIcon, UploadSimpleIcon } from "@phosphor-icons/react"; import { useMutation } from "@tanstack/react-query"; +import { useNavigate } from "@tanstack/react-router"; import { useEffect, useRef, useState } from "react"; import { useForm, useWatch } from "react-hook-form"; import { toast } from "sonner"; @@ -63,7 +64,21 @@ const formSchema = z.discriminatedUnion("type", [ type FormValues = z.infer; +function fileToBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result as string; + // remove data URL prefix (e.g., "data:application/pdf;base64," or "data:application/vnd...;base64,") + resolve(result.split(",")[1]); + }; + reader.onerror = reject; + reader.readAsDataURL(file); + }); +} + export function ImportResumeDialog(_: DialogProps<"resume.import">) { + const navigate = useNavigate(); const { enabled: isAIEnabled, provider, model, apiKey, baseURL } = useAIStore(); const closeDialog = useDialogStore((state) => state.closeDialog); @@ -135,8 +150,8 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { if (!isAIEnabled) throw new Error(t`This feature requires AI Integration to be enabled. Please enable it in the settings.`); - const arrayBuffer = await values.file.arrayBuffer(); - const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); + // const arrayBuffer = await values.file.arrayBuffer(); + const base64 = await fileToBase64(values.file); data = await client.ai.parsePdf({ provider, @@ -151,8 +166,9 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { if (!isAIEnabled) throw new Error(t`This feature requires AI Integration to be enabled. Please enable it in the settings.`); - const arrayBuffer = await values.file.arrayBuffer(); - const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); + // const arrayBuffer = await values.file.arrayBuffer(); + const base64 = await fileToBase64(values.file); + const mediaType = values.file.type === "application/msword" ? ("application/msword" as const) @@ -170,9 +186,10 @@ export function ImportResumeDialog(_: DialogProps<"resume.import">) { if (!data) throw new Error("No data was returned from the AI provider."); - await importResume({ data }); + const id = await importResume({ data }); toast.success(t`Your resume has been imported successfully.`, { id: toastId, description: null }); closeDialog(); + navigate({ to: `/builder/$resumeId`, params: { resumeId: id } }); } catch (error: unknown) { if (error instanceof Error) { toast.error(error.message, { id: toastId, description: null }); diff --git a/src/integrations/orpc/router/ai.ts b/src/integrations/orpc/router/ai.ts index ad055c7e3..baf3791df 100644 --- a/src/integrations/orpc/router/ai.ts +++ b/src/integrations/orpc/router/ai.ts @@ -2,10 +2,10 @@ import { ORPCError } from "@orpc/client"; import { type } from "@orpc/server"; import { AISDKError, type UIMessage } from "ai"; import { OllamaError } from "ai-sdk-ollama"; -import z, { ZodError } from "zod"; +import z, { flattenError, ZodError } from "zod"; import type { ResumeData } from "@/schema/resume/data"; import { protectedProcedure } from "../context"; -import { aiCredentialsSchema, aiProviderSchema, aiService, fileInputSchema, formatZodError } from "../services/ai"; +import { aiCredentialsSchema, aiProviderSchema, aiService, fileInputSchema } from "../services/ai"; type AIProvider = z.infer; @@ -69,6 +69,10 @@ export const aiRouter = { message: "The AI provider returned an error or is unreachable.", status: 502, }, + BAD_REQUEST: { + message: "The AI returned an improperly formatted structure.", + status: 400, + }, }) .handler(async ({ input }): Promise => { try { @@ -79,7 +83,10 @@ export const aiRouter = { } if (error instanceof ZodError) { - throw new Error(formatZodError(error)); + throw new ORPCError("BAD_REQUEST", { + message: "Invalid resume data structure", + cause: flattenError(error), + }); } throw error; } @@ -111,6 +118,10 @@ export const aiRouter = { message: "The AI provider returned an error or is unreachable.", status: 502, }, + BAD_REQUEST: { + message: "The AI returned an improperly formatted structure.", + status: 400, + }, }) .handler(async ({ input }) => { try { @@ -121,7 +132,10 @@ export const aiRouter = { } if (error instanceof ZodError) { - throw new Error(formatZodError(error)); + throw new ORPCError("BAD_REQUEST", { + message: "Invalid resume data structure", + cause: flattenError(error), + }); } throw error; diff --git a/src/integrations/orpc/services/ai.server.ts b/src/integrations/orpc/services/ai.server.ts new file mode 100644 index 000000000..131787f24 --- /dev/null +++ b/src/integrations/orpc/services/ai.server.ts @@ -0,0 +1,14 @@ +import { extractRawText } from "mammoth"; +import { PDFParse } from "pdf-parse"; + +export async function extractPdfText(base64Data: string): Promise { + const pdfParse = new PDFParse({ data: base64Data }); + const data = await pdfParse.getText(); + return data.text; +} + +export async function extractDocxText(base64Data: string): Promise { + const buffer = Buffer.from(base64Data, "base64"); + const data = await extractRawText({ buffer }); + return data.value; +} diff --git a/src/integrations/orpc/services/ai.ts b/src/integrations/orpc/services/ai.ts index 64487d3e7..391444a50 100644 --- a/src/integrations/orpc/services/ai.ts +++ b/src/integrations/orpc/services/ai.ts @@ -13,9 +13,9 @@ import { type UIMessage, } from "ai"; import { createOllama } from "ai-sdk-ollama"; +import { jsonrepair } from "jsonrepair"; import { match } from "ts-pattern"; -import type { ZodError } from "zod"; -import z, { flattenError } from "zod"; +import z, { flattenError, ZodError } from "zod"; import chatSystemPromptTemplate from "@/integrations/ai/prompts/chat-system.md?raw"; import docxParserSystemPrompt from "@/integrations/ai/prompts/docx-parser-system.md?raw"; import docxParserUserPrompt from "@/integrations/ai/prompts/docx-parser-user.md?raw"; @@ -28,6 +28,204 @@ import { } from "@/integrations/ai/tools/patch-resume"; import type { ResumeData } from "@/schema/resume/data"; import { defaultResumeData, resumeDataSchema } from "@/schema/resume/data"; +import { isObject } from "@/utils/sanitize"; +import { extractDocxText, extractPdfText } from "./ai.server"; + +const aiExtractionTemplate = { + ...defaultResumeData, + basics: { + ...defaultResumeData.basics, + customFields: [{ id: "", icon: "", text: "", link: "" }], + }, + sections: { + ...defaultResumeData.sections, + profiles: { + ...defaultResumeData.sections.profiles, + items: [{ id: "", hidden: false, icon: "", network: "", username: "", website: { url: "", label: "" } }], + }, + experience: { + ...defaultResumeData.sections.experience, + items: [ + { + id: "", + hidden: false, + company: "", + position: "", + location: "", + period: "", + website: { url: "", label: "" }, + description: "", + }, + ], + }, + education: { + ...defaultResumeData.sections.education, + items: [ + { + id: "", + hidden: false, + school: "", + degree: "", + area: "", + grade: "", + location: "", + period: "", + website: { url: "", label: "" }, + description: "", + }, + ], + }, + projects: { + ...defaultResumeData.sections.projects, + items: [{ id: "", hidden: false, name: "", period: "", website: { url: "", label: "" }, description: "" }], + }, + skills: { + ...defaultResumeData.sections.skills, + items: [{ id: "", hidden: false, icon: "", name: "", proficiency: "", level: 0, keywords: [] }], + }, + languages: { + ...defaultResumeData.sections.languages, + items: [{ id: "", hidden: false, language: "", fluency: "", level: 0 }], + }, + interests: { + ...defaultResumeData.sections.interests, + items: [{ id: "", hidden: false, icon: "", name: "", keywords: [] }], + }, + awards: { + ...defaultResumeData.sections.awards, + items: [ + { id: "", hidden: false, title: "", awarder: "", date: "", website: { url: "", label: "" }, description: "" }, + ], + }, + certifications: { + ...defaultResumeData.sections.certifications, + items: [ + { id: "", hidden: false, title: "", issuer: "", date: "", website: { url: "", label: "" }, description: "" }, + ], + }, + publications: { + ...defaultResumeData.sections.publications, + items: [ + { id: "", hidden: false, title: "", publisher: "", date: "", website: { url: "", label: "" }, description: "" }, + ], + }, + volunteer: { + ...defaultResumeData.sections.volunteer, + items: [ + { + id: "", + hidden: false, + organization: "", + location: "", + period: "", + website: { url: "", label: "" }, + description: "", + }, + ], + }, + references: { + ...defaultResumeData.sections.references, + items: [ + { id: "", hidden: false, name: "", position: "", website: { url: "", label: "" }, phone: "", description: "" }, + ], + }, + }, +}; + +/** + * Merges two objects recursively, filling in missing properties in the target object + * with values from the source object, but does not overwrite existing properties in the target + * unless the source provides a defined, non-null value. + * + * Both target and source must be plain objects (Record). + * This function does not mutate either argument; returns a new object. + * + * @param target - The object to merge into (existing values take precedence) + * @param source - The object providing default values + * @returns The merged object + */ +function mergeDefaults, S extends Record>( + target: T, + source: S, +): T & S { + if (!isObject(target) || !isObject(source)) { + // Use source value if defined (non-null, non-undefined), else fallback to target + return (source !== undefined && source !== null ? source : target) as T & S; + } + + const output: Record = { ...target }; + + for (const key of Object.keys(source)) { + const sourceValue = source[key]; + if (sourceValue === undefined || sourceValue === null) { + continue; + } + const targetValue = target[key]; + + if (isObject(sourceValue) && isObject(targetValue)) { + output[key] = mergeDefaults(targetValue as Record, sourceValue as Record); + } else if (isObject(sourceValue) && (targetValue === undefined || targetValue === null)) { + // Fill with source object only if target does not have it + output[key] = sourceValue; + } else if (!isObject(sourceValue)) { + output[key] = sourceValue; + } else if (targetValue === undefined) { + output[key] = sourceValue; + } + } + + return output as T & S; +} + +function logAndRethrow(context: string, error: unknown): never { + if (error instanceof Error) { + console.error(`${context}:`, error.message); + throw error; + } + console.error(`Unknown error in ${context}:`, error); + throw new Error(`An unknown error occurred during ${context}.`); +} + +function parseAndValidateResumeJson(resultText: string): ResumeData { + let jsonString = resultText; + const firstCurly = jsonString.indexOf("{"); + const firstSquare = jsonString.indexOf("["); + const lastCurly = jsonString.lastIndexOf("}"); + const lastSquare = jsonString.lastIndexOf("]"); + + let firstIndex = -1; + if (firstCurly !== -1 && firstSquare !== -1) { + firstIndex = Math.min(firstCurly, firstSquare); + } else { + firstIndex = Math.max(firstCurly, firstSquare); + } + const lastIndex = Math.max(lastCurly, lastSquare); + + if (firstIndex !== -1 && lastIndex !== -1 && lastIndex >= firstIndex) { + jsonString = jsonString.substring(firstIndex, lastIndex + 1); + } + + try { + const repairedJson = jsonrepair(jsonString); + const parsedJson = JSON.parse(repairedJson); + const mergedData = mergeDefaults(defaultResumeData, parsedJson); + + return resumeDataSchema.parse({ + ...mergedData, + customSections: [], + picture: defaultResumeData.picture, + metadata: defaultResumeData.metadata, + }); + } catch (error: unknown) { + if (error instanceof ZodError) { + console.error("Zod Validation Errors:", JSON.stringify(flattenError(error), null, 2)); + throw error; + } + + console.error("Unknown error:", error); + throw new Error("An unknown error occurred while validating the merged resume data."); + } +} export const aiProviderSchema = z.enum(["ollama", "openai", "gemini", "anthropic", "vercel-ai-gateway"]); @@ -89,35 +287,28 @@ type ParsePdfInput = z.infer & { async function parsePdf(input: ParsePdfInput): Promise { const model = getModel(input); + const pdfText = await extractPdfText(input.file.data).catch((error: unknown) => + logAndRethrow("Failed to parse PDF locally", error), + ); + const result = await generateText({ model, - output: Output.object({ schema: resumeDataSchema }), messages: [ { role: "system", - content: pdfParserSystemPrompt, + content: + pdfParserSystemPrompt + + "\n\nIMPORTANT: You must return ONLY raw valid JSON. Do not return markdown, do not return explanations. Just the JSON object. Use the following JSON as a template and fill in the extracted values. For arrays, you MUST use the exact key names shown in the template (e.g. use 'description' instead of 'summary', 'website' instead of 'url'):\n\n" + + JSON.stringify(aiExtractionTemplate, null, 2), }, { role: "user", - content: [ - { type: "text", text: pdfParserUserPrompt }, - { - type: "file", - filename: input.file.name, - mediaType: "application/pdf", - data: input.file.data, - }, - ], + content: `${pdfParserUserPrompt}\n\n--- EXTRACTED RESUME TEXT ---\n${pdfText}\n--- END OF EXTRACTED TEXT ---`, }, ], - }); + }).catch((error: unknown) => logAndRethrow("Failed to generate the text with the model", error)); - return resumeDataSchema.parse({ - ...result.output, - customSections: [], - picture: defaultResumeData.picture, - metadata: defaultResumeData.metadata, - }); + return parseAndValidateResumeJson(result.text); } type ParseDocxInput = z.infer & { @@ -128,36 +319,28 @@ type ParseDocxInput = z.infer & { async function parseDocx(input: ParseDocxInput): Promise { const model = getModel(input); + const docxText = await extractDocxText(input.file.data).catch((error: unknown) => + logAndRethrow("Failed to parse DOCX locally", error), + ); + const result = await generateText({ model, - output: Output.object({ schema: resumeDataSchema }), messages: [ - { role: "system", content: docxParserSystemPrompt }, + { + role: "system", + content: + docxParserSystemPrompt + + "\n\nIMPORTANT: You must return ONLY raw valid JSON. Do not return markdown, do not return explanations. Just the JSON object. Use the following JSON as a template and fill in the extracted values. For arrays, you MUST use the exact key names shown in the template (e.g. use 'description' instead of 'summary', 'website' instead of 'url'):\n\n" + + JSON.stringify(aiExtractionTemplate, null, 2), + }, { role: "user", - content: [ - { type: "text", text: docxParserUserPrompt }, - { - type: "file", - filename: input.file.name, - mediaType: input.mediaType, - data: input.file.data, - }, - ], + content: `${docxParserUserPrompt}\n\n--- EXTRACTED RESUME TEXT ---\n${docxText}\n--- END OF EXTRACTED TEXT ---`, }, ], - }); + }).catch((error: unknown) => logAndRethrow("Failed to generate the text with the model", error)); - return resumeDataSchema.parse({ - ...result.output, - customSections: [], - picture: defaultResumeData.picture, - metadata: defaultResumeData.metadata, - }); -} - -export function formatZodError(error: ZodError): string { - return JSON.stringify(flattenError(error)); + return parseAndValidateResumeJson(result.text); } function buildChatSystemPrompt(resumeData: ResumeData): string { diff --git a/src/routes/builder/$resumeId/-sidebar/right/index.tsx b/src/routes/builder/$resumeId/-sidebar/right/index.tsx index 8158021bf..4a8e2064d 100644 --- a/src/routes/builder/$resumeId/-sidebar/right/index.tsx +++ b/src/routes/builder/$resumeId/-sidebar/right/index.tsx @@ -47,7 +47,10 @@ export function BuilderSidebarRight() { <> - +
{rightSidebarSections.map((section) => ( @@ -85,18 +88,18 @@ function SidebarEdge({ scrollAreaRef }: SidebarEdgeProps) {
- {rightSidebarSections.map((section) => ( - - ))} -
+ {rightSidebarSections.map((section) => ( + + ))} +
); diff --git a/src/utils/sanitize.ts b/src/utils/sanitize.ts index e21189d50..9137edfdc 100644 --- a/src/utils/sanitize.ts +++ b/src/utils/sanitize.ts @@ -109,3 +109,13 @@ export function sanitizeCss(css: string): string { return sanitized; } + +/** + * Checks if the given value is a plain JSON object (not null, not array, not other types). + * + * @param value - The value to check. + * @returns True if the value is a plain object, false otherwise. + */ +export function isObject(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +}