From 39fbc382ae5eb2efa735b477edc60d809b43484d Mon Sep 17 00:00:00 2001 From: Artem Astapenko <3767150+jamakase@users.noreply.github.com> Date: Fri, 13 Sep 2024 01:44:44 +0300 Subject: [PATCH] Refactor frontend (#55) * Refactor frontend * Frontend fixes * Fix addition * Add pdf support --- frontend/package-lock.json | 646 +++++++++++++++++- frontend/package.json | 6 +- .../(withSidebar)/conversations/[id]/page.tsx | 133 ++++ .../(withSidebar)/conversations/layout.tsx | 36 + .../app/(withSidebar)/conversations/page.tsx | 26 + frontend/src/app/(withSidebar)/layout.tsx | 15 + frontend/src/app/(withSidebar)/page.tsx | 5 + frontend/src/app/_components/MainLayout.tsx | 58 ++ frontend/src/app/_components/MainSidebar.tsx | 55 ++ .../MessageList.tsx | 6 +- frontend/src/app/_components/Sidebar.tsx | 130 ++++ frontend/src/app/components/Sidebar.tsx | 128 ---- frontend/src/app/conversations/[id]/page.tsx | 295 -------- frontend/src/app/layout.tsx | 6 +- frontend/src/app/page.tsx | 31 - frontend/src/app/search/page.tsx | 101 ++- frontend/src/components/ui/button.tsx | 6 +- frontend/src/components/ui/drawer.tsx | 118 ++++ frontend/src/components/ui/sheet.tsx | 140 ++++ frontend/src/components/ui/tooltip.tsx | 30 + services/ml/app/models/qa.py | 9 + services/ml/app/routes/file.py | 29 + services/ml/app/routes/qa.py | 0 services/ml/app/server.py | 19 +- services/ml/poetry.lock | 22 +- services/ml/pyproject.toml | 1 + 26 files changed, 1507 insertions(+), 544 deletions(-) create mode 100644 frontend/src/app/(withSidebar)/conversations/[id]/page.tsx create mode 100644 frontend/src/app/(withSidebar)/conversations/layout.tsx create mode 100644 frontend/src/app/(withSidebar)/conversations/page.tsx create mode 100644 frontend/src/app/(withSidebar)/layout.tsx create mode 100644 frontend/src/app/(withSidebar)/page.tsx create mode 100644 frontend/src/app/_components/MainLayout.tsx create mode 100644 frontend/src/app/_components/MainSidebar.tsx rename frontend/src/app/{components => _components}/MessageList.tsx (75%) create mode 100644 frontend/src/app/_components/Sidebar.tsx delete mode 100644 frontend/src/app/components/Sidebar.tsx delete mode 100644 frontend/src/app/conversations/[id]/page.tsx delete mode 100644 frontend/src/app/page.tsx create mode 100644 frontend/src/components/ui/drawer.tsx create mode 100644 frontend/src/components/ui/sheet.tsx create mode 100644 frontend/src/components/ui/tooltip.tsx create mode 100644 services/ml/app/models/qa.py create mode 100644 services/ml/app/routes/file.py create mode 100644 services/ml/app/routes/qa.py diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 79bb864..be104d5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,9 +8,11 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "framer-motion": "^11.5.2", @@ -18,9 +20,11 @@ "next": "14.2.7", "react": "^18", "react-dom": "^18", + "react-query": "^3.39.3", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "vaul": "^0.9.2" }, "devDependencies": { "@types/node": "^20", @@ -45,6 +49,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -101,6 +116,40 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz", + "integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==", + "dependencies": { + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz", + "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz", + "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -431,6 +480,28 @@ "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", "license": "MIT" }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", @@ -461,6 +532,41 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -476,6 +582,70 @@ } } }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", + "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", + "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-icons": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", @@ -485,6 +655,77 @@ "react": "^16.x || ^17.x || ^18.x" } }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", + "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-presence": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", @@ -581,6 +822,39 @@ } } }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz", + "integrity": "sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -596,6 +870,40 @@ } } }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-layout-effect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", @@ -611,6 +919,67 @@ } } }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", + "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -904,6 +1273,17 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", @@ -1116,6 +1496,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1131,7 +1519,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1148,6 +1535,21 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -1326,8 +1728,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -1505,6 +1906,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2338,8 +2749,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -2408,6 +2818,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -2684,7 +3102,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2693,8 +3110,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { "version": "1.0.7", @@ -2710,6 +3126,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -3148,6 +3572,11 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3309,6 +3738,15 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3329,11 +3767,15 @@ "node": ">=8.6" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3374,6 +3816,14 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -3615,11 +4065,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -3696,7 +4150,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4003,6 +4456,98 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -4043,6 +4588,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -4061,6 +4611,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -4109,7 +4664,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -4125,7 +4679,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4865,6 +5418,15 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4874,6 +5436,47 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4892,6 +5495,18 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vaul": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-0.9.2.tgz", + "integrity": "sha512-m2A7UgAU/JMWiwUhmARK8LMvEfXiudA4trJxfZF5AtH2uBTgN855msZ2yjPnUDfa7i5glocMYLSfML8wriBtBA==", + "dependencies": { + "@radix-ui/react-dialog": "^1.0.4" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5084,8 +5699,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/yaml": { "version": "2.5.1", diff --git a/frontend/package.json b/frontend/package.json index 990f583..71166e4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,9 +9,11 @@ "lint": "next lint" }, "dependencies": { + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "framer-motion": "^11.5.2", @@ -19,9 +21,11 @@ "next": "14.2.7", "react": "^18", "react-dom": "^18", + "react-query": "^3.39.3", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "vaul": "^0.9.2" }, "devDependencies": { "@types/node": "^20", diff --git a/frontend/src/app/(withSidebar)/conversations/[id]/page.tsx b/frontend/src/app/(withSidebar)/conversations/[id]/page.tsx new file mode 100644 index 0000000..c6b9009 --- /dev/null +++ b/frontend/src/app/(withSidebar)/conversations/[id]/page.tsx @@ -0,0 +1,133 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { api } from "@/domain/api/api"; +import { useConfig } from "@/domain/config/ConfigProvider"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "react-query"; +import MessageList from "../../../_components/MessageList"; +import { Send } from "lucide-react"; + +// Тип для хранения информации о чате +type Conversation = { + id: number; + name: string; + messages: Array<{ id: number; text: string; sender: string }>; +}; + +// Основная функция компонента +export default function ConversationPage({ + params, +}: { + params: { id: string }; +}) { + const [message, setMessage] = useState(""); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + const config = useConfig(); + const router = useRouter(); + const queryClient = useQueryClient(); + + const { data: currentConversation } = useQuery( + ["conversation", params.id], + () => + api.getMessages(config.ENDPOINT, parseInt(params.id)).then((response) => { + if ( + response && + response.length > 0 && + response[0].messages && + response[0].messages.length > 0 + ) { + const conversationMessages = + response[0].messages[0][parseInt(params.id)]; + if (conversationMessages && Array.isArray(conversationMessages)) { + return conversationMessages.map((msg: any) => ({ + id: msg.id, + text: msg.text, + sender: msg.role === 1 ? "bot" : "user", + })); + } + } + return []; + }), + { enabled: !!params.id } + ); + + const sendMessageMutation = useMutation( + (newMessage: string) => + api.sendMessage(config.ENDPOINT, parseInt(params.id), newMessage), + { + onSuccess: (data) => { + queryClient.invalidateQueries(["conversation", params.id]); + }, + } + ); + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + handleSendMessage(); + } + }; + + const handleSendMessage = async () => { + if (!message.trim()) return; + sendMessageMutation.mutate(message); + setMessage(""); + }; + + return ( +
+ {/* {!isSidebarOpen && ( + + )} */} + +
+ +
+ + +
+
+ setMessage(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Введите ваш вопрос" + className="flex-grow p-2 focus:outline-none h-full" + style={{ color: "black" }} + /> + +
+
+
+
+
+ ); +} diff --git a/frontend/src/app/(withSidebar)/conversations/layout.tsx b/frontend/src/app/(withSidebar)/conversations/layout.tsx new file mode 100644 index 0000000..8dde831 --- /dev/null +++ b/frontend/src/app/(withSidebar)/conversations/layout.tsx @@ -0,0 +1,36 @@ +"use client"; + +import Sidebar from "@/app/_components/Sidebar"; +import { api } from "@/domain/api/api"; +import { useConfig } from "@/domain/config/ConfigProvider"; +import { useRouter } from "next/navigation"; +import { useMutation } from "react-query"; + +export default function RootLayout({ + children, + params, +}: { + children: React.ReactNode; + params: { id?: string }; +}) { + const conversationId = params.id; + const router = useRouter(); + const config = useConfig(); + + const createConversationMutation = useMutation({ + mutationFn: () => api.createConversation(config.ENDPOINT), + onSuccess: (response: any) => { + router.push(`/conversations/${response.data.id}`); + }, + }); + + return ( +
+ + {children} +
+ ); +} diff --git a/frontend/src/app/(withSidebar)/conversations/page.tsx b/frontend/src/app/(withSidebar)/conversations/page.tsx new file mode 100644 index 0000000..04b517d --- /dev/null +++ b/frontend/src/app/(withSidebar)/conversations/page.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { api } from '@/domain/api/api'; +import { useConfig } from '@/domain/config/ConfigProvider'; +import { MessagesSquare } from 'lucide-react'; +import { useQuery } from 'react-query'; + +export default function Home() { + const config = useConfig(); + + const { data: conversations = [] } = useQuery('conversations', () => + api.get_messages__user_id_(config.ENDPOINT) + .then(response => response.data.map((id: number, index: number) => ({ + id, + name: `Чат ${index + 1}`, + messages: [] + }))) + ); + + return ( +
+ + Самое время начать общение! +
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/(withSidebar)/layout.tsx b/frontend/src/app/(withSidebar)/layout.tsx new file mode 100644 index 0000000..6467cb3 --- /dev/null +++ b/frontend/src/app/(withSidebar)/layout.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from "react-query"; +// import { MainLayout } from "./components/MainLayout"; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + const queryClient = new QueryClient(); + return ( + {children} + ); +} diff --git a/frontend/src/app/(withSidebar)/page.tsx b/frontend/src/app/(withSidebar)/page.tsx new file mode 100644 index 0000000..fa39b84 --- /dev/null +++ b/frontend/src/app/(withSidebar)/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from 'next/navigation'; + +export default function Home() { + redirect('/conversations'); +} \ No newline at end of file diff --git a/frontend/src/app/_components/MainLayout.tsx b/frontend/src/app/_components/MainLayout.tsx new file mode 100644 index 0000000..8f929c7 --- /dev/null +++ b/frontend/src/app/_components/MainLayout.tsx @@ -0,0 +1,58 @@ +"use client" +import { Sheet, SheetContent, SheetTrigger, SheetClose } from "@/components/ui/sheet" +import MainSidebar from "./MainSidebar" +import { MessagesSquare, PanelLeft, Search } from "lucide-react" +import { Button } from "@/components/ui/button" +import Link from "next/link" +import { usePathname } from "next/navigation" + +const sideBarData = [ + {id: 1, name: 'Список чатов', icon: MessagesSquare, href: '/conversations'}, + {id: 2, name: 'Поиск по документам', icon: Search, href: '/search'}, +]; + +export function MainLayout({children}: {children?: any}) { + const pathname = usePathname(); + + return ( +
+ +
+ +
+ + + + + + + + +
+ {pathname === '/search' ? "Поиск по документам" : 'Список чатов'} +
+
+ +
+ {children} +
+
+
+ ) +}; diff --git a/frontend/src/app/_components/MainSidebar.tsx b/frontend/src/app/_components/MainSidebar.tsx new file mode 100644 index 0000000..920f813 --- /dev/null +++ b/frontend/src/app/_components/MainSidebar.tsx @@ -0,0 +1,55 @@ +"use client" + +import React from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation' + +import { + Tooltip, + TooltipContent, + TooltipTrigger, + TooltipProvider + } from "@/components/ui/tooltip" + +type Conversation = { + id: number; + name: string; + messages: Array<{ id: number; text: string; sender: string }>; +}; + +type SidebarProps = { + data: {id: number, name: string, icon: React.ElementType, href: string}[]; +}; + +export default function MainSidebar({ data }: SidebarProps) { + const pathname = usePathname(); + + return ( + + ); +} diff --git a/frontend/src/app/components/MessageList.tsx b/frontend/src/app/_components/MessageList.tsx similarity index 75% rename from frontend/src/app/components/MessageList.tsx rename to frontend/src/app/_components/MessageList.tsx index 466e09d..0711455 100644 --- a/frontend/src/app/components/MessageList.tsx +++ b/frontend/src/app/_components/MessageList.tsx @@ -14,7 +14,7 @@ type MessageListProps = { export default function MessageList({ messages }: MessageListProps) { return ( - + {messages.map((msg) => ( msg.text && ( @@ -26,8 +26,8 @@ export default function MessageList({ messages }: MessageListProps) { transition={{ duration: 0.5 }} className={`mb-4 ${msg.sender === 'user' ? 'flex justify-end' : ''}`} > -
{msg.text}
diff --git a/frontend/src/app/_components/Sidebar.tsx b/frontend/src/app/_components/Sidebar.tsx new file mode 100644 index 0000000..7b20efd --- /dev/null +++ b/frontend/src/app/_components/Sidebar.tsx @@ -0,0 +1,130 @@ +import { api } from '@/domain/api/api'; +import { useConfig } from "@/domain/config/ConfigProvider"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { AnimatePresence, motion } from "framer-motion"; +import { X, MessageCirclePlus } from 'lucide-react'; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useMutation, useQuery, useQueryClient } from "react-query"; + +type Conversation = { + id: number; + name: string; + messages: Array<{ id: number; text: string; sender: string }>; +}; + +type SidebarProps = { + currentConversationId: number | null | string; + onAddConversation: () => void; +}; + +export default function Sidebar({ + currentConversationId, + onAddConversation +}: SidebarProps) { + const router = useRouter(); + const config = useConfig(); + + const queryClient = useQueryClient(); + + const { data: conversations = [] } = useQuery("conversations", async () => { + const response = await api.get_messages__user_id_(config.ENDPOINT); + return response.data.map((id: number, index: number) => ({ + id, + name: `Чат ${index + 1}`, + messages: [], + })); + }); + + const deleteConversationMutation = useMutation( + (conversationId: number) => + api.deleteConversation(config.ENDPOINT, conversationId), + { + onSuccess: () => { + queryClient.invalidateQueries("conversations"); + router.push("/conversations"); + }, + } + ); + + const handleDeleteConversation = (conversationId: number) => { + deleteConversationMutation.mutate(conversationId); + }; + + return ( + + ); +} diff --git a/frontend/src/app/components/Sidebar.tsx b/frontend/src/app/components/Sidebar.tsx deleted file mode 100644 index 2b8533b..0000000 --- a/frontend/src/app/components/Sidebar.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, { useRef } from 'react'; -import Link from 'next/link'; -import { motion, AnimatePresence } from 'framer-motion'; -import { Button } from "@/components/ui/button" -import { useRouter } from 'next/navigation'; - -type Conversation = { - id: number; - name: string; - messages: Array<{ id: number; text: string; sender: string }>; -}; - -type SidebarProps = { - conversations: Conversation[]; - currentConversationId: number | null; - onConversationChange: (conversationId: number) => void; - onAddConversation: () => void; - onDeleteConversation: (conversationId: number) => void; - isSidebarOpen: boolean; - onCloseSidebar: () => void; -}; - -export default function Sidebar({ conversations, currentConversationId, onConversationChange, onAddConversation, onDeleteConversation, isSidebarOpen, onCloseSidebar }: SidebarProps) { - const menuRef = useRef(null); - const router = useRouter(); - - return ( - - ); -} diff --git a/frontend/src/app/conversations/[id]/page.tsx b/frontend/src/app/conversations/[id]/page.tsx deleted file mode 100644 index 1ace06e..0000000 --- a/frontend/src/app/conversations/[id]/page.tsx +++ /dev/null @@ -1,295 +0,0 @@ -"use client"; - -import { useState, useEffect } from 'react'; -import { api } from '@/domain/api/api'; -import Sidebar from '../../components/Sidebar'; -import MessageList from '../../components/MessageList'; -import { useConfig } from '@/domain/config/ConfigProvider'; -import { Button } from "@/components/ui/button" -import { useRouter } from 'next/navigation'; - -// Тип для хранения информации о чате -type Conversation = { - id: number; - name: string; - messages: Array<{ id: number; text: string; sender: string }>; -}; - -// Основная функция компонента -export default function ConversationPage({ params }: { params: { id: string } }) { - // Состояния для хранения списка чатов, текущего чата и сообщений - const [conversations, setConversations] = useState([]); - const [currentConversationId, setCurrentConversationId] = useState(null); - const [message, setMessage] = useState(''); - const [messages, setMessages] = useState>([]); - const [isSidebarOpen, setIsSidebarOpen] = useState(false); - - const config = useConfig(); - const router = useRouter(); - - // Инициализация пользователя - useEffect(() => { - const initializeUser = async () => { - try { - const response = await api.get_messages__user_id_(config.ENDPOINT); - console.log('API response:', response); - - if (response.data && Array.isArray(response.data) && response.data.length > 0) { - const formattedConversations = response.data.map((id: number, index: number) => ({ - id: id, - name: `Чат ${index + 1}`, - messages: [] - })); - - setConversations(formattedConversations); - - const initialConversationId = parseInt(params.id); - setCurrentConversationId(initialConversationId); - - await loadMessagesForConversation(initialConversationId); - } else { - const newConversation = await handleAddConversation(); - setConversations([newConversation]); - setCurrentConversationId(newConversation.id); - router.push(`/conversations/${newConversation.id}`); - } - } catch (error) { - console.error('Ошибка при инициализации пользователя:', error); - } - }; - - initializeUser(); - }, [params.id, config.ENDPOINT, router]); - - useEffect(() => { - const loadData = async () => { - if (currentConversationId) { - await loadMessagesForConversation(currentConversationId); - } - }; - - loadData(); - }, [currentConversationId, config.ENDPOINT]); - - const handleSendMessage = async () => { - if (!message.trim()) return; - - try { - if (currentConversationId === null) { - const newConversation = await handleAddConversation(); - if (!newConversation) { - throw new Error('Не удалось создать новый чат'); - } - } - const newMessage = { - id: messages.length + 1, - text: message, - sender: 'user' - }; - - const conversationIdAtSend = currentConversationId; - - setMessages(prevMessages => [...prevMessages, newMessage]); - setConversations(prevConversations => - prevConversations.map(conversation => - conversation.id === conversationIdAtSend - ? { ...conversation, messages: [...conversation.messages, newMessage] } - : conversation - ) - ); - - const response = await api.sendMessage(config.ENDPOINT, conversationIdAtSend!, message); - - if (!response.task_id) { - throw new Error('Ошибка при отправке сообщения'); - } - - console.log('Сообщение успешно отправлено, task_id:', response.task_id); - setMessage(''); - - let botResponse; - do { - await new Promise(resolve => setTimeout(resolve, 1000)); - botResponse = await api.checkTaskResult(config.ENDPOINT, response.task_id); - } while (!botResponse.ready); - - if (botResponse.successful && botResponse.value) { - const botMessage = { - id: messages.length + 2, - text: botResponse.value.answer, - sender: 'bot' - }; - - setMessages(prevMessages => [...prevMessages, botMessage]); - setConversations(prevConversations => - prevConversations.map(conversation => - conversation.id === currentConversationId - ? { ...conversation, messages: [...conversation.messages, botMessage] } - : conversation - ) - ); - } - - } catch (error) { - console.error('Произошла ошибка:', error); - setMessage(''); - } - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - handleSendMessage(); - } - }; - - const handleChatChange = async (id: number) => { - setCurrentConversationId(id); - await loadMessagesForConversation(id); - router.push(`/conversations/${id}`); - }; - - const handleAddConversation = async () => { - try { - const response = await api.createConversation(config.ENDPOINT); - if (response.conversation_id) { - const maxChatNumber = Math.max(...conversations.map(conv => { - const match = conv.name.match(/Чат (\d+)/); - return match ? parseInt(match[1]) : 0; - }), 0); - - const newChatNumber = maxChatNumber + 1; - - const newConversation = { - id: response.conversation_id, - name: `Чат ${newChatNumber}`, - messages: [] - }; - - setConversations(prevConversations => [...prevConversations, newConversation]); - setCurrentConversationId(newConversation.id); - setMessages([]); - return newConversation; - } else { - throw new Error('Не удалось создать новый чат'); - } - } catch (error) { - console.error('Ошибка при создании чата:', error); - throw error; - } - }; - - const handleDeleteConversation = async (conversationId: number) => { - try { - await api.deleteConversation(config.ENDPOINT, conversationId); - - setConversations(prevConversations => { - const updatedConversations = prevConversations.filter(conversation => conversation.id !== conversationId); - if (updatedConversations.length === 0) { - setCurrentConversationId(null); - setMessages([]); - } else if (currentConversationId === conversationId) { - const newCurrentConversation = updatedConversations[0]; - setCurrentConversationId(newCurrentConversation.id); - setMessages(newCurrentConversation.messages); - } - return updatedConversations; - }); - console.log('Чат успешно удален'); - } catch (error) { - console.error('Ошибка при удалении чата:', error); - } - }; - - const loadMessagesForConversation = async (conversationId: number) => { - try { - const response = await api.getMessages(config.ENDPOINT, conversationId); - if (response && response.length > 0 && response[0].messages && response[0].messages.length > 0) { - const conversationMessages = response[0].messages[0][conversationId]; - if (conversationMessages && Array.isArray(conversationMessages)) { - const formattedMessages = conversationMessages.map((msg: any) => ({ - id: msg.id, - text: msg.text, - sender: msg.role === 1 ? 'bot' : 'user' - })); - setMessages(formattedMessages); - setConversations(prevConversations => - prevConversations.map(conv => - conv.id === conversationId ? { ...conv, messages: formattedMessages } : conv - ) - ); - } else { - setMessages([]); - } - } else { - setMessages([]); - } - } catch (error) { - console.error('Ошибка при загрузке сообщений:', error); - } - }; - - return ( -
- {!isSidebarOpen && ( - - )} - -
-
- handleDeleteConversation(id)} - isSidebarOpen={isSidebarOpen} - onCloseSidebar={() => setIsSidebarOpen(false)} - /> -
- -
- - -
-
- setMessage(e.target.value)} - onKeyDown={handleKeyDown} - placeholder="Введите ваш вопрос" - className="flex-grow p-2 bg-[#EFF0F3] border border-gray-300 rounded-l-2xl focus:outline-none h-full" - style={{ color: 'black' }} - /> - -
-
-
-
-
- ); -} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index de20d4d..52abc8c 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -4,6 +4,7 @@ import { Inter } from "next/font/google"; import "./globals.css"; import { ConfigProvider } from "@/domain/config/ConfigProvider"; import { headers } from "next/headers"; +import { MainLayout } from "./_components/MainLayout"; const robotoMono = Roboto_Mono({ subsets: ["latin", "cyrillic"], @@ -41,7 +42,10 @@ export default async function RootLayout({ - {children} + + {/* {children} */} + {children} + ); diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx deleted file mode 100644 index 13467ed..0000000 --- a/frontend/src/app/page.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import { useEffect } from 'react'; -import { useRouter } from 'next/navigation'; -import { api } from '@/domain/api/api'; -import { useConfig } from '@/domain/config/ConfigProvider'; - -export default function Home() { - const router = useRouter(); - const config = useConfig(); - - useEffect(() => { - const redirectToConversation = async () => { - try { - const response = await api.get_messages__user_id_(config.ENDPOINT); - if (response.data && Array.isArray(response.data) && response.data.length > 0) { - router.push(`/conversations/${response.data[0]}`); - } else { - const newConversation = await api.createConversation(config.ENDPOINT); - router.push(`/conversations/${newConversation.conversation_id}`); - } - } catch (error) { - console.error('Ошибка при перенаправлении:', error); - } - }; - - redirectToConversation(); - }, [router, config.ENDPOINT]); - - return null; -} \ No newline at end of file diff --git a/frontend/src/app/search/page.tsx b/frontend/src/app/search/page.tsx index d2eec0a..759e5c0 100644 --- a/frontend/src/app/search/page.tsx +++ b/frontend/src/app/search/page.tsx @@ -1,66 +1,53 @@ "use client"; -import React, { useState } from 'react'; -import Link from 'next/link'; +import React, { useState } from "react"; +import { Search as SearchIcon } from 'lucide-react'; export default function Search() { - const [searchQuery, setSearchQuery] = useState(''); + const [searchQuery, setSearchQuery] = useState(""); - const handleSearch = () => { - console.log('Поиск:', searchQuery); - }; + const handleSearch = () => { + console.log("Поиск:", searchQuery); + }; - return ( -
-
-
-

Поиск по документам

- - - Вернуться на главную - - -
-
-
-
-
-
- + return ( +
+ {/*
*/} +
+ {/*
*/} +

+ Поиск по документам +

- +
+
+
+
+
+ - - - -
-
-
-
+ + + +
+
- ); -} \ No newline at end of file +
+
+ ); +} diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index 0270f64..93f2de8 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -5,12 +5,12 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-all focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 active:scale-90", { variants: { variant: { default: - "bg-primary text-primary-foreground shadow hover:bg-primary/90", + "bg-primary text-primary-foreground shadow hover:bg-primary/80", destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: @@ -19,11 +19,13 @@ const buttonVariants = cva( "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", + sidebar: "flex justify-center items-center w-full bg-transparent text-balack transition-all duration-500 hover:bg-white/70" }, size: { default: "h-9 px-4 py-2", sm: "h-8 rounded-md px-3 text-xs", lg: "h-10 rounded-md px-8", + xl: "h-12 rounded-lg px-2", icon: "h-9 w-9", }, }, diff --git a/frontend/src/components/ui/drawer.tsx b/frontend/src/components/ui/drawer.tsx new file mode 100644 index 0000000..6a0ef53 --- /dev/null +++ b/frontend/src/components/ui/drawer.tsx @@ -0,0 +1,118 @@ +"use client" + +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +const Drawer = ({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps) => ( + +) +Drawer.displayName = "Drawer" + +const DrawerTrigger = DrawerPrimitive.Trigger + +const DrawerPortal = DrawerPrimitive.Portal + +const DrawerClose = DrawerPrimitive.Close + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)) +DrawerContent.displayName = "DrawerContent" + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerHeader.displayName = "DrawerHeader" + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerFooter.displayName = "DrawerFooter" + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerTitle.displayName = DrawerPrimitive.Title.displayName + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerDescription.displayName = DrawerPrimitive.Description.displayName + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/frontend/src/components/ui/sheet.tsx b/frontend/src/components/ui/sheet.tsx new file mode 100644 index 0000000..417e7e1 --- /dev/null +++ b/frontend/src/components/ui/sheet.tsx @@ -0,0 +1,140 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { Cross2Icon } from "@radix-ui/react-icons" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + + + Close + + {children} + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..9e74821 --- /dev/null +++ b/frontend/src/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/services/ml/app/models/qa.py b/services/ml/app/models/qa.py new file mode 100644 index 0000000..3080d00 --- /dev/null +++ b/services/ml/app/models/qa.py @@ -0,0 +1,9 @@ +from langchain_core.pydantic_v1 import BaseModel, Field, validator + +class InputChat(BaseModel): + """Input for the chat endpoint.""" + question: str = Field(..., description="The query to retrieve relevant documents.") + +class OutputChat(BaseModel): + """Output for the chat endpoint.""" + result: str = Field(..., description="The output containing the result.") diff --git a/services/ml/app/routes/file.py b/services/ml/app/routes/file.py new file mode 100644 index 0000000..ed8b7ff --- /dev/null +++ b/services/ml/app/routes/file.py @@ -0,0 +1,29 @@ +import base64 + +from fastapi import FastAPI +from langchain_community.document_loaders.parsers.pdf import PDFMinerParser +from langchain_core.document_loaders import Blob +from langchain_core.runnables import RunnableLambda +from langchain_core.pydantic_v1 import BaseModel, Field, validator + +from langserve import CustomUserType, add_routes + +class FileProcessingRequest(CustomUserType): + """Request including a base64 encoded file.""" + + # The extra field is used to specify a widget for the playground UI. + file: str = Field(..., extra={"widget": {"type": "base64file"}}) + + +def _process_file(request: FileProcessingRequest) -> str: + """Extract the text from all pages of the PDF.""" + content = base64.b64decode(request.file.encode("utf-8")) + blob = Blob(data=content) + documents = list(PDFMinerParser().lazy_parse(blob)) + + processed_content = [] + for i, doc in enumerate(documents, 1): + page_content = doc.page_content + processed_content.append(f"Page {i}:\n{page_content}\n") + + return "\n".join(processed_content) diff --git a/services/ml/app/routes/qa.py b/services/ml/app/routes/qa.py new file mode 100644 index 0000000..e69de29 diff --git a/services/ml/app/server.py b/services/ml/app/server.py index 83c6a13..b8ef7fa 100644 --- a/services/ml/app/server.py +++ b/services/ml/app/server.py @@ -1,11 +1,13 @@ +from app.models.qa import InputChat, OutputChat +from app.routes.file import FileProcessingRequest, _process_file from fastapi import FastAPI from langchain.schema.runnable import RunnableParallel, RunnablePassthrough from langserve import add_routes from packages.llm import LLMInstance from packages.prompts import prompt from packages.retriever import Retriever -from langchain_core.pydantic_v1 import BaseModel, Field, validator from langchain_core.output_parsers import StrOutputParser +from langchain_core.runnables import RunnableLambda from .config import config @@ -13,14 +15,6 @@ retriever_instance = Retriever(llm_instance.get_embeddings(), host=config.QDRANT_HOST, collection_name=config.QDRANT_COLLECTION_NAME, api_key=config.QDRANT_API_KEY) -class InputChat(BaseModel): - """Input for the chat endpoint.""" - question: str = Field(..., description="The query to retrieve relevant documents.") - -class OutputChat(BaseModel): - """Output for the chat endpoint.""" - result: str = Field(..., description="The output containing the result.") - # Modify the qa_chain definition qa_chain = ( RunnableParallel( @@ -42,6 +36,13 @@ class OutputChat(BaseModel): # Add routes for the QA chain instead of just the retriever add_routes(app, path="/llm", runnable=llm_instance.get_llm()) add_routes(app, qa_chain) +add_routes( + app, + RunnableLambda(_process_file).with_types(input_type=FileProcessingRequest), + config_keys=["configurable"], + path="/pdf", +) + if __name__ == "__main__": import uvicorn diff --git a/services/ml/poetry.lock b/services/ml/poetry.lock index ad06ecf..53be3b7 100644 --- a/services/ml/poetry.lock +++ b/services/ml/poetry.lock @@ -2257,6 +2257,26 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pdfminer-six" +version = "20240706" +description = "PDF parser and analyzer" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pdfminer.six-20240706-py3-none-any.whl", hash = "sha256:f4f70e74174b4b3542fcb8406a210b6e2e27cd0f0b5fd04534a8cc0d8951e38c"}, + {file = "pdfminer.six-20240706.tar.gz", hash = "sha256:c631a46d5da957a9ffe4460c5dce21e8431dabb615fee5f9f4400603a58d95a6"}, +] + +[package.dependencies] +charset-normalizer = ">=2.0.0" +cryptography = ">=36.0.0" + +[package.extras] +dev = ["atheris", "black", "mypy (==0.931)", "nox", "pytest"] +docs = ["sphinx", "sphinx-argparse"] +image = ["Pillow"] + [[package]] name = "pillow" version = "10.4.0" @@ -4018,4 +4038,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.11, <3.13" -content-hash = "f9422f93739794c9f454357df25c1e6c08f9cd16f57206186ed1eec38ee65d4d" +content-hash = "a13235581203598a17305f4ba008f1e8a4734e1482b081fcbeda474e81466c24" diff --git a/services/ml/pyproject.toml b/services/ml/pyproject.toml index 9adf278..4716b0f 100644 --- a/services/ml/pyproject.toml +++ b/services/ml/pyproject.toml @@ -20,6 +20,7 @@ yandexcloud = "^0.316.0" langchain-qdrant = "^0.1.3" fastembed = "^0.3.6" langchain-openai = "^0.1.23" +pdfminer-six = "^20240706" [tool.poetry.group.dev.dependencies] langchain-cli = ">=0.0.15"