From 6faefb534854f98aef23e414bb8c5150f82376a7 Mon Sep 17 00:00:00 2001 From: JockDaRock Date: Wed, 16 Jul 2025 12:32:41 -0500 Subject: [PATCH 1/4] updated save options for doc-editor --- agent-frontend/package-lock.json | 325 ++++++++++++- agent-frontend/package.json | 2 + .../src/components/DocumentEditor.jsx | 426 ++++++++++++++++++ 3 files changed, 746 insertions(+), 7 deletions(-) diff --git a/agent-frontend/package-lock.json b/agent-frontend/package-lock.json index 8e7cb04..112d77d 100644 --- a/agent-frontend/package-lock.json +++ b/agent-frontend/package-lock.json @@ -12,6 +12,8 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "docx": "^9.5.1", + "jspdf": "^3.0.1", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", @@ -4315,11 +4317,12 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, "node_modules/@types/node": { - "version": "22.15.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.14.tgz", - "integrity": "sha512-BL1eyu/XWsFGTtDWOYULQEs4KR0qdtYfCxYAUYRoB7JP7h9ETYLgQTww6kH8Sj2C0pFGgrpM0XKv6/kbIzYJ1g==", + "version": "24.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", + "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", + "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/node-forge": { @@ -4356,6 +4359,13 @@ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", @@ -5311,6 +5321,18 @@ "node": ">= 4.0.0" } }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -5648,6 +5670,16 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -5817,6 +5849,18 @@ "node-int64": "^0.4.0" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -5951,6 +5995,26 @@ } ] }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -6502,6 +6566,16 @@ "postcss": "^8.4" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -7184,6 +7258,41 @@ "node": ">=6.0.0" } }, + "node_modules/docx": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/docx/-/docx-9.5.1.tgz", + "integrity": "sha512-ABDI7JEirFD2+bHhOBlsGZxaG1UgZb2M/QMKhLSDGgVNhxDesTCDcP+qoDnDGjZ4EOXTRfUjUgwHVuZ6VSTfWQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^24.0.1", + "hash.js": "^1.1.7", + "jszip": "^3.10.1", + "nanoid": "^5.1.3", + "xml": "^1.0.1", + "xml-js": "^1.6.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/docx/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -7255,6 +7364,16 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -8474,6 +8593,12 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9241,6 +9366,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -9516,6 +9651,20 @@ } } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -9674,6 +9823,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", @@ -12443,6 +12598,24 @@ "node": ">=0.10.0" } }, + "node_modules/jspdf": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz", + "integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.7", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -12457,6 +12630,54 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -12534,6 +12755,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.29.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", @@ -14110,6 +14340,12 @@ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -16715,6 +16951,16 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -17197,6 +17443,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -17459,6 +17711,16 @@ "node": ">=8" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -17990,6 +18252,16 @@ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -18373,6 +18645,16 @@ "node": ">=8" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -18707,9 +18989,10 @@ "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", @@ -18958,6 +19241,16 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -19819,6 +20112,24 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, "node_modules/xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/agent-frontend/package.json b/agent-frontend/package.json index 33b1e58..af52e61 100644 --- a/agent-frontend/package.json +++ b/agent-frontend/package.json @@ -7,6 +7,8 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "docx": "^9.5.1", + "jspdf": "^3.0.1", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", diff --git a/agent-frontend/src/components/DocumentEditor.jsx b/agent-frontend/src/components/DocumentEditor.jsx index 4785a50..74652c3 100644 --- a/agent-frontend/src/components/DocumentEditor.jsx +++ b/agent-frontend/src/components/DocumentEditor.jsx @@ -2,6 +2,8 @@ import React, { useState, useContext, useRef, useEffect } from 'react'; import { SettingsContext } from './SettingsContext'; import toast from 'react-hot-toast'; import './DocumentPreview.css'; +import { Document, Packer, Paragraph, TextRun, HeadingLevel, LevelFormat, AlignmentType } from 'docx'; +import jsPDF from 'jspdf'; const DocumentEditor = () => { const { settings } = useContext(SettingsContext); @@ -371,6 +373,409 @@ const DocumentEditor = () => { .replace(/\n/g, '
'); }; + const handleWordExport = async () => { + try { + toast.loading('Generating Word document...', { id: 'word-export' }); + + const content = markdownContent || ''; + if (!content.trim()) { + toast.error('Document is empty. Please add some content before exporting.'); + return; + } + + // Parse markdown content into document structure + const lines = content.split('\n'); + const docElements = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + if (!line) { + // Add empty paragraph for spacing + docElements.push(new Paragraph({ text: '' })); + continue; + } + + // Handle headings + if (line.startsWith('#')) { + const level = line.match(/^#+/)[0].length; + const text = line.replace(/^#+\s*/, ''); + const headingLevel = Math.min(level, 6); + + docElements.push( + new Paragraph({ + text: text, + heading: [HeadingLevel.HEADING_1, HeadingLevel.HEADING_2, HeadingLevel.HEADING_3, + HeadingLevel.HEADING_4, HeadingLevel.HEADING_5, HeadingLevel.HEADING_6][headingLevel - 1], + spacing: { before: 240, after: 120 } + }) + ); + } + // Handle blockquotes + else if (line.startsWith('>')) { + const quoteText = line.replace(/^>\s*/, ''); + docElements.push( + new Paragraph({ + children: [new TextRun({ text: quoteText, italics: true })], + indent: { left: 720 }, + spacing: { before: 120, after: 120 } + }) + ); + } + // Handle unordered lists + else if (line.startsWith('- ')) { + const text = line.replace(/^-\s*/, ''); + docElements.push( + new Paragraph({ + text: text, + bullet: { level: 0 }, + spacing: { before: 60, after: 60 } + }) + ); + } + // Handle ordered lists + else if (line.match(/^\d+\.\s/)) { + const text = line.replace(/^\d+\.\s*/, ''); + docElements.push( + new Paragraph({ + text: text, + numbering: { reference: 'my-numbering', level: 0 }, + spacing: { before: 60, after: 60 } + }) + ); + } + // Handle code blocks + else if (line.startsWith('```')) { + // Find the end of the code block + let codeContent = ''; + let j = i + 1; + while (j < lines.length && !lines[j].trim().startsWith('```')) { + codeContent += lines[j] + '\n'; + j++; + } + i = j; // Skip to the end of the code block + + docElements.push( + new Paragraph({ + children: [new TextRun({ + text: codeContent.trim(), + font: { name: 'Courier New' }, + size: 20 + })], + spacing: { before: 120, after: 120 }, + indent: { left: 360 } + }) + ); + } + // Handle regular paragraphs + else { + // Process inline formatting properly + const children = []; + let remaining = line; + + // Parse the text to handle inline formatting + while (remaining) { + // Look for bold text + const boldMatch = remaining.match(/\*\*([^*]+)\*\*/); + if (boldMatch) { + // Add text before bold + if (boldMatch.index > 0) { + children.push(new TextRun({ text: remaining.substring(0, boldMatch.index) })); + } + // Add bold text + children.push(new TextRun({ text: boldMatch[1], bold: true })); + remaining = remaining.substring(boldMatch.index + boldMatch[0].length); + continue; + } + + // Look for italic text + const italicMatch = remaining.match(/\*([^*]+)\*/); + if (italicMatch) { + // Add text before italic + if (italicMatch.index > 0) { + children.push(new TextRun({ text: remaining.substring(0, italicMatch.index) })); + } + // Add italic text + children.push(new TextRun({ text: italicMatch[1], italics: true })); + remaining = remaining.substring(italicMatch.index + italicMatch[0].length); + continue; + } + + // Look for inline code + const codeMatch = remaining.match(/`([^`]+)`/); + if (codeMatch) { + // Add text before code + if (codeMatch.index > 0) { + children.push(new TextRun({ text: remaining.substring(0, codeMatch.index) })); + } + // Add code text + children.push(new TextRun({ + text: codeMatch[1], + font: { name: 'Courier New' }, + size: 20 + })); + remaining = remaining.substring(codeMatch.index + codeMatch[0].length); + continue; + } + + // No more formatting, add remaining text + children.push(new TextRun({ text: remaining })); + break; + } + + // If no content was processed, add the original line + if (children.length === 0) { + children.push(new TextRun({ text: line })); + } + + docElements.push( + new Paragraph({ + children: children, + spacing: { before: 120, after: 120 } + }) + ); + } + } + + // Create the document with proper structure and compatibility + const doc = new Document({ + creator: 'Scribby Document Editor', + title: 'Scribby Document', + description: 'Document created with Scribby', + numbering: { + config: [ + { + reference: 'my-numbering', + levels: [ + { + level: 0, + format: LevelFormat.DECIMAL, + text: '%1.', + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 720, hanging: 360 }, + }, + }, + }, + ], + }, + ], + }, + sections: [ + { + properties: {}, + children: docElements.length > 0 ? docElements : [ + new Paragraph({ + text: 'Empty Document', + spacing: { before: 120, after: 120 } + }) + ] + } + ] + }); + + // Generate and download the document + const buffer = await Packer.toBlob(doc); + const url = URL.createObjectURL(buffer); + const link = document.createElement('a'); + link.href = url; + link.download = `scribby-document-${new Date().toISOString().slice(0, 10)}.docx`; + link.style.display = 'none'; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + + toast.success('Word document exported successfully!', { id: 'word-export' }); + } catch (error) { + console.error('Word export error:', error); + toast.error('Failed to export Word document. Please try again.', { id: 'word-export' }); + } + }; + + const handlePDFExport = async () => { + try { + toast.loading('Generating PDF document...', { id: 'pdf-export' }); + + const content = markdownContent || ''; + if (!content.trim()) { + toast.error('Document is empty. Please add some content before exporting.'); + return; + } + + // Create a new jsPDF instance + const doc = new jsPDF({ + orientation: 'portrait', + unit: 'mm', + format: 'a4' + }); + + // Set up document properties + const pageWidth = doc.internal.pageSize.getWidth(); + const pageHeight = doc.internal.pageSize.getHeight(); + const margin = 20; + const maxLineWidth = pageWidth - 2 * margin; + + let yPosition = margin; + const lineHeight = 7; + const paragraphSpacing = 10; + + // Parse markdown content line by line + const lines = content.split('\n'); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + // Skip empty lines but add some spacing + if (!line) { + yPosition += lineHeight / 2; + continue; + } + + // Check if we need a new page + if (yPosition > pageHeight - margin) { + doc.addPage(); + yPosition = margin; + } + + // Handle headings + if (line.startsWith('#')) { + const level = line.match(/^#+/)[0].length; + const text = line.replace(/^#+\s*/, ''); + + // Set font size based on heading level + const fontSize = Math.max(20 - (level - 1) * 2, 12); + doc.setFontSize(fontSize); + doc.setFont('helvetica', 'bold'); + + yPosition += paragraphSpacing; + + // Split text if it's too long + const textLines = doc.splitTextToSize(text, maxLineWidth); + textLines.forEach((textLine) => { + if (yPosition > pageHeight - margin) { + doc.addPage(); + yPosition = margin; + } + doc.text(textLine, margin, yPosition); + yPosition += lineHeight + 2; + }); + + yPosition += paragraphSpacing / 2; + } + // Handle blockquotes + else if (line.startsWith('>')) { + const quoteText = line.replace(/^>\s*/, ''); + doc.setFontSize(11); + doc.setFont('helvetica', 'italic'); + + yPosition += paragraphSpacing / 2; + + const textLines = doc.splitTextToSize(quoteText, maxLineWidth - 20); + textLines.forEach((textLine) => { + if (yPosition > pageHeight - margin) { + doc.addPage(); + yPosition = margin; + } + doc.text(textLine, margin + 10, yPosition); + yPosition += lineHeight; + }); + + yPosition += paragraphSpacing / 2; + } + // Handle lists + else if (line.startsWith('- ') || line.match(/^\d+\.\s/)) { + const text = line.replace(/^[-\d.]\s*/, ''); + const bullet = line.startsWith('- ') ? '• ' : '1. '; + + doc.setFontSize(12); + doc.setFont('helvetica', 'normal'); + + const textLines = doc.splitTextToSize(text, maxLineWidth - 20); + textLines.forEach((textLine, index) => { + if (yPosition > pageHeight - margin) { + doc.addPage(); + yPosition = margin; + } + const prefix = index === 0 ? bullet : ' '; + doc.text(prefix + textLine, margin + 10, yPosition); + yPosition += lineHeight; + }); + } + // Handle code blocks + else if (line.startsWith('```')) { + // Find the end of the code block + let codeContent = ''; + let j = i + 1; + while (j < lines.length && !lines[j].trim().startsWith('```')) { + codeContent += lines[j] + '\n'; + j++; + } + i = j; // Skip to the end of the code block + + doc.setFontSize(10); + doc.setFont('courier', 'normal'); + + yPosition += paragraphSpacing / 2; + + const codeLines = codeContent.split('\n'); + codeLines.forEach((codeLine) => { + if (yPosition > pageHeight - margin) { + doc.addPage(); + yPosition = margin; + } + doc.text(codeLine, margin + 5, yPosition); + yPosition += lineHeight; + }); + + yPosition += paragraphSpacing / 2; + } + // Handle regular paragraphs + else { + // Process inline formatting (simplified) + let processedText = line + .replace(/\*\*([^*]+)\*\*/g, '$1') // Remove bold markers for PDF + .replace(/\*([^*]+)\*/g, '$1') // Remove italic markers for PDF + .replace(/`([^`]+)`/g, '$1'); // Remove inline code markers for PDF + + doc.setFontSize(12); + doc.setFont('helvetica', 'normal'); + + const textLines = doc.splitTextToSize(processedText, maxLineWidth); + textLines.forEach((textLine) => { + if (yPosition > pageHeight - margin) { + doc.addPage(); + yPosition = margin; + } + doc.text(textLine, margin, yPosition); + yPosition += lineHeight; + }); + + yPosition += paragraphSpacing / 2; + } + } + + // Add page numbers + const pageCount = doc.internal.getNumberOfPages(); + for (let i = 1; i <= pageCount; i++) { + doc.setPage(i); + doc.setFontSize(10); + doc.setFont('helvetica', 'normal'); + doc.text(`Page ${i} of ${pageCount}`, pageWidth - margin - 20, pageHeight - 10); + } + + // Save the PDF + doc.save(`scribby-document-${new Date().toISOString().slice(0, 10)}.pdf`); + + toast.success('PDF document exported successfully!', { id: 'pdf-export' }); + } catch (error) { + console.error('PDF export error:', error); + toast.error('Failed to export PDF document. Please try again.', { id: 'pdf-export' }); + } + }; + const renderEditor = () => { switch (activeTab) { case 'text': // Preview Tab - Shows rendered content @@ -546,6 +951,27 @@ const DocumentEditor = () => { 📝 Markdown (.md) +
+ + )} From 7db89a84191321b895f53db036cc3e0f26677358 Mon Sep 17 00:00:00 2001 From: JockDaRock Date: Wed, 16 Jul 2025 13:17:27 -0500 Subject: [PATCH 2/4] fixed black and white text based on dark and light mode, for consistency. --- .../src/components/DocumentEditor.jsx | 41 ++++++++++++------- .../src/components/DocumentPreview.css | 14 +++++++ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/agent-frontend/src/components/DocumentEditor.jsx b/agent-frontend/src/components/DocumentEditor.jsx index 74652c3..f4c3713 100644 --- a/agent-frontend/src/components/DocumentEditor.jsx +++ b/agent-frontend/src/components/DocumentEditor.jsx @@ -116,6 +116,23 @@ const DocumentEditor = () => { return `${codeBlockPlaceholder}${codeBlocks.length - 1}`; }); + // Helper function to process inline formatting + const processInlineFormatting = (text) => { + return text + // Handle inline code (but not code blocks) + .replace(/`([^`]+)`/g, '$1') + + // Handle bold and italic + .replace(/\*\*([^*]+)\*\*/g, '$1') + .replace(/\*([^*]+)\*/g, '$1') + + // Handle links + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') + + // Handle images + .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1'); + }; + // Handle lists - this is more complex to get right const lines = htmlContent.split('\n'); let htmlLines = []; @@ -137,7 +154,8 @@ const DocumentEditor = () => { inOrderedList = false; } const text = line.replace(/^- (.*)/, '$1'); - htmlLines.push(`
  • ${text}
  • `); + const processedText = processInlineFormatting(text); + htmlLines.push(`
  • ${processedText}
  • `); } else if (isOrderedItem) { if (!inOrderedList) { htmlLines.push('
      '); @@ -148,7 +166,8 @@ const DocumentEditor = () => { inUnorderedList = false; } const text = line.replace(/^\d+\. (.*)/, '$1'); - htmlLines.push(`
    1. ${text}
    2. `); + const processedText = processInlineFormatting(text); + htmlLines.push(`
    3. ${processedText}
    4. `); } else { if (inUnorderedList) { htmlLines.push(''); @@ -169,22 +188,14 @@ const DocumentEditor = () => { .replace(/^##### (.*$)/, '
      $1
      ') .replace(/^###### (.*$)/, '
      $1
      ') - // Handle inline code (but not code blocks) - .replace(/`([^`]+)`/g, '$1') - - // Handle bold and italic - .replace(/\*\*([^*]+)\*\*/g, '$1') - .replace(/\*([^*]+)\*/g, '$1') - - // Handle links - .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') - - // Handle images - .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1') - // Handle blockquotes .replace(/^> (.*$)/, '
      $1
      '); + // Apply inline formatting to non-heading, non-blockquote lines + if (!processedLine.match(/^/) && !processedLine.match(/^
      /)) { + processedLine = processInlineFormatting(processedLine); + } + // Add paragraphs for non-empty lines that aren't already wrapped in HTML tags if (processedLine.trim() && !processedLine.match(/^<[^>]+>/) && !processedLine.includes(codeBlockPlaceholder)) { processedLine = `

      ${processedLine}

      `; diff --git a/agent-frontend/src/components/DocumentPreview.css b/agent-frontend/src/components/DocumentPreview.css index 69e5000..fa0e9f9 100644 --- a/agent-frontend/src/components/DocumentPreview.css +++ b/agent-frontend/src/components/DocumentPreview.css @@ -134,6 +134,20 @@ color: #e5e7eb; } +.dark .document-preview ul, +.dark .document-preview ol, +.dark .document-preview li { + color: #e5e7eb; +} + +.dark .document-preview strong { + color: #f9fafb; +} + +.dark .document-preview em { + color: #e5e7eb; +} + .dark .document-preview blockquote { background-color: #374151; color: #d1d5db; From b2eab760cf8484832109477966acab441ffc80ab Mon Sep 17 00:00:00 2001 From: JockDaRock Date: Fri, 8 Aug 2025 16:47:03 -0500 Subject: [PATCH 3/4] updated doc editor and added general content generation tools --- .gitignore | 1 + agent-frontend/src/App.jsx | 2 + agent-frontend/src/components/Sidebar.jsx | 9 + .../src/components/SocialMediaCreator.jsx | 1080 +++++++++++++++++ backend-agent/agent.py | 256 ++-- 5 files changed, 1267 insertions(+), 81 deletions(-) create mode 100644 agent-frontend/src/components/SocialMediaCreator.jsx diff --git a/.gitignore b/.gitignore index d36df23..77e4ff1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ agent-frontend/node_modules +agent-frontend/build backend/.venv backend/__pycache__ backend/outputs diff --git a/agent-frontend/src/App.jsx b/agent-frontend/src/App.jsx index 233eddf..2f02728 100644 --- a/agent-frontend/src/App.jsx +++ b/agent-frontend/src/App.jsx @@ -15,6 +15,7 @@ import ProcessingStatus from './components/ProcessingStatus'; import ResultsDisplay from './components/ResultsDisplay'; import Settings from './components/Settings'; import DocumentEditor from './components/DocumentEditor'; +import SocialMediaCreator from './components/SocialMediaCreator'; // Main App component function AppContent() { @@ -156,6 +157,7 @@ function AppContent() { /> } /> } /> + } /> diff --git a/agent-frontend/src/components/Sidebar.jsx b/agent-frontend/src/components/Sidebar.jsx index 13b98fd..9602bf6 100644 --- a/agent-frontend/src/components/Sidebar.jsx +++ b/agent-frontend/src/components/Sidebar.jsx @@ -14,6 +14,15 @@ const Sidebar = ({ isOpen, onClose }) => { ) }, + { + path: '/social-creator', + label: 'Social Media Creator', + icon: ( + + + + ) + }, { path: '/document-editor', label: 'Document Editor', diff --git a/agent-frontend/src/components/SocialMediaCreator.jsx b/agent-frontend/src/components/SocialMediaCreator.jsx new file mode 100644 index 0000000..33ea0b9 --- /dev/null +++ b/agent-frontend/src/components/SocialMediaCreator.jsx @@ -0,0 +1,1080 @@ +import React, { useState, useContext, useEffect, useCallback } from 'react'; +import { Link } from 'react-router-dom'; +import toast from 'react-hot-toast'; +import { SettingsContext } from './SettingsContext'; + +// Platform-specific icons +const PlatformIcon = ({ platform }) => { + const iconClasses = "h-5 w-5"; + + switch (platform) { + case 'LinkedIn': + return ( + + + + ); + case 'Twitter': + return ( + + + + ); + case 'BlueSky': + return ( + + + + ); + case 'Instagram': + return ( + + + + ); + case 'Facebook': + return ( + + + + ); + case 'TikTok': + return ( + + + + ); + default: + return ( + + + + ); + } +}; + +// Platform Card Component +const PlatformCard = ({ platform, content, settings }) => { + const [isEditing, setIsEditing] = useState(false); + const [editedText, setEditedText] = useState(content.text); + const [isCopied, setIsCopied] = useState(false); + const [isRevisioning, setIsRevisioning] = useState(false); + const [revisionInstructions, setRevisionInstructions] = useState(''); + const [isRevisionLoading, setIsRevisionLoading] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(isEditing ? editedText : content.text); + setIsCopied(true); + toast.success(`${platform} content copied to clipboard!`); + setTimeout(() => setIsCopied(false), 2000); + }; + + const handleEdit = () => { + setIsEditing(true); + }; + + const handleSave = () => { + setIsEditing(false); + content.text = editedText; + content.character_count = editedText.length; + }; + + const handleCancel = () => { + setIsEditing(false); + setEditedText(content.text); + }; + + const handleRevisionSubmit = async () => { + if (!revisionInstructions.trim()) { + toast.error('Please provide revision instructions'); + return; + } + + if (!settings.llmApiKey) { + toast.error('LLM API key is required for revision. Please check your settings.'); + return; + } + + setIsRevisionLoading(true); + + try { + const agentApiUrl = settings.llmApiUrl || process.env.REACT_APP_AGENT_API_URL || 'http://localhost:8001'; + const response = await fetch(`${agentApiUrl}/revise`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + content: editedText, + platform: platform, + content_type: 'social_media', + instructions: revisionInstructions, + llm_api_key: settings.llmApiKey, + llm_model: settings.defaultLlmModel, + llm_base_url: settings.llmBaseUrl + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Revision request failed: ${errorText}`); + } + + const data = await response.json(); + setEditedText(data.revised_content); + content.text = data.revised_content; + content.character_count = data.revised_content.length; + + setIsRevisioning(false); + setRevisionInstructions(''); + toast.success('Content revised successfully!'); + + } catch (error) { + console.error('Revision error:', error); + toast.error('Failed to revise content. Please try again.'); + } finally { + setIsRevisionLoading(false); + } + }; + + // Platform-specific styles + const platformStyles = { + LinkedIn: 'bg-blue-100 border-blue-300 text-blue-800 dark:bg-blue-900/30 dark:border-blue-800 dark:text-blue-200', + Twitter: 'bg-sky-100 border-sky-300 text-sky-800 dark:bg-sky-900/30 dark:border-sky-800 dark:text-sky-200', + BlueSky: 'bg-cyan-100 border-cyan-300 text-cyan-800 dark:bg-cyan-900/30 dark:border-cyan-800 dark:text-cyan-200', + Instagram: 'bg-pink-100 border-pink-300 text-pink-800 dark:bg-pink-900/30 dark:border-pink-800 dark:text-pink-200', + Facebook: 'bg-indigo-100 border-indigo-300 text-indigo-800 dark:bg-indigo-900/30 dark:border-indigo-800 dark:text-indigo-200', + TikTok: 'bg-gray-100 border-gray-300 text-gray-800 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200', + }; + + const style = platformStyles[platform] || 'bg-gray-100 border-gray-300 text-gray-800 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200'; + + const charLimit = { + LinkedIn: 3000, + Twitter: 280, + BlueSky: 300, + Instagram: 2200, + Facebook: 63206, + TikTok: 2200, + }[platform] || 1000; + + const charCount = isEditing ? editedText.length : content.character_count; + const isOverLimit = charCount > charLimit; + + return ( +
      +
      +
      + +

      {platform}

      +
      +
      + + + +
      +
      + + {isEditing ? ( +
      +