Spaces:
Paused
Paused
Soham Waghmare
commited on
Commit
·
780df80
1
Parent(s):
2d040a7
feat: mvp frontend
Browse files- .gitignore +4 -0
- KnowledgeNet.excalidraw +2193 -20
- backend/.gitignore +0 -1
- backend/app.py +10 -15
- backend/knet.py +131 -90
- backend/output.json +0 -0
- frontend/bun.lock +0 -0
- frontend/components.json +21 -0
- frontend/package.json +27 -3
- frontend/src/app/fonts/GeistMonoVF.woff +0 -0
- frontend/src/app/fonts/GeistVF.woff +0 -0
- frontend/src/app/globals.css +67 -15
- frontend/src/app/layout.tsx +9 -24
- frontend/src/app/page.tsx +3 -3
- frontend/src/components/ChatHistory.tsx +63 -0
- frontend/src/components/ChatInterface.tsx +274 -0
- frontend/src/components/Message.tsx +97 -0
- frontend/src/components/MessageInput.tsx +75 -0
- frontend/src/components/ResearchControls.tsx +73 -0
- frontend/src/components/theme-provider.tsx +8 -0
- frontend/src/components/ui/ChatLayout.tsx +85 -0
- frontend/src/components/ui/ConversationList.tsx +52 -0
- frontend/src/components/ui/ThemeToggle.tsx +34 -0
- frontend/src/components/ui/alert.tsx +27 -0
- frontend/src/components/ui/avatar.tsx +50 -0
- frontend/src/components/ui/button.tsx +57 -0
- frontend/src/components/ui/card.tsx +76 -0
- frontend/src/components/ui/checkbox.tsx +30 -0
- frontend/src/components/ui/dialog.tsx +122 -0
- frontend/src/components/ui/dropdown-menu.tsx +200 -0
- frontend/src/components/ui/input.tsx +22 -0
- frontend/src/components/ui/label.tsx +26 -0
- frontend/src/components/ui/resizable.tsx +45 -0
- frontend/src/components/ui/scroll-area.tsx +24 -0
- frontend/src/components/ui/select.tsx +159 -0
- frontend/src/components/ui/separator.tsx +31 -0
- frontend/src/components/ui/sheet.tsx +140 -0
- frontend/src/components/ui/tabs.tsx +22 -0
- frontend/src/components/ui/textarea.tsx +95 -0
- frontend/src/components/ui/tooltip.tsx +32 -0
- frontend/src/lib/socket.ts +30 -0
- frontend/src/lib/types.ts +60 -0
- frontend/src/lib/utils.ts +6 -0
- frontend/tailwind.config.ts +52 -8
.gitignore
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
# Flask ignore files
|
| 2 |
backend/__pycache__/
|
| 3 |
backend/*.pyc
|
|
@@ -6,6 +8,8 @@ backend/*.pyd
|
|
| 6 |
backend/*.pyo
|
| 7 |
backend/.venv/
|
| 8 |
backend/.env*
|
|
|
|
|
|
|
| 9 |
|
| 10 |
# Next.js ignore files
|
| 11 |
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
|
|
| 1 |
+
**/.env
|
| 2 |
+
|
| 3 |
# Flask ignore files
|
| 4 |
backend/__pycache__/
|
| 5 |
backend/*.pyc
|
|
|
|
| 8 |
backend/*.pyo
|
| 9 |
backend/.venv/
|
| 10 |
backend/.env*
|
| 11 |
+
backend/downloads/*
|
| 12 |
+
backend/output.json
|
| 13 |
|
| 14 |
# Next.js ignore files
|
| 15 |
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
KnowledgeNet.excalidraw
CHANGED
|
@@ -3990,7 +3990,7 @@
|
|
| 3990 |
"version": 92,
|
| 3991 |
"versionNonce": 1221535793,
|
| 3992 |
"isDeleted": false,
|
| 3993 |
-
"boundElements":
|
| 3994 |
"updated": 1740410117898,
|
| 3995 |
"link": null,
|
| 3996 |
"locked": false,
|
|
@@ -4005,10 +4005,10 @@
|
|
| 4005 |
"lineHeight": 1.25
|
| 4006 |
},
|
| 4007 |
{
|
| 4008 |
-
"id": "
|
| 4009 |
"type": "text",
|
| 4010 |
-
"x":
|
| 4011 |
-
"y": -
|
| 4012 |
"width": 727.5238647460938,
|
| 4013 |
"height": 105,
|
| 4014 |
"angle": 0,
|
|
@@ -4023,12 +4023,12 @@
|
|
| 4023 |
"frameId": null,
|
| 4024 |
"index": "b0I",
|
| 4025 |
"roundness": null,
|
| 4026 |
-
"seed":
|
| 4027 |
-
"version":
|
| 4028 |
-
"versionNonce":
|
| 4029 |
"isDeleted": false,
|
| 4030 |
"boundElements": [],
|
| 4031 |
-
"updated":
|
| 4032 |
"link": null,
|
| 4033 |
"locked": false,
|
| 4034 |
"text": "Use Case Diagram",
|
|
@@ -4111,7 +4111,7 @@
|
|
| 4111 |
"version": 166,
|
| 4112 |
"versionNonce": 854836337,
|
| 4113 |
"isDeleted": false,
|
| 4114 |
-
"boundElements":
|
| 4115 |
"updated": 1740410330502,
|
| 4116 |
"link": null,
|
| 4117 |
"locked": false,
|
|
@@ -4187,7 +4187,7 @@
|
|
| 4187 |
"version": 24,
|
| 4188 |
"versionNonce": 1896009777,
|
| 4189 |
"isDeleted": false,
|
| 4190 |
-
"boundElements":
|
| 4191 |
"updated": 1740410209932,
|
| 4192 |
"link": null,
|
| 4193 |
"locked": false,
|
|
@@ -4279,7 +4279,7 @@
|
|
| 4279 |
"version": 121,
|
| 4280 |
"versionNonce": 628335089,
|
| 4281 |
"isDeleted": false,
|
| 4282 |
-
"boundElements":
|
| 4283 |
"updated": 1740410219593,
|
| 4284 |
"link": null,
|
| 4285 |
"locked": false,
|
|
@@ -4355,7 +4355,7 @@
|
|
| 4355 |
"version": 261,
|
| 4356 |
"versionNonce": 481340991,
|
| 4357 |
"isDeleted": false,
|
| 4358 |
-
"boundElements":
|
| 4359 |
"updated": 1740410234474,
|
| 4360 |
"link": null,
|
| 4361 |
"locked": false,
|
|
@@ -4443,7 +4443,7 @@
|
|
| 4443 |
"version": 295,
|
| 4444 |
"versionNonce": 86343071,
|
| 4445 |
"isDeleted": false,
|
| 4446 |
-
"boundElements":
|
| 4447 |
"updated": 1740410412315,
|
| 4448 |
"link": null,
|
| 4449 |
"locked": false,
|
|
@@ -4480,7 +4480,7 @@
|
|
| 4480 |
"version": 85,
|
| 4481 |
"versionNonce": 745306257,
|
| 4482 |
"isDeleted": false,
|
| 4483 |
-
"boundElements":
|
| 4484 |
"updated": 1740410342893,
|
| 4485 |
"link": null,
|
| 4486 |
"locked": false,
|
|
@@ -4551,7 +4551,7 @@
|
|
| 4551 |
"version": 64,
|
| 4552 |
"versionNonce": 1485153297,
|
| 4553 |
"isDeleted": false,
|
| 4554 |
-
"boundElements":
|
| 4555 |
"updated": 1740410346276,
|
| 4556 |
"link": null,
|
| 4557 |
"locked": false,
|
|
@@ -4614,7 +4614,7 @@
|
|
| 4614 |
"version": 115,
|
| 4615 |
"versionNonce": 1416571167,
|
| 4616 |
"isDeleted": false,
|
| 4617 |
-
"boundElements":
|
| 4618 |
"updated": 1740410351698,
|
| 4619 |
"link": null,
|
| 4620 |
"locked": false,
|
|
@@ -4685,7 +4685,7 @@
|
|
| 4685 |
"version": 113,
|
| 4686 |
"versionNonce": 694937969,
|
| 4687 |
"isDeleted": false,
|
| 4688 |
-
"boundElements":
|
| 4689 |
"updated": 1740410359339,
|
| 4690 |
"link": null,
|
| 4691 |
"locked": false,
|
|
@@ -4756,7 +4756,7 @@
|
|
| 4756 |
"version": 99,
|
| 4757 |
"versionNonce": 1591321329,
|
| 4758 |
"isDeleted": false,
|
| 4759 |
-
"boundElements":
|
| 4760 |
"updated": 1740410373406,
|
| 4761 |
"link": null,
|
| 4762 |
"locked": false,
|
|
@@ -4827,7 +4827,7 @@
|
|
| 4827 |
"version": 105,
|
| 4828 |
"versionNonce": 1997100959,
|
| 4829 |
"isDeleted": false,
|
| 4830 |
-
"boundElements":
|
| 4831 |
"updated": 1740410388128,
|
| 4832 |
"link": null,
|
| 4833 |
"locked": false,
|
|
@@ -4898,7 +4898,7 @@
|
|
| 4898 |
"version": 44,
|
| 4899 |
"versionNonce": 1743458015,
|
| 4900 |
"isDeleted": false,
|
| 4901 |
-
"boundElements":
|
| 4902 |
"updated": 1740410394139,
|
| 4903 |
"link": null,
|
| 4904 |
"locked": false,
|
|
@@ -4945,6 +4945,2179 @@
|
|
| 4945 |
"fixedSegments": null,
|
| 4946 |
"startIsSpecial": null,
|
| 4947 |
"endIsSpecial": null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4948 |
}
|
| 4949 |
],
|
| 4950 |
"appState": {
|
|
|
|
| 3990 |
"version": 92,
|
| 3991 |
"versionNonce": 1221535793,
|
| 3992 |
"isDeleted": false,
|
| 3993 |
+
"boundElements": [],
|
| 3994 |
"updated": 1740410117898,
|
| 3995 |
"link": null,
|
| 3996 |
"locked": false,
|
|
|
|
| 4005 |
"lineHeight": 1.25
|
| 4006 |
},
|
| 4007 |
{
|
| 4008 |
+
"id": "Hd85L_o2whuQ37yZPmET7",
|
| 4009 |
"type": "text",
|
| 4010 |
+
"x": 3834.7474993968267,
|
| 4011 |
+
"y": -385.5833329999999,
|
| 4012 |
"width": 727.5238647460938,
|
| 4013 |
"height": 105,
|
| 4014 |
"angle": 0,
|
|
|
|
| 4023 |
"frameId": null,
|
| 4024 |
"index": "b0I",
|
| 4025 |
"roundness": null,
|
| 4026 |
+
"seed": 1112393545,
|
| 4027 |
+
"version": 573,
|
| 4028 |
+
"versionNonce": 1303852039,
|
| 4029 |
"isDeleted": false,
|
| 4030 |
"boundElements": [],
|
| 4031 |
+
"updated": 1741576813086,
|
| 4032 |
"link": null,
|
| 4033 |
"locked": false,
|
| 4034 |
"text": "Use Case Diagram",
|
|
|
|
| 4111 |
"version": 166,
|
| 4112 |
"versionNonce": 854836337,
|
| 4113 |
"isDeleted": false,
|
| 4114 |
+
"boundElements": [],
|
| 4115 |
"updated": 1740410330502,
|
| 4116 |
"link": null,
|
| 4117 |
"locked": false,
|
|
|
|
| 4187 |
"version": 24,
|
| 4188 |
"versionNonce": 1896009777,
|
| 4189 |
"isDeleted": false,
|
| 4190 |
+
"boundElements": [],
|
| 4191 |
"updated": 1740410209932,
|
| 4192 |
"link": null,
|
| 4193 |
"locked": false,
|
|
|
|
| 4279 |
"version": 121,
|
| 4280 |
"versionNonce": 628335089,
|
| 4281 |
"isDeleted": false,
|
| 4282 |
+
"boundElements": [],
|
| 4283 |
"updated": 1740410219593,
|
| 4284 |
"link": null,
|
| 4285 |
"locked": false,
|
|
|
|
| 4355 |
"version": 261,
|
| 4356 |
"versionNonce": 481340991,
|
| 4357 |
"isDeleted": false,
|
| 4358 |
+
"boundElements": [],
|
| 4359 |
"updated": 1740410234474,
|
| 4360 |
"link": null,
|
| 4361 |
"locked": false,
|
|
|
|
| 4443 |
"version": 295,
|
| 4444 |
"versionNonce": 86343071,
|
| 4445 |
"isDeleted": false,
|
| 4446 |
+
"boundElements": [],
|
| 4447 |
"updated": 1740410412315,
|
| 4448 |
"link": null,
|
| 4449 |
"locked": false,
|
|
|
|
| 4480 |
"version": 85,
|
| 4481 |
"versionNonce": 745306257,
|
| 4482 |
"isDeleted": false,
|
| 4483 |
+
"boundElements": [],
|
| 4484 |
"updated": 1740410342893,
|
| 4485 |
"link": null,
|
| 4486 |
"locked": false,
|
|
|
|
| 4551 |
"version": 64,
|
| 4552 |
"versionNonce": 1485153297,
|
| 4553 |
"isDeleted": false,
|
| 4554 |
+
"boundElements": [],
|
| 4555 |
"updated": 1740410346276,
|
| 4556 |
"link": null,
|
| 4557 |
"locked": false,
|
|
|
|
| 4614 |
"version": 115,
|
| 4615 |
"versionNonce": 1416571167,
|
| 4616 |
"isDeleted": false,
|
| 4617 |
+
"boundElements": [],
|
| 4618 |
"updated": 1740410351698,
|
| 4619 |
"link": null,
|
| 4620 |
"locked": false,
|
|
|
|
| 4685 |
"version": 113,
|
| 4686 |
"versionNonce": 694937969,
|
| 4687 |
"isDeleted": false,
|
| 4688 |
+
"boundElements": [],
|
| 4689 |
"updated": 1740410359339,
|
| 4690 |
"link": null,
|
| 4691 |
"locked": false,
|
|
|
|
| 4756 |
"version": 99,
|
| 4757 |
"versionNonce": 1591321329,
|
| 4758 |
"isDeleted": false,
|
| 4759 |
+
"boundElements": [],
|
| 4760 |
"updated": 1740410373406,
|
| 4761 |
"link": null,
|
| 4762 |
"locked": false,
|
|
|
|
| 4827 |
"version": 105,
|
| 4828 |
"versionNonce": 1997100959,
|
| 4829 |
"isDeleted": false,
|
| 4830 |
+
"boundElements": [],
|
| 4831 |
"updated": 1740410388128,
|
| 4832 |
"link": null,
|
| 4833 |
"locked": false,
|
|
|
|
| 4898 |
"version": 44,
|
| 4899 |
"versionNonce": 1743458015,
|
| 4900 |
"isDeleted": false,
|
| 4901 |
+
"boundElements": [],
|
| 4902 |
"updated": 1740410394139,
|
| 4903 |
"link": null,
|
| 4904 |
"locked": false,
|
|
|
|
| 4945 |
"fixedSegments": null,
|
| 4946 |
"startIsSpecial": null,
|
| 4947 |
"endIsSpecial": null
|
| 4948 |
+
},
|
| 4949 |
+
{
|
| 4950 |
+
"id": "_DXsWPZLu9Lg_N3cYUbAP",
|
| 4951 |
+
"type": "rectangle",
|
| 4952 |
+
"x": 6143.606589566099,
|
| 4953 |
+
"y": -106.7448757200093,
|
| 4954 |
+
"width": 1541.4296875,
|
| 4955 |
+
"height": 768.1015948763029,
|
| 4956 |
+
"angle": 0,
|
| 4957 |
+
"strokeColor": "#1e1e1e",
|
| 4958 |
+
"backgroundColor": "transparent",
|
| 4959 |
+
"fillStyle": "solid",
|
| 4960 |
+
"strokeWidth": 2,
|
| 4961 |
+
"strokeStyle": "solid",
|
| 4962 |
+
"roughness": 1,
|
| 4963 |
+
"opacity": 100,
|
| 4964 |
+
"groupIds": [
|
| 4965 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 4966 |
+
],
|
| 4967 |
+
"frameId": null,
|
| 4968 |
+
"index": "b1c",
|
| 4969 |
+
"roundness": null,
|
| 4970 |
+
"seed": 2136851817,
|
| 4971 |
+
"version": 92,
|
| 4972 |
+
"versionNonce": 750499017,
|
| 4973 |
+
"isDeleted": false,
|
| 4974 |
+
"boundElements": [],
|
| 4975 |
+
"updated": 1741576957668,
|
| 4976 |
+
"link": null,
|
| 4977 |
+
"locked": false
|
| 4978 |
+
},
|
| 4979 |
+
{
|
| 4980 |
+
"id": "hh-vJczGSvSCpkhjc-ZFd",
|
| 4981 |
+
"type": "rectangle",
|
| 4982 |
+
"x": 6884.735501342112,
|
| 4983 |
+
"y": 712.7366477728008,
|
| 4984 |
+
"width": 64.6875,
|
| 4985 |
+
"height": 44,
|
| 4986 |
+
"angle": 0,
|
| 4987 |
+
"strokeColor": "#1e1e1e",
|
| 4988 |
+
"backgroundColor": "transparent",
|
| 4989 |
+
"fillStyle": "solid",
|
| 4990 |
+
"strokeWidth": 2,
|
| 4991 |
+
"strokeStyle": "solid",
|
| 4992 |
+
"roughness": 1,
|
| 4993 |
+
"opacity": 100,
|
| 4994 |
+
"groupIds": [],
|
| 4995 |
+
"frameId": null,
|
| 4996 |
+
"index": "b1d",
|
| 4997 |
+
"roundness": null,
|
| 4998 |
+
"seed": 808661065,
|
| 4999 |
+
"version": 154,
|
| 5000 |
+
"versionNonce": 1545689785,
|
| 5001 |
+
"isDeleted": false,
|
| 5002 |
+
"boundElements": [
|
| 5003 |
+
{
|
| 5004 |
+
"type": "text",
|
| 5005 |
+
"id": "zSH_0RVoAH6FqvlC-qOEe"
|
| 5006 |
+
},
|
| 5007 |
+
{
|
| 5008 |
+
"id": "uuVJhiL1l0abY9oNZiD10",
|
| 5009 |
+
"type": "arrow"
|
| 5010 |
+
},
|
| 5011 |
+
{
|
| 5012 |
+
"id": "RG4hRN73PDSPg9TvjEABI",
|
| 5013 |
+
"type": "arrow"
|
| 5014 |
+
}
|
| 5015 |
+
],
|
| 5016 |
+
"updated": 1741577112903,
|
| 5017 |
+
"link": null,
|
| 5018 |
+
"locked": false
|
| 5019 |
+
},
|
| 5020 |
+
{
|
| 5021 |
+
"id": "5rQwEFQ0OLt7lCCh6ROrl",
|
| 5022 |
+
"type": "rectangle",
|
| 5023 |
+
"x": 6816.856929026729,
|
| 5024 |
+
"y": -77.24788666946003,
|
| 5025 |
+
"width": 245.24902833716055,
|
| 5026 |
+
"height": 51.91470072896671,
|
| 5027 |
+
"angle": 0,
|
| 5028 |
+
"strokeColor": "#1e1e1e",
|
| 5029 |
+
"backgroundColor": "transparent",
|
| 5030 |
+
"fillStyle": "solid",
|
| 5031 |
+
"strokeWidth": 2,
|
| 5032 |
+
"strokeStyle": "solid",
|
| 5033 |
+
"roughness": 1,
|
| 5034 |
+
"opacity": 100,
|
| 5035 |
+
"groupIds": [
|
| 5036 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5037 |
+
],
|
| 5038 |
+
"frameId": null,
|
| 5039 |
+
"index": "b1e",
|
| 5040 |
+
"roundness": null,
|
| 5041 |
+
"seed": 1020804905,
|
| 5042 |
+
"version": 101,
|
| 5043 |
+
"versionNonce": 692196601,
|
| 5044 |
+
"isDeleted": false,
|
| 5045 |
+
"boundElements": [
|
| 5046 |
+
{
|
| 5047 |
+
"type": "text",
|
| 5048 |
+
"id": "YN7Z_MhrnVfus-HHFPTk1"
|
| 5049 |
+
},
|
| 5050 |
+
{
|
| 5051 |
+
"id": "Gr_IecqseQ5STfEBPzVML",
|
| 5052 |
+
"type": "arrow"
|
| 5053 |
+
},
|
| 5054 |
+
{
|
| 5055 |
+
"id": "uuVJhiL1l0abY9oNZiD10",
|
| 5056 |
+
"type": "arrow"
|
| 5057 |
+
}
|
| 5058 |
+
],
|
| 5059 |
+
"updated": 1741577060675,
|
| 5060 |
+
"link": null,
|
| 5061 |
+
"locked": false
|
| 5062 |
+
},
|
| 5063 |
+
{
|
| 5064 |
+
"id": "lcNOx1PJ1LukZ3eN5WcGs",
|
| 5065 |
+
"type": "rectangle",
|
| 5066 |
+
"x": 6658.135474507539,
|
| 5067 |
+
"y": 33.66079216060521,
|
| 5068 |
+
"width": 184.00590482095765,
|
| 5069 |
+
"height": 51.91470072896671,
|
| 5070 |
+
"angle": 0,
|
| 5071 |
+
"strokeColor": "#1e1e1e",
|
| 5072 |
+
"backgroundColor": "transparent",
|
| 5073 |
+
"fillStyle": "solid",
|
| 5074 |
+
"strokeWidth": 2,
|
| 5075 |
+
"strokeStyle": "solid",
|
| 5076 |
+
"roughness": 1,
|
| 5077 |
+
"opacity": 100,
|
| 5078 |
+
"groupIds": [
|
| 5079 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5080 |
+
],
|
| 5081 |
+
"frameId": null,
|
| 5082 |
+
"index": "b1f",
|
| 5083 |
+
"roundness": null,
|
| 5084 |
+
"seed": 1404596745,
|
| 5085 |
+
"version": 104,
|
| 5086 |
+
"versionNonce": 210016041,
|
| 5087 |
+
"isDeleted": false,
|
| 5088 |
+
"boundElements": [
|
| 5089 |
+
{
|
| 5090 |
+
"type": "text",
|
| 5091 |
+
"id": "A45yyUfo1lPMEY8TmwNAv"
|
| 5092 |
+
},
|
| 5093 |
+
{
|
| 5094 |
+
"id": "Gr_IecqseQ5STfEBPzVML",
|
| 5095 |
+
"type": "arrow"
|
| 5096 |
+
},
|
| 5097 |
+
{
|
| 5098 |
+
"id": "jXazvlSAB2utF1woHHpxJ",
|
| 5099 |
+
"type": "arrow"
|
| 5100 |
+
},
|
| 5101 |
+
{
|
| 5102 |
+
"id": "3raa-F9gTwZ7ZG2sCiVMU",
|
| 5103 |
+
"type": "arrow"
|
| 5104 |
+
}
|
| 5105 |
+
],
|
| 5106 |
+
"updated": 1741576957669,
|
| 5107 |
+
"link": null,
|
| 5108 |
+
"locked": false
|
| 5109 |
+
},
|
| 5110 |
+
{
|
| 5111 |
+
"id": "YPquWrf902Bx_VzPe5ZKo",
|
| 5112 |
+
"type": "rectangle",
|
| 5113 |
+
"x": 6733.988825412842,
|
| 5114 |
+
"y": 144.56947099067045,
|
| 5115 |
+
"width": 308.5753767050585,
|
| 5116 |
+
"height": 51.91470072896671,
|
| 5117 |
+
"angle": 0,
|
| 5118 |
+
"strokeColor": "#1e1e1e",
|
| 5119 |
+
"backgroundColor": "transparent",
|
| 5120 |
+
"fillStyle": "solid",
|
| 5121 |
+
"strokeWidth": 2,
|
| 5122 |
+
"strokeStyle": "solid",
|
| 5123 |
+
"roughness": 1,
|
| 5124 |
+
"opacity": 100,
|
| 5125 |
+
"groupIds": [
|
| 5126 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5127 |
+
],
|
| 5128 |
+
"frameId": null,
|
| 5129 |
+
"index": "b1g",
|
| 5130 |
+
"roundness": null,
|
| 5131 |
+
"seed": 1238321385,
|
| 5132 |
+
"version": 108,
|
| 5133 |
+
"versionNonce": 1116994953,
|
| 5134 |
+
"isDeleted": false,
|
| 5135 |
+
"boundElements": [
|
| 5136 |
+
{
|
| 5137 |
+
"type": "text",
|
| 5138 |
+
"id": "fXbbtx_NcVZBC_fsO1Uff"
|
| 5139 |
+
},
|
| 5140 |
+
{
|
| 5141 |
+
"id": "jXazvlSAB2utF1woHHpxJ",
|
| 5142 |
+
"type": "arrow"
|
| 5143 |
+
}
|
| 5144 |
+
],
|
| 5145 |
+
"updated": 1741576957669,
|
| 5146 |
+
"link": null,
|
| 5147 |
+
"locked": false
|
| 5148 |
+
},
|
| 5149 |
+
{
|
| 5150 |
+
"id": "O4ynYwner8EHSv-cUHyx0",
|
| 5151 |
+
"type": "rectangle",
|
| 5152 |
+
"x": 6653.296124741433,
|
| 5153 |
+
"y": 289.69465711937283,
|
| 5154 |
+
"width": 469.9607780478762,
|
| 5155 |
+
"height": 51.91470072896671,
|
| 5156 |
+
"angle": 0,
|
| 5157 |
+
"strokeColor": "#1e1e1e",
|
| 5158 |
+
"backgroundColor": "transparent",
|
| 5159 |
+
"fillStyle": "solid",
|
| 5160 |
+
"strokeWidth": 2,
|
| 5161 |
+
"strokeStyle": "solid",
|
| 5162 |
+
"roughness": 1,
|
| 5163 |
+
"opacity": 100,
|
| 5164 |
+
"groupIds": [
|
| 5165 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5166 |
+
],
|
| 5167 |
+
"frameId": null,
|
| 5168 |
+
"index": "b1h",
|
| 5169 |
+
"roundness": null,
|
| 5170 |
+
"seed": 1218986953,
|
| 5171 |
+
"version": 112,
|
| 5172 |
+
"versionNonce": 199055049,
|
| 5173 |
+
"isDeleted": false,
|
| 5174 |
+
"boundElements": [
|
| 5175 |
+
{
|
| 5176 |
+
"type": "text",
|
| 5177 |
+
"id": "V3UN0NpFvZDTX16OkuBNR"
|
| 5178 |
+
},
|
| 5179 |
+
{
|
| 5180 |
+
"id": "hktnzSdCZsg2XG9eDy1n-",
|
| 5181 |
+
"type": "arrow"
|
| 5182 |
+
},
|
| 5183 |
+
{
|
| 5184 |
+
"id": "fMbl9KWZiggWCvF5xaMRT",
|
| 5185 |
+
"type": "arrow"
|
| 5186 |
+
},
|
| 5187 |
+
{
|
| 5188 |
+
"id": "QTMS_iw6t6ynVYi-LOM0h",
|
| 5189 |
+
"type": "arrow"
|
| 5190 |
+
},
|
| 5191 |
+
{
|
| 5192 |
+
"id": "FCEWIaj7DywBOG0ukJrRU",
|
| 5193 |
+
"type": "arrow"
|
| 5194 |
+
},
|
| 5195 |
+
{
|
| 5196 |
+
"id": "vZM--SBjM4zyowOgs3hDv",
|
| 5197 |
+
"type": "arrow"
|
| 5198 |
+
}
|
| 5199 |
+
],
|
| 5200 |
+
"updated": 1741576992554,
|
| 5201 |
+
"link": null,
|
| 5202 |
+
"locked": false
|
| 5203 |
+
},
|
| 5204 |
+
{
|
| 5205 |
+
"id": "d-Obq1jyhqw7abPHd6_i5",
|
| 5206 |
+
"type": "rectangle",
|
| 5207 |
+
"x": 6233.346569847856,
|
| 5208 |
+
"y": 434.81984324807524,
|
| 5209 |
+
"width": 220.0659739352541,
|
| 5210 |
+
"height": 51.91470072896671,
|
| 5211 |
+
"angle": 0,
|
| 5212 |
+
"strokeColor": "#1e1e1e",
|
| 5213 |
+
"backgroundColor": "transparent",
|
| 5214 |
+
"fillStyle": "solid",
|
| 5215 |
+
"strokeWidth": 2,
|
| 5216 |
+
"strokeStyle": "solid",
|
| 5217 |
+
"roughness": 1,
|
| 5218 |
+
"opacity": 100,
|
| 5219 |
+
"groupIds": [
|
| 5220 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5221 |
+
],
|
| 5222 |
+
"frameId": null,
|
| 5223 |
+
"index": "b1i",
|
| 5224 |
+
"roundness": null,
|
| 5225 |
+
"seed": 1012851369,
|
| 5226 |
+
"version": 113,
|
| 5227 |
+
"versionNonce": 1896293513,
|
| 5228 |
+
"isDeleted": false,
|
| 5229 |
+
"boundElements": [
|
| 5230 |
+
{
|
| 5231 |
+
"type": "text",
|
| 5232 |
+
"id": "lKO9DytZCWjoMyMUO9WnK"
|
| 5233 |
+
},
|
| 5234 |
+
{
|
| 5235 |
+
"id": "3raa-F9gTwZ7ZG2sCiVMU",
|
| 5236 |
+
"type": "arrow"
|
| 5237 |
+
},
|
| 5238 |
+
{
|
| 5239 |
+
"id": "fMbl9KWZiggWCvF5xaMRT",
|
| 5240 |
+
"type": "arrow"
|
| 5241 |
+
}
|
| 5242 |
+
],
|
| 5243 |
+
"updated": 1741576957670,
|
| 5244 |
+
"link": null,
|
| 5245 |
+
"locked": false
|
| 5246 |
+
},
|
| 5247 |
+
{
|
| 5248 |
+
"id": "SLQjJ54OvcwHKgE0saBYW",
|
| 5249 |
+
"type": "rectangle",
|
| 5250 |
+
"x": 6553.568648323342,
|
| 5251 |
+
"y": 434.81984324807524,
|
| 5252 |
+
"width": 298.06707435580034,
|
| 5253 |
+
"height": 51.91470072896671,
|
| 5254 |
+
"angle": 0,
|
| 5255 |
+
"strokeColor": "#1e1e1e",
|
| 5256 |
+
"backgroundColor": "transparent",
|
| 5257 |
+
"fillStyle": "solid",
|
| 5258 |
+
"strokeWidth": 2,
|
| 5259 |
+
"strokeStyle": "solid",
|
| 5260 |
+
"roughness": 1,
|
| 5261 |
+
"opacity": 100,
|
| 5262 |
+
"groupIds": [
|
| 5263 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5264 |
+
],
|
| 5265 |
+
"frameId": null,
|
| 5266 |
+
"index": "b1j",
|
| 5267 |
+
"roundness": null,
|
| 5268 |
+
"seed": 27174281,
|
| 5269 |
+
"version": 106,
|
| 5270 |
+
"versionNonce": 448796681,
|
| 5271 |
+
"isDeleted": false,
|
| 5272 |
+
"boundElements": [
|
| 5273 |
+
{
|
| 5274 |
+
"type": "text",
|
| 5275 |
+
"id": "ZIBlXFowG3rpzs7ZAmiZZ"
|
| 5276 |
+
},
|
| 5277 |
+
{
|
| 5278 |
+
"id": "QTMS_iw6t6ynVYi-LOM0h",
|
| 5279 |
+
"type": "arrow"
|
| 5280 |
+
}
|
| 5281 |
+
],
|
| 5282 |
+
"updated": 1741576957670,
|
| 5283 |
+
"link": null,
|
| 5284 |
+
"locked": false
|
| 5285 |
+
},
|
| 5286 |
+
{
|
| 5287 |
+
"id": "SmhmRdO5OootjWvVdhZVC",
|
| 5288 |
+
"type": "rectangle",
|
| 5289 |
+
"x": 6910.6297007802405,
|
| 5290 |
+
"y": 434.81984324807524,
|
| 5291 |
+
"width": 326.64228249851993,
|
| 5292 |
+
"height": 51.91470072896671,
|
| 5293 |
+
"angle": 0,
|
| 5294 |
+
"strokeColor": "#1e1e1e",
|
| 5295 |
+
"backgroundColor": "transparent",
|
| 5296 |
+
"fillStyle": "solid",
|
| 5297 |
+
"strokeWidth": 2,
|
| 5298 |
+
"strokeStyle": "solid",
|
| 5299 |
+
"roughness": 1,
|
| 5300 |
+
"opacity": 100,
|
| 5301 |
+
"groupIds": [
|
| 5302 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5303 |
+
],
|
| 5304 |
+
"frameId": null,
|
| 5305 |
+
"index": "b1k",
|
| 5306 |
+
"roundness": null,
|
| 5307 |
+
"seed": 1403194473,
|
| 5308 |
+
"version": 104,
|
| 5309 |
+
"versionNonce": 1977506121,
|
| 5310 |
+
"isDeleted": false,
|
| 5311 |
+
"boundElements": [
|
| 5312 |
+
{
|
| 5313 |
+
"type": "text",
|
| 5314 |
+
"id": "avhHlotKdqzZ5gQytgcwQ"
|
| 5315 |
+
},
|
| 5316 |
+
{
|
| 5317 |
+
"id": "FCEWIaj7DywBOG0ukJrRU",
|
| 5318 |
+
"type": "arrow"
|
| 5319 |
+
}
|
| 5320 |
+
],
|
| 5321 |
+
"updated": 1741576964302,
|
| 5322 |
+
"link": null,
|
| 5323 |
+
"locked": false
|
| 5324 |
+
},
|
| 5325 |
+
{
|
| 5326 |
+
"id": "-AKHuCF8e7h2_yxWV-Lgt",
|
| 5327 |
+
"type": "rectangle",
|
| 5328 |
+
"x": 7406.898105937576,
|
| 5329 |
+
"y": 434.81984324807524,
|
| 5330 |
+
"width": 236.842386457754,
|
| 5331 |
+
"height": 51.91470072896671,
|
| 5332 |
+
"angle": 0,
|
| 5333 |
+
"strokeColor": "#1e1e1e",
|
| 5334 |
+
"backgroundColor": "transparent",
|
| 5335 |
+
"fillStyle": "solid",
|
| 5336 |
+
"strokeWidth": 2,
|
| 5337 |
+
"strokeStyle": "solid",
|
| 5338 |
+
"roughness": 1,
|
| 5339 |
+
"opacity": 100,
|
| 5340 |
+
"groupIds": [
|
| 5341 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5342 |
+
],
|
| 5343 |
+
"frameId": null,
|
| 5344 |
+
"index": "b1l",
|
| 5345 |
+
"roundness": null,
|
| 5346 |
+
"seed": 2050463561,
|
| 5347 |
+
"version": 109,
|
| 5348 |
+
"versionNonce": 928298601,
|
| 5349 |
+
"isDeleted": false,
|
| 5350 |
+
"boundElements": [
|
| 5351 |
+
{
|
| 5352 |
+
"type": "text",
|
| 5353 |
+
"id": "GHHkKHEJHxyvjEsluWanq"
|
| 5354 |
+
},
|
| 5355 |
+
{
|
| 5356 |
+
"id": "vZM--SBjM4zyowOgs3hDv",
|
| 5357 |
+
"type": "arrow"
|
| 5358 |
+
},
|
| 5359 |
+
{
|
| 5360 |
+
"id": "F704_xUi4glbWWo1UMSZD",
|
| 5361 |
+
"type": "arrow"
|
| 5362 |
+
}
|
| 5363 |
+
],
|
| 5364 |
+
"updated": 1741577012386,
|
| 5365 |
+
"link": null,
|
| 5366 |
+
"locked": false
|
| 5367 |
+
},
|
| 5368 |
+
{
|
| 5369 |
+
"id": "W854nPSz0p4lut302JyEo",
|
| 5370 |
+
"type": "rectangle",
|
| 5371 |
+
"x": 6547.033221686829,
|
| 5372 |
+
"y": 579.9450293767776,
|
| 5373 |
+
"width": 311.13792762882497,
|
| 5374 |
+
"height": 51.91470072896671,
|
| 5375 |
+
"angle": 0,
|
| 5376 |
+
"strokeColor": "#1e1e1e",
|
| 5377 |
+
"backgroundColor": "transparent",
|
| 5378 |
+
"fillStyle": "solid",
|
| 5379 |
+
"strokeWidth": 2,
|
| 5380 |
+
"strokeStyle": "solid",
|
| 5381 |
+
"roughness": 1,
|
| 5382 |
+
"opacity": 100,
|
| 5383 |
+
"groupIds": [
|
| 5384 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5385 |
+
],
|
| 5386 |
+
"frameId": null,
|
| 5387 |
+
"index": "b1m",
|
| 5388 |
+
"roundness": null,
|
| 5389 |
+
"seed": 934790697,
|
| 5390 |
+
"version": 107,
|
| 5391 |
+
"versionNonce": 1443202297,
|
| 5392 |
+
"isDeleted": false,
|
| 5393 |
+
"boundElements": [
|
| 5394 |
+
{
|
| 5395 |
+
"type": "text",
|
| 5396 |
+
"id": "-7qd6ZmMNssePA_ZVXOds"
|
| 5397 |
+
},
|
| 5398 |
+
{
|
| 5399 |
+
"id": "eXqdTotBC05JGtmSBdiba",
|
| 5400 |
+
"type": "arrow"
|
| 5401 |
+
},
|
| 5402 |
+
{
|
| 5403 |
+
"id": "RG4hRN73PDSPg9TvjEABI",
|
| 5404 |
+
"type": "arrow"
|
| 5405 |
+
}
|
| 5406 |
+
],
|
| 5407 |
+
"updated": 1741577112902,
|
| 5408 |
+
"link": null,
|
| 5409 |
+
"locked": false
|
| 5410 |
+
},
|
| 5411 |
+
{
|
| 5412 |
+
"id": "zkIptPJSdTc7fyDulV4Nc",
|
| 5413 |
+
"type": "rectangle",
|
| 5414 |
+
"x": 7406.235244066099,
|
| 5415 |
+
"y": 532.5354253333344,
|
| 5416 |
+
"width": 236.28111124423486,
|
| 5417 |
+
"height": 44,
|
| 5418 |
+
"angle": 0,
|
| 5419 |
+
"strokeColor": "#1e1e1e",
|
| 5420 |
+
"backgroundColor": "transparent",
|
| 5421 |
+
"fillStyle": "solid",
|
| 5422 |
+
"strokeWidth": 2,
|
| 5423 |
+
"strokeStyle": "solid",
|
| 5424 |
+
"roughness": 1,
|
| 5425 |
+
"opacity": 100,
|
| 5426 |
+
"groupIds": [],
|
| 5427 |
+
"frameId": null,
|
| 5428 |
+
"index": "b1n",
|
| 5429 |
+
"roundness": null,
|
| 5430 |
+
"seed": 1997478153,
|
| 5431 |
+
"version": 154,
|
| 5432 |
+
"versionNonce": 717019239,
|
| 5433 |
+
"isDeleted": false,
|
| 5434 |
+
"boundElements": [
|
| 5435 |
+
{
|
| 5436 |
+
"type": "text",
|
| 5437 |
+
"id": "yhuAJFICJoRjDFmjFzM6C"
|
| 5438 |
+
},
|
| 5439 |
+
{
|
| 5440 |
+
"id": "F704_xUi4glbWWo1UMSZD",
|
| 5441 |
+
"type": "arrow"
|
| 5442 |
+
}
|
| 5443 |
+
],
|
| 5444 |
+
"updated": 1741577022253,
|
| 5445 |
+
"link": null,
|
| 5446 |
+
"locked": false
|
| 5447 |
+
},
|
| 5448 |
+
{
|
| 5449 |
+
"id": "hktnzSdCZsg2XG9eDy1n-",
|
| 5450 |
+
"type": "arrow",
|
| 5451 |
+
"x": 6888.2769562202075,
|
| 5452 |
+
"y": 197.07411150064814,
|
| 5453 |
+
"width": 0.00048490204972040374,
|
| 5454 |
+
"height": 85.77724415899723,
|
| 5455 |
+
"angle": 0,
|
| 5456 |
+
"strokeColor": "#1e1e1e",
|
| 5457 |
+
"backgroundColor": "transparent",
|
| 5458 |
+
"fillStyle": "solid",
|
| 5459 |
+
"strokeWidth": 2,
|
| 5460 |
+
"strokeStyle": "solid",
|
| 5461 |
+
"roughness": 1,
|
| 5462 |
+
"opacity": 100,
|
| 5463 |
+
"groupIds": [
|
| 5464 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5465 |
+
],
|
| 5466 |
+
"frameId": null,
|
| 5467 |
+
"index": "b1r",
|
| 5468 |
+
"roundness": {
|
| 5469 |
+
"type": 2
|
| 5470 |
+
},
|
| 5471 |
+
"seed": 2095697033,
|
| 5472 |
+
"version": 259,
|
| 5473 |
+
"versionNonce": 1243141799,
|
| 5474 |
+
"isDeleted": false,
|
| 5475 |
+
"boundElements": [],
|
| 5476 |
+
"updated": 1741576957690,
|
| 5477 |
+
"link": null,
|
| 5478 |
+
"locked": false,
|
| 5479 |
+
"points": [
|
| 5480 |
+
[
|
| 5481 |
+
0,
|
| 5482 |
+
0
|
| 5483 |
+
],
|
| 5484 |
+
[
|
| 5485 |
+
0,
|
| 5486 |
+
46.015302918856854
|
| 5487 |
+
],
|
| 5488 |
+
[
|
| 5489 |
+
-0.00048490204972040374,
|
| 5490 |
+
85.77724415899723
|
| 5491 |
+
]
|
| 5492 |
+
],
|
| 5493 |
+
"lastCommittedPoint": null,
|
| 5494 |
+
"startBinding": null,
|
| 5495 |
+
"endBinding": {
|
| 5496 |
+
"elementId": "O4ynYwner8EHSv-cUHyx0",
|
| 5497 |
+
"focus": -0.0000018829436698626987,
|
| 5498 |
+
"gap": 6.843301459727456
|
| 5499 |
+
},
|
| 5500 |
+
"startArrowhead": null,
|
| 5501 |
+
"endArrowhead": "arrow",
|
| 5502 |
+
"elbowed": false
|
| 5503 |
+
},
|
| 5504 |
+
{
|
| 5505 |
+
"id": "eXqdTotBC05JGtmSBdiba",
|
| 5506 |
+
"type": "arrow",
|
| 5507 |
+
"x": 6702.601669303934,
|
| 5508 |
+
"y": 487.3244837580529,
|
| 5509 |
+
"width": 9.094947017729282e-13,
|
| 5510 |
+
"height": 85.77724415899729,
|
| 5511 |
+
"angle": 0,
|
| 5512 |
+
"strokeColor": "#1e1e1e",
|
| 5513 |
+
"backgroundColor": "transparent",
|
| 5514 |
+
"fillStyle": "solid",
|
| 5515 |
+
"strokeWidth": 2,
|
| 5516 |
+
"strokeStyle": "solid",
|
| 5517 |
+
"roughness": 1,
|
| 5518 |
+
"opacity": 100,
|
| 5519 |
+
"groupIds": [
|
| 5520 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5521 |
+
],
|
| 5522 |
+
"frameId": null,
|
| 5523 |
+
"index": "b1x",
|
| 5524 |
+
"roundness": {
|
| 5525 |
+
"type": 2
|
| 5526 |
+
},
|
| 5527 |
+
"seed": 2118324681,
|
| 5528 |
+
"version": 261,
|
| 5529 |
+
"versionNonce": 1203561479,
|
| 5530 |
+
"isDeleted": false,
|
| 5531 |
+
"boundElements": [
|
| 5532 |
+
{
|
| 5533 |
+
"type": "text",
|
| 5534 |
+
"id": "urJkseY3HJY69Oariem4m"
|
| 5535 |
+
}
|
| 5536 |
+
],
|
| 5537 |
+
"updated": 1741576957690,
|
| 5538 |
+
"link": null,
|
| 5539 |
+
"locked": false,
|
| 5540 |
+
"points": [
|
| 5541 |
+
[
|
| 5542 |
+
0,
|
| 5543 |
+
0
|
| 5544 |
+
],
|
| 5545 |
+
[
|
| 5546 |
+
0,
|
| 5547 |
+
46.015302918856854
|
| 5548 |
+
],
|
| 5549 |
+
[
|
| 5550 |
+
-9.094947017729282e-13,
|
| 5551 |
+
85.77724415899729
|
| 5552 |
+
]
|
| 5553 |
+
],
|
| 5554 |
+
"lastCommittedPoint": null,
|
| 5555 |
+
"startBinding": null,
|
| 5556 |
+
"endBinding": {
|
| 5557 |
+
"elementId": "W854nPSz0p4lut302JyEo",
|
| 5558 |
+
"focus": -0.0000033181252662927257,
|
| 5559 |
+
"gap": 6.8433014597274
|
| 5560 |
+
},
|
| 5561 |
+
"startArrowhead": null,
|
| 5562 |
+
"endArrowhead": "arrow",
|
| 5563 |
+
"elbowed": false
|
| 5564 |
+
},
|
| 5565 |
+
{
|
| 5566 |
+
"id": "zSH_0RVoAH6FqvlC-qOEe",
|
| 5567 |
+
"type": "text",
|
| 5568 |
+
"x": 6894.8592730095925,
|
| 5569 |
+
"y": 722.2366477728008,
|
| 5570 |
+
"width": 44.43995666503906,
|
| 5571 |
+
"height": 25,
|
| 5572 |
+
"angle": 0,
|
| 5573 |
+
"strokeColor": "#1e1e1e",
|
| 5574 |
+
"backgroundColor": "transparent",
|
| 5575 |
+
"fillStyle": "solid",
|
| 5576 |
+
"strokeWidth": 2,
|
| 5577 |
+
"strokeStyle": "solid",
|
| 5578 |
+
"roughness": 1,
|
| 5579 |
+
"opacity": 100,
|
| 5580 |
+
"groupIds": [],
|
| 5581 |
+
"frameId": null,
|
| 5582 |
+
"index": "b21",
|
| 5583 |
+
"roundness": null,
|
| 5584 |
+
"seed": 1023108425,
|
| 5585 |
+
"version": 141,
|
| 5586 |
+
"versionNonce": 1817672585,
|
| 5587 |
+
"isDeleted": false,
|
| 5588 |
+
"boundElements": [],
|
| 5589 |
+
"updated": 1741577040688,
|
| 5590 |
+
"link": null,
|
| 5591 |
+
"locked": false,
|
| 5592 |
+
"text": "User",
|
| 5593 |
+
"fontSize": 20,
|
| 5594 |
+
"fontFamily": 5,
|
| 5595 |
+
"textAlign": "center",
|
| 5596 |
+
"verticalAlign": "middle",
|
| 5597 |
+
"containerId": "hh-vJczGSvSCpkhjc-ZFd",
|
| 5598 |
+
"originalText": "User",
|
| 5599 |
+
"autoResize": true,
|
| 5600 |
+
"lineHeight": 1.25
|
| 5601 |
+
},
|
| 5602 |
+
{
|
| 5603 |
+
"id": "YN7Z_MhrnVfus-HHFPTk1",
|
| 5604 |
+
"type": "text",
|
| 5605 |
+
"x": 6833.114194660153,
|
| 5606 |
+
"y": -66.0390308302513,
|
| 5607 |
+
"width": 212.7344970703125,
|
| 5608 |
+
"height": 29.496989050549267,
|
| 5609 |
+
"angle": 0,
|
| 5610 |
+
"strokeColor": "#1e1e1e",
|
| 5611 |
+
"backgroundColor": "transparent",
|
| 5612 |
+
"fillStyle": "solid",
|
| 5613 |
+
"strokeWidth": 2,
|
| 5614 |
+
"strokeStyle": "solid",
|
| 5615 |
+
"roughness": 1,
|
| 5616 |
+
"opacity": 100,
|
| 5617 |
+
"groupIds": [
|
| 5618 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5619 |
+
],
|
| 5620 |
+
"frameId": null,
|
| 5621 |
+
"index": "b22",
|
| 5622 |
+
"roundness": null,
|
| 5623 |
+
"seed": 1288819753,
|
| 5624 |
+
"version": 112,
|
| 5625 |
+
"versionNonce": 441046665,
|
| 5626 |
+
"isDeleted": false,
|
| 5627 |
+
"boundElements": [],
|
| 5628 |
+
"updated": 1741576957668,
|
| 5629 |
+
"link": null,
|
| 5630 |
+
"locked": false,
|
| 5631 |
+
"text": "AWS API Gateway",
|
| 5632 |
+
"fontSize": 23.597591240439414,
|
| 5633 |
+
"fontFamily": 5,
|
| 5634 |
+
"textAlign": "center",
|
| 5635 |
+
"verticalAlign": "middle",
|
| 5636 |
+
"containerId": "5rQwEFQ0OLt7lCCh6ROrl",
|
| 5637 |
+
"originalText": "AWS API Gateway",
|
| 5638 |
+
"autoResize": true,
|
| 5639 |
+
"lineHeight": 1.25
|
| 5640 |
+
},
|
| 5641 |
+
{
|
| 5642 |
+
"id": "A45yyUfo1lPMEY8TmwNAv",
|
| 5643 |
+
"type": "text",
|
| 5644 |
+
"x": 6679.344878334033,
|
| 5645 |
+
"y": 44.869647999813935,
|
| 5646 |
+
"width": 141.58709716796875,
|
| 5647 |
+
"height": 29.496989050549267,
|
| 5648 |
+
"angle": 0,
|
| 5649 |
+
"strokeColor": "#1e1e1e",
|
| 5650 |
+
"backgroundColor": "transparent",
|
| 5651 |
+
"fillStyle": "solid",
|
| 5652 |
+
"strokeWidth": 2,
|
| 5653 |
+
"strokeStyle": "solid",
|
| 5654 |
+
"roughness": 1,
|
| 5655 |
+
"opacity": 100,
|
| 5656 |
+
"groupIds": [
|
| 5657 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5658 |
+
],
|
| 5659 |
+
"frameId": null,
|
| 5660 |
+
"index": "b23",
|
| 5661 |
+
"roundness": null,
|
| 5662 |
+
"seed": 968320777,
|
| 5663 |
+
"version": 116,
|
| 5664 |
+
"versionNonce": 471452169,
|
| 5665 |
+
"isDeleted": false,
|
| 5666 |
+
"boundElements": [],
|
| 5667 |
+
"updated": 1741576957669,
|
| 5668 |
+
"link": null,
|
| 5669 |
+
"locked": false,
|
| 5670 |
+
"text": "AWS Lambda",
|
| 5671 |
+
"fontSize": 23.597591240439414,
|
| 5672 |
+
"fontFamily": 5,
|
| 5673 |
+
"textAlign": "center",
|
| 5674 |
+
"verticalAlign": "middle",
|
| 5675 |
+
"containerId": "lcNOx1PJ1LukZ3eN5WcGs",
|
| 5676 |
+
"originalText": "AWS Lambda",
|
| 5677 |
+
"autoResize": true,
|
| 5678 |
+
"lineHeight": 1.25
|
| 5679 |
+
},
|
| 5680 |
+
{
|
| 5681 |
+
"id": "fXbbtx_NcVZBC_fsO1Uff",
|
| 5682 |
+
"type": "text",
|
| 5683 |
+
"x": 6752.7992341022855,
|
| 5684 |
+
"y": 155.77832682987918,
|
| 5685 |
+
"width": 270.9545593261719,
|
| 5686 |
+
"height": 29.496989050549267,
|
| 5687 |
+
"angle": 0,
|
| 5688 |
+
"strokeColor": "#1e1e1e",
|
| 5689 |
+
"backgroundColor": "transparent",
|
| 5690 |
+
"fillStyle": "solid",
|
| 5691 |
+
"strokeWidth": 2,
|
| 5692 |
+
"strokeStyle": "solid",
|
| 5693 |
+
"roughness": 1,
|
| 5694 |
+
"opacity": 100,
|
| 5695 |
+
"groupIds": [
|
| 5696 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5697 |
+
],
|
| 5698 |
+
"frameId": null,
|
| 5699 |
+
"index": "b24",
|
| 5700 |
+
"roundness": null,
|
| 5701 |
+
"seed": 1579654633,
|
| 5702 |
+
"version": 120,
|
| 5703 |
+
"versionNonce": 2083349609,
|
| 5704 |
+
"isDeleted": false,
|
| 5705 |
+
"boundElements": [],
|
| 5706 |
+
"updated": 1741576957669,
|
| 5707 |
+
"link": null,
|
| 5708 |
+
"locked": false,
|
| 5709 |
+
"text": "AWS SQS (Task Queue)",
|
| 5710 |
+
"fontSize": 23.597591240439414,
|
| 5711 |
+
"fontFamily": 5,
|
| 5712 |
+
"textAlign": "center",
|
| 5713 |
+
"verticalAlign": "middle",
|
| 5714 |
+
"containerId": "YPquWrf902Bx_VzPe5ZKo",
|
| 5715 |
+
"originalText": "AWS SQS (Task Queue)",
|
| 5716 |
+
"autoResize": true,
|
| 5717 |
+
"lineHeight": 1.25
|
| 5718 |
+
},
|
| 5719 |
+
{
|
| 5720 |
+
"id": "V3UN0NpFvZDTX16OkuBNR",
|
| 5721 |
+
"type": "text",
|
| 5722 |
+
"x": 6684.789361665762,
|
| 5723 |
+
"y": 300.90351295858153,
|
| 5724 |
+
"width": 406.97430419921875,
|
| 5725 |
+
"height": 29.496989050549267,
|
| 5726 |
+
"angle": 0,
|
| 5727 |
+
"strokeColor": "#1e1e1e",
|
| 5728 |
+
"backgroundColor": "transparent",
|
| 5729 |
+
"fillStyle": "solid",
|
| 5730 |
+
"strokeWidth": 2,
|
| 5731 |
+
"strokeStyle": "solid",
|
| 5732 |
+
"roughness": 1,
|
| 5733 |
+
"opacity": 100,
|
| 5734 |
+
"groupIds": [
|
| 5735 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5736 |
+
],
|
| 5737 |
+
"frameId": null,
|
| 5738 |
+
"index": "b25",
|
| 5739 |
+
"roundness": null,
|
| 5740 |
+
"seed": 959314121,
|
| 5741 |
+
"version": 112,
|
| 5742 |
+
"versionNonce": 1184395529,
|
| 5743 |
+
"isDeleted": false,
|
| 5744 |
+
"boundElements": [],
|
| 5745 |
+
"updated": 1741576957669,
|
| 5746 |
+
"link": null,
|
| 5747 |
+
"locked": false,
|
| 5748 |
+
"text": "Celery Workers (Distributed Tasks)",
|
| 5749 |
+
"fontSize": 23.597591240439414,
|
| 5750 |
+
"fontFamily": 5,
|
| 5751 |
+
"textAlign": "center",
|
| 5752 |
+
"verticalAlign": "middle",
|
| 5753 |
+
"containerId": "O4ynYwner8EHSv-cUHyx0",
|
| 5754 |
+
"originalText": "Celery Workers (Distributed Tasks)",
|
| 5755 |
+
"autoResize": true,
|
| 5756 |
+
"lineHeight": 1.25
|
| 5757 |
+
},
|
| 5758 |
+
{
|
| 5759 |
+
"id": "lKO9DytZCWjoMyMUO9WnK",
|
| 5760 |
+
"type": "text",
|
| 5761 |
+
"x": 6252.558107535698,
|
| 5762 |
+
"y": 446.02869908728394,
|
| 5763 |
+
"width": 181.6428985595703,
|
| 5764 |
+
"height": 29.496989050549267,
|
| 5765 |
+
"angle": 0,
|
| 5766 |
+
"strokeColor": "#1e1e1e",
|
| 5767 |
+
"backgroundColor": "transparent",
|
| 5768 |
+
"fillStyle": "solid",
|
| 5769 |
+
"strokeWidth": 2,
|
| 5770 |
+
"strokeStyle": "solid",
|
| 5771 |
+
"roughness": 1,
|
| 5772 |
+
"opacity": 100,
|
| 5773 |
+
"groupIds": [
|
| 5774 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5775 |
+
],
|
| 5776 |
+
"frameId": null,
|
| 5777 |
+
"index": "b26",
|
| 5778 |
+
"roundness": null,
|
| 5779 |
+
"seed": 1550186409,
|
| 5780 |
+
"version": 112,
|
| 5781 |
+
"versionNonce": 1604561769,
|
| 5782 |
+
"isDeleted": false,
|
| 5783 |
+
"boundElements": [],
|
| 5784 |
+
"updated": 1741576957670,
|
| 5785 |
+
"link": null,
|
| 5786 |
+
"locked": false,
|
| 5787 |
+
"text": "AWS DynamoDB",
|
| 5788 |
+
"fontSize": 23.597591240439414,
|
| 5789 |
+
"fontFamily": 5,
|
| 5790 |
+
"textAlign": "center",
|
| 5791 |
+
"verticalAlign": "middle",
|
| 5792 |
+
"containerId": "d-Obq1jyhqw7abPHd6_i5",
|
| 5793 |
+
"originalText": "AWS DynamoDB",
|
| 5794 |
+
"autoResize": true,
|
| 5795 |
+
"lineHeight": 1.25
|
| 5796 |
+
},
|
| 5797 |
+
{
|
| 5798 |
+
"id": "ZIBlXFowG3rpzs7ZAmiZZ",
|
| 5799 |
+
"type": "text",
|
| 5800 |
+
"x": 6574.75627638259,
|
| 5801 |
+
"y": 446.02869908728394,
|
| 5802 |
+
"width": 255.6918182373047,
|
| 5803 |
+
"height": 29.496989050549267,
|
| 5804 |
+
"angle": 0,
|
| 5805 |
+
"strokeColor": "#1e1e1e",
|
| 5806 |
+
"backgroundColor": "transparent",
|
| 5807 |
+
"fillStyle": "solid",
|
| 5808 |
+
"strokeWidth": 2,
|
| 5809 |
+
"strokeStyle": "solid",
|
| 5810 |
+
"roughness": 1,
|
| 5811 |
+
"opacity": 100,
|
| 5812 |
+
"groupIds": [
|
| 5813 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5814 |
+
],
|
| 5815 |
+
"frameId": null,
|
| 5816 |
+
"index": "b27",
|
| 5817 |
+
"roundness": null,
|
| 5818 |
+
"seed": 435886729,
|
| 5819 |
+
"version": 120,
|
| 5820 |
+
"versionNonce": 1559375593,
|
| 5821 |
+
"isDeleted": false,
|
| 5822 |
+
"boundElements": [],
|
| 5823 |
+
"updated": 1741576957670,
|
| 5824 |
+
"link": null,
|
| 5825 |
+
"locked": false,
|
| 5826 |
+
"text": "AWS S3 (File Storage)",
|
| 5827 |
+
"fontSize": 23.597591240439414,
|
| 5828 |
+
"fontFamily": 5,
|
| 5829 |
+
"textAlign": "center",
|
| 5830 |
+
"verticalAlign": "middle",
|
| 5831 |
+
"containerId": "SLQjJ54OvcwHKgE0saBYW",
|
| 5832 |
+
"originalText": "AWS S3 (File Storage)",
|
| 5833 |
+
"autoResize": true,
|
| 5834 |
+
"lineHeight": 1.25
|
| 5835 |
+
},
|
| 5836 |
+
{
|
| 5837 |
+
"id": "avhHlotKdqzZ5gQytgcwQ",
|
| 5838 |
+
"type": "text",
|
| 5839 |
+
"x": 6933.366338855672,
|
| 5840 |
+
"y": 446.02869908728394,
|
| 5841 |
+
"width": 281.16900634765625,
|
| 5842 |
+
"height": 29.496989050549267,
|
| 5843 |
+
"angle": 0,
|
| 5844 |
+
"strokeColor": "#1e1e1e",
|
| 5845 |
+
"backgroundColor": "transparent",
|
| 5846 |
+
"fillStyle": "solid",
|
| 5847 |
+
"strokeWidth": 2,
|
| 5848 |
+
"strokeStyle": "solid",
|
| 5849 |
+
"roughness": 1,
|
| 5850 |
+
"opacity": 100,
|
| 5851 |
+
"groupIds": [
|
| 5852 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5853 |
+
],
|
| 5854 |
+
"frameId": null,
|
| 5855 |
+
"index": "b28",
|
| 5856 |
+
"roundness": null,
|
| 5857 |
+
"seed": 1923670377,
|
| 5858 |
+
"version": 120,
|
| 5859 |
+
"versionNonce": 748899209,
|
| 5860 |
+
"isDeleted": false,
|
| 5861 |
+
"boundElements": [],
|
| 5862 |
+
"updated": 1741576957670,
|
| 5863 |
+
"link": null,
|
| 5864 |
+
"locked": false,
|
| 5865 |
+
"text": "AWS SNS (Notifications)",
|
| 5866 |
+
"fontSize": 23.597591240439414,
|
| 5867 |
+
"fontFamily": 5,
|
| 5868 |
+
"textAlign": "center",
|
| 5869 |
+
"verticalAlign": "middle",
|
| 5870 |
+
"containerId": "SmhmRdO5OootjWvVdhZVC",
|
| 5871 |
+
"originalText": "AWS SNS (Notifications)",
|
| 5872 |
+
"autoResize": true,
|
| 5873 |
+
"lineHeight": 1.25
|
| 5874 |
+
},
|
| 5875 |
+
{
|
| 5876 |
+
"id": "GHHkKHEJHxyvjEsluWanq",
|
| 5877 |
+
"type": "text",
|
| 5878 |
+
"x": 7430.640893404734,
|
| 5879 |
+
"y": 446.02869908728394,
|
| 5880 |
+
"width": 189.3568115234375,
|
| 5881 |
+
"height": 29.496989050549267,
|
| 5882 |
+
"angle": 0,
|
| 5883 |
+
"strokeColor": "#1e1e1e",
|
| 5884 |
+
"backgroundColor": "transparent",
|
| 5885 |
+
"fillStyle": "solid",
|
| 5886 |
+
"strokeWidth": 2,
|
| 5887 |
+
"strokeStyle": "solid",
|
| 5888 |
+
"roughness": 1,
|
| 5889 |
+
"opacity": 100,
|
| 5890 |
+
"groupIds": [
|
| 5891 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5892 |
+
],
|
| 5893 |
+
"frameId": null,
|
| 5894 |
+
"index": "b29",
|
| 5895 |
+
"roundness": null,
|
| 5896 |
+
"seed": 1136315465,
|
| 5897 |
+
"version": 112,
|
| 5898 |
+
"versionNonce": 150350889,
|
| 5899 |
+
"isDeleted": false,
|
| 5900 |
+
"boundElements": [],
|
| 5901 |
+
"updated": 1741576957670,
|
| 5902 |
+
"link": null,
|
| 5903 |
+
"locked": false,
|
| 5904 |
+
"text": "AWS CloudWatch",
|
| 5905 |
+
"fontSize": 23.597591240439414,
|
| 5906 |
+
"fontFamily": 5,
|
| 5907 |
+
"textAlign": "center",
|
| 5908 |
+
"verticalAlign": "middle",
|
| 5909 |
+
"containerId": "-AKHuCF8e7h2_yxWV-Lgt",
|
| 5910 |
+
"originalText": "AWS CloudWatch",
|
| 5911 |
+
"autoResize": true,
|
| 5912 |
+
"lineHeight": 1.25
|
| 5913 |
+
},
|
| 5914 |
+
{
|
| 5915 |
+
"id": "-7qd6ZmMNssePA_ZVXOds",
|
| 5916 |
+
"type": "text",
|
| 5917 |
+
"x": 6571.94904829421,
|
| 5918 |
+
"y": 591.1538852159863,
|
| 5919 |
+
"width": 261.3062744140625,
|
| 5920 |
+
"height": 29.496989050549267,
|
| 5921 |
+
"angle": 0,
|
| 5922 |
+
"strokeColor": "#1e1e1e",
|
| 5923 |
+
"backgroundColor": "transparent",
|
| 5924 |
+
"fillStyle": "solid",
|
| 5925 |
+
"strokeWidth": 2,
|
| 5926 |
+
"strokeStyle": "solid",
|
| 5927 |
+
"roughness": 1,
|
| 5928 |
+
"opacity": 100,
|
| 5929 |
+
"groupIds": [
|
| 5930 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 5931 |
+
],
|
| 5932 |
+
"frameId": null,
|
| 5933 |
+
"index": "b2A",
|
| 5934 |
+
"roundness": null,
|
| 5935 |
+
"seed": 890002217,
|
| 5936 |
+
"version": 112,
|
| 5937 |
+
"versionNonce": 396251049,
|
| 5938 |
+
"isDeleted": false,
|
| 5939 |
+
"boundElements": [],
|
| 5940 |
+
"updated": 1741576957670,
|
| 5941 |
+
"link": null,
|
| 5942 |
+
"locked": false,
|
| 5943 |
+
"text": "AWS CloudFront (CDN)",
|
| 5944 |
+
"fontSize": 23.597591240439414,
|
| 5945 |
+
"fontFamily": 5,
|
| 5946 |
+
"textAlign": "center",
|
| 5947 |
+
"verticalAlign": "middle",
|
| 5948 |
+
"containerId": "W854nPSz0p4lut302JyEo",
|
| 5949 |
+
"originalText": "AWS CloudFront (CDN)",
|
| 5950 |
+
"autoResize": true,
|
| 5951 |
+
"lineHeight": 1.25
|
| 5952 |
+
},
|
| 5953 |
+
{
|
| 5954 |
+
"id": "yhuAJFICJoRjDFmjFzM6C",
|
| 5955 |
+
"type": "text",
|
| 5956 |
+
"x": 7432.925856145735,
|
| 5957 |
+
"y": 542.0354253333344,
|
| 5958 |
+
"width": 182.89988708496094,
|
| 5959 |
+
"height": 25,
|
| 5960 |
+
"angle": 0,
|
| 5961 |
+
"strokeColor": "#1e1e1e",
|
| 5962 |
+
"backgroundColor": "transparent",
|
| 5963 |
+
"fillStyle": "solid",
|
| 5964 |
+
"strokeWidth": 2,
|
| 5965 |
+
"strokeStyle": "solid",
|
| 5966 |
+
"roughness": 1,
|
| 5967 |
+
"opacity": 100,
|
| 5968 |
+
"groupIds": [],
|
| 5969 |
+
"frameId": null,
|
| 5970 |
+
"index": "b2B",
|
| 5971 |
+
"roundness": null,
|
| 5972 |
+
"seed": 1728646665,
|
| 5973 |
+
"version": 148,
|
| 5974 |
+
"versionNonce": 1559308967,
|
| 5975 |
+
"isDeleted": false,
|
| 5976 |
+
"boundElements": [],
|
| 5977 |
+
"updated": 1741577022253,
|
| 5978 |
+
"link": null,
|
| 5979 |
+
"locked": false,
|
| 5980 |
+
"text": "DevOps Monitoring",
|
| 5981 |
+
"fontSize": 20,
|
| 5982 |
+
"fontFamily": 5,
|
| 5983 |
+
"textAlign": "center",
|
| 5984 |
+
"verticalAlign": "middle",
|
| 5985 |
+
"containerId": "zkIptPJSdTc7fyDulV4Nc",
|
| 5986 |
+
"originalText": "DevOps Monitoring",
|
| 5987 |
+
"autoResize": true,
|
| 5988 |
+
"lineHeight": 1.25
|
| 5989 |
+
},
|
| 5990 |
+
{
|
| 5991 |
+
"id": "urJkseY3HJY69Oariem4m",
|
| 5992 |
+
"type": "text",
|
| 5993 |
+
"x": 6614.139244072,
|
| 5994 |
+
"y": 518.5912921516351,
|
| 5995 |
+
"width": 176.9248504638672,
|
| 5996 |
+
"height": 29.496989050549267,
|
| 5997 |
+
"angle": 0,
|
| 5998 |
+
"strokeColor": "#1e1e1e",
|
| 5999 |
+
"backgroundColor": "transparent",
|
| 6000 |
+
"fillStyle": "solid",
|
| 6001 |
+
"strokeWidth": 2,
|
| 6002 |
+
"strokeStyle": "solid",
|
| 6003 |
+
"roughness": 1,
|
| 6004 |
+
"opacity": 100,
|
| 6005 |
+
"groupIds": [
|
| 6006 |
+
"wjqPa5lw_KjJSGahompw4"
|
| 6007 |
+
],
|
| 6008 |
+
"frameId": null,
|
| 6009 |
+
"index": "b2I",
|
| 6010 |
+
"roundness": null,
|
| 6011 |
+
"seed": 1695539753,
|
| 6012 |
+
"version": 103,
|
| 6013 |
+
"versionNonce": 1733728649,
|
| 6014 |
+
"isDeleted": false,
|
| 6015 |
+
"boundElements": [],
|
| 6016 |
+
"updated": 1741576957670,
|
| 6017 |
+
"link": null,
|
| 6018 |
+
"locked": false,
|
| 6019 |
+
"text": "Serves Content",
|
| 6020 |
+
"fontSize": 23.597591240439414,
|
| 6021 |
+
"fontFamily": 5,
|
| 6022 |
+
"textAlign": "center",
|
| 6023 |
+
"verticalAlign": "middle",
|
| 6024 |
+
"containerId": "eXqdTotBC05JGtmSBdiba",
|
| 6025 |
+
"originalText": "Serves Content",
|
| 6026 |
+
"autoResize": true,
|
| 6027 |
+
"lineHeight": 1.25
|
| 6028 |
+
},
|
| 6029 |
+
{
|
| 6030 |
+
"id": "QwQpbmMZYUJCdK5IFIkvr",
|
| 6031 |
+
"type": "text",
|
| 6032 |
+
"x": 6544.071555396827,
|
| 6033 |
+
"y": -344.4583329999999,
|
| 6034 |
+
"width": 849.491943359375,
|
| 6035 |
+
"height": 105,
|
| 6036 |
+
"angle": 0,
|
| 6037 |
+
"strokeColor": "#1e1e1e",
|
| 6038 |
+
"backgroundColor": "transparent",
|
| 6039 |
+
"fillStyle": "solid",
|
| 6040 |
+
"strokeWidth": 2,
|
| 6041 |
+
"strokeStyle": "solid",
|
| 6042 |
+
"roughness": 1,
|
| 6043 |
+
"opacity": 100,
|
| 6044 |
+
"groupIds": [],
|
| 6045 |
+
"frameId": null,
|
| 6046 |
+
"index": "b2K",
|
| 6047 |
+
"roundness": null,
|
| 6048 |
+
"seed": 925609498,
|
| 6049 |
+
"version": 680,
|
| 6050 |
+
"versionNonce": 1532562041,
|
| 6051 |
+
"isDeleted": false,
|
| 6052 |
+
"boundElements": [],
|
| 6053 |
+
"updated": 1741577153339,
|
| 6054 |
+
"link": null,
|
| 6055 |
+
"locked": false,
|
| 6056 |
+
"text": "Cloud Infrastructure",
|
| 6057 |
+
"fontSize": 84,
|
| 6058 |
+
"fontFamily": 5,
|
| 6059 |
+
"textAlign": "left",
|
| 6060 |
+
"verticalAlign": "top",
|
| 6061 |
+
"containerId": null,
|
| 6062 |
+
"originalText": "Cloud Infrastructure",
|
| 6063 |
+
"autoResize": true,
|
| 6064 |
+
"lineHeight": 1.25
|
| 6065 |
+
},
|
| 6066 |
+
{
|
| 6067 |
+
"id": "Gr_IecqseQ5STfEBPzVML",
|
| 6068 |
+
"type": "arrow",
|
| 6069 |
+
"x": 6939.381443195309,
|
| 6070 |
+
"y": -20.33318594049333,
|
| 6071 |
+
"width": 189.343016277292,
|
| 6072 |
+
"height": 48.99397810109854,
|
| 6073 |
+
"angle": 0,
|
| 6074 |
+
"strokeColor": "#1e1e1e",
|
| 6075 |
+
"backgroundColor": "transparent",
|
| 6076 |
+
"fillStyle": "solid",
|
| 6077 |
+
"strokeWidth": 2,
|
| 6078 |
+
"strokeStyle": "solid",
|
| 6079 |
+
"roughness": 1,
|
| 6080 |
+
"opacity": 100,
|
| 6081 |
+
"groupIds": [],
|
| 6082 |
+
"frameId": null,
|
| 6083 |
+
"index": "b2L",
|
| 6084 |
+
"roundness": null,
|
| 6085 |
+
"seed": 1516912969,
|
| 6086 |
+
"version": 12,
|
| 6087 |
+
"versionNonce": 742760681,
|
| 6088 |
+
"isDeleted": false,
|
| 6089 |
+
"boundElements": [],
|
| 6090 |
+
"updated": 1741576957669,
|
| 6091 |
+
"link": null,
|
| 6092 |
+
"locked": false,
|
| 6093 |
+
"points": [
|
| 6094 |
+
[
|
| 6095 |
+
0,
|
| 6096 |
+
0
|
| 6097 |
+
],
|
| 6098 |
+
[
|
| 6099 |
+
0,
|
| 6100 |
+
24.496989050549274
|
| 6101 |
+
],
|
| 6102 |
+
[
|
| 6103 |
+
-189.343016277292,
|
| 6104 |
+
24.496989050549274
|
| 6105 |
+
],
|
| 6106 |
+
[
|
| 6107 |
+
-189.343016277292,
|
| 6108 |
+
48.99397810109854
|
| 6109 |
+
]
|
| 6110 |
+
],
|
| 6111 |
+
"lastCommittedPoint": null,
|
| 6112 |
+
"startBinding": {
|
| 6113 |
+
"elementId": "5rQwEFQ0OLt7lCCh6ROrl",
|
| 6114 |
+
"fixedPoint": [
|
| 6115 |
+
0.49959225118778955,
|
| 6116 |
+
1.096311833253238
|
| 6117 |
+
],
|
| 6118 |
+
"focus": 0,
|
| 6119 |
+
"gap": 0
|
| 6120 |
+
},
|
| 6121 |
+
"endBinding": {
|
| 6122 |
+
"elementId": "lcNOx1PJ1LukZ3eN5WcGs",
|
| 6123 |
+
"fixedPoint": [
|
| 6124 |
+
0.4994565391795548,
|
| 6125 |
+
-0.0963118332532381
|
| 6126 |
+
],
|
| 6127 |
+
"focus": 0,
|
| 6128 |
+
"gap": 0
|
| 6129 |
+
},
|
| 6130 |
+
"startArrowhead": null,
|
| 6131 |
+
"endArrowhead": "arrow",
|
| 6132 |
+
"elbowed": true,
|
| 6133 |
+
"fixedSegments": null,
|
| 6134 |
+
"startIsSpecial": null,
|
| 6135 |
+
"endIsSpecial": null
|
| 6136 |
+
},
|
| 6137 |
+
{
|
| 6138 |
+
"id": "jXazvlSAB2utF1woHHpxJ",
|
| 6139 |
+
"type": "arrow",
|
| 6140 |
+
"x": 6750.038426918017,
|
| 6141 |
+
"y": 90.5754928895719,
|
| 6142 |
+
"width": 138.13808684735432,
|
| 6143 |
+
"height": 48.99397810109855,
|
| 6144 |
+
"angle": 0,
|
| 6145 |
+
"strokeColor": "#1e1e1e",
|
| 6146 |
+
"backgroundColor": "transparent",
|
| 6147 |
+
"fillStyle": "solid",
|
| 6148 |
+
"strokeWidth": 2,
|
| 6149 |
+
"strokeStyle": "solid",
|
| 6150 |
+
"roughness": 1,
|
| 6151 |
+
"opacity": 100,
|
| 6152 |
+
"groupIds": [],
|
| 6153 |
+
"frameId": null,
|
| 6154 |
+
"index": "b2M",
|
| 6155 |
+
"roundness": null,
|
| 6156 |
+
"seed": 535680231,
|
| 6157 |
+
"version": 12,
|
| 6158 |
+
"versionNonce": 1337388873,
|
| 6159 |
+
"isDeleted": false,
|
| 6160 |
+
"boundElements": [],
|
| 6161 |
+
"updated": 1741576957669,
|
| 6162 |
+
"link": null,
|
| 6163 |
+
"locked": false,
|
| 6164 |
+
"points": [
|
| 6165 |
+
[
|
| 6166 |
+
0,
|
| 6167 |
+
0
|
| 6168 |
+
],
|
| 6169 |
+
[
|
| 6170 |
+
0,
|
| 6171 |
+
24.496989050549274
|
| 6172 |
+
],
|
| 6173 |
+
[
|
| 6174 |
+
138.13808684735432,
|
| 6175 |
+
24.496989050549274
|
| 6176 |
+
],
|
| 6177 |
+
[
|
| 6178 |
+
138.13808684735432,
|
| 6179 |
+
48.99397810109855
|
| 6180 |
+
]
|
| 6181 |
+
],
|
| 6182 |
+
"lastCommittedPoint": null,
|
| 6183 |
+
"startBinding": {
|
| 6184 |
+
"elementId": "lcNOx1PJ1LukZ3eN5WcGs",
|
| 6185 |
+
"fixedPoint": [
|
| 6186 |
+
0.4994565391795548,
|
| 6187 |
+
1.096311833253238
|
| 6188 |
+
],
|
| 6189 |
+
"focus": 0,
|
| 6190 |
+
"gap": 0
|
| 6191 |
+
},
|
| 6192 |
+
"endBinding": {
|
| 6193 |
+
"elementId": "YPquWrf902Bx_VzPe5ZKo",
|
| 6194 |
+
"fixedPoint": [
|
| 6195 |
+
0.4996759300723609,
|
| 6196 |
+
-0.0963118332532381
|
| 6197 |
+
],
|
| 6198 |
+
"focus": 0,
|
| 6199 |
+
"gap": 0
|
| 6200 |
+
},
|
| 6201 |
+
"startArrowhead": null,
|
| 6202 |
+
"endArrowhead": "arrow",
|
| 6203 |
+
"elbowed": true,
|
| 6204 |
+
"fixedSegments": null,
|
| 6205 |
+
"startIsSpecial": null,
|
| 6206 |
+
"endIsSpecial": null
|
| 6207 |
+
},
|
| 6208 |
+
{
|
| 6209 |
+
"id": "3raa-F9gTwZ7ZG2sCiVMU",
|
| 6210 |
+
"type": "arrow",
|
| 6211 |
+
"x": 6228.346569847856,
|
| 6212 |
+
"y": 460.6771936125586,
|
| 6213 |
+
"width": 459.7889046596829,
|
| 6214 |
+
"height": 401.15905108747006,
|
| 6215 |
+
"angle": 0,
|
| 6216 |
+
"strokeColor": "#1e1e1e",
|
| 6217 |
+
"backgroundColor": "transparent",
|
| 6218 |
+
"fillStyle": "solid",
|
| 6219 |
+
"strokeWidth": 2,
|
| 6220 |
+
"strokeStyle": "solid",
|
| 6221 |
+
"roughness": 1,
|
| 6222 |
+
"opacity": 100,
|
| 6223 |
+
"groupIds": [],
|
| 6224 |
+
"frameId": null,
|
| 6225 |
+
"index": "b2N",
|
| 6226 |
+
"roundness": null,
|
| 6227 |
+
"seed": 1355940775,
|
| 6228 |
+
"version": 13,
|
| 6229 |
+
"versionNonce": 449599049,
|
| 6230 |
+
"isDeleted": false,
|
| 6231 |
+
"boundElements": [
|
| 6232 |
+
{
|
| 6233 |
+
"type": "text",
|
| 6234 |
+
"id": "N-p9QoQ7IUIthn6hq46No"
|
| 6235 |
+
}
|
| 6236 |
+
],
|
| 6237 |
+
"updated": 1741576957670,
|
| 6238 |
+
"link": null,
|
| 6239 |
+
"locked": false,
|
| 6240 |
+
"points": [
|
| 6241 |
+
[
|
| 6242 |
+
0,
|
| 6243 |
+
0
|
| 6244 |
+
],
|
| 6245 |
+
[
|
| 6246 |
+
-35,
|
| 6247 |
+
0
|
| 6248 |
+
],
|
| 6249 |
+
[
|
| 6250 |
+
-35,
|
| 6251 |
+
-401.15905108747006
|
| 6252 |
+
],
|
| 6253 |
+
[
|
| 6254 |
+
424.7889046596829,
|
| 6255 |
+
-401.15905108747006
|
| 6256 |
+
]
|
| 6257 |
+
],
|
| 6258 |
+
"lastCommittedPoint": null,
|
| 6259 |
+
"startBinding": {
|
| 6260 |
+
"elementId": "d-Obq1jyhqw7abPHd6_i5",
|
| 6261 |
+
"fixedPoint": [
|
| 6262 |
+
-0.02272045928132014,
|
| 6263 |
+
0.4980737633349352
|
| 6264 |
+
],
|
| 6265 |
+
"focus": 0,
|
| 6266 |
+
"gap": 0
|
| 6267 |
+
},
|
| 6268 |
+
"endBinding": {
|
| 6269 |
+
"elementId": "lcNOx1PJ1LukZ3eN5WcGs",
|
| 6270 |
+
"fixedPoint": [
|
| 6271 |
+
-0.02717304102205375,
|
| 6272 |
+
0.49807376333493464
|
| 6273 |
+
],
|
| 6274 |
+
"focus": 0,
|
| 6275 |
+
"gap": 0
|
| 6276 |
+
},
|
| 6277 |
+
"startArrowhead": null,
|
| 6278 |
+
"endArrowhead": "arrow",
|
| 6279 |
+
"elbowed": true,
|
| 6280 |
+
"fixedSegments": null,
|
| 6281 |
+
"startIsSpecial": null,
|
| 6282 |
+
"endIsSpecial": null
|
| 6283 |
+
},
|
| 6284 |
+
{
|
| 6285 |
+
"id": "N-p9QoQ7IUIthn6hq46No",
|
| 6286 |
+
"type": "text",
|
| 6287 |
+
"x": 6106.646611046586,
|
| 6288 |
+
"y": 245.59254412216723,
|
| 6289 |
+
"width": 173.39991760253906,
|
| 6290 |
+
"height": 30,
|
| 6291 |
+
"angle": 0,
|
| 6292 |
+
"strokeColor": "#1e1e1e",
|
| 6293 |
+
"backgroundColor": "transparent",
|
| 6294 |
+
"fillStyle": "solid",
|
| 6295 |
+
"strokeWidth": 2,
|
| 6296 |
+
"strokeStyle": "solid",
|
| 6297 |
+
"roughness": 1,
|
| 6298 |
+
"opacity": 100,
|
| 6299 |
+
"groupIds": [],
|
| 6300 |
+
"frameId": null,
|
| 6301 |
+
"index": "b2O",
|
| 6302 |
+
"roundness": null,
|
| 6303 |
+
"seed": 121996329,
|
| 6304 |
+
"version": 51,
|
| 6305 |
+
"versionNonce": 1428605609,
|
| 6306 |
+
"isDeleted": false,
|
| 6307 |
+
"boundElements": [],
|
| 6308 |
+
"updated": 1741576898934,
|
| 6309 |
+
"link": null,
|
| 6310 |
+
"locked": false,
|
| 6311 |
+
"text": "Provides Data",
|
| 6312 |
+
"fontSize": 24,
|
| 6313 |
+
"fontFamily": 1,
|
| 6314 |
+
"textAlign": "center",
|
| 6315 |
+
"verticalAlign": "middle",
|
| 6316 |
+
"containerId": "3raa-F9gTwZ7ZG2sCiVMU",
|
| 6317 |
+
"originalText": "Provides Data",
|
| 6318 |
+
"autoResize": true,
|
| 6319 |
+
"lineHeight": 1.25
|
| 6320 |
+
},
|
| 6321 |
+
{
|
| 6322 |
+
"id": "fMbl9KWZiggWCvF5xaMRT",
|
| 6323 |
+
"type": "arrow",
|
| 6324 |
+
"x": 6648.296124741433,
|
| 6325 |
+
"y": 315.5520074838562,
|
| 6326 |
+
"width": 305.01656792595077,
|
| 6327 |
+
"height": 114.26783576421906,
|
| 6328 |
+
"angle": 0,
|
| 6329 |
+
"strokeColor": "#1e1e1e",
|
| 6330 |
+
"backgroundColor": "transparent",
|
| 6331 |
+
"fillStyle": "solid",
|
| 6332 |
+
"strokeWidth": 2,
|
| 6333 |
+
"strokeStyle": "solid",
|
| 6334 |
+
"roughness": 1,
|
| 6335 |
+
"opacity": 100,
|
| 6336 |
+
"groupIds": [],
|
| 6337 |
+
"frameId": null,
|
| 6338 |
+
"index": "b2P",
|
| 6339 |
+
"roundness": null,
|
| 6340 |
+
"seed": 1912476361,
|
| 6341 |
+
"version": 12,
|
| 6342 |
+
"versionNonce": 89957673,
|
| 6343 |
+
"isDeleted": false,
|
| 6344 |
+
"boundElements": [
|
| 6345 |
+
{
|
| 6346 |
+
"type": "text",
|
| 6347 |
+
"id": "TrZYRDh3Zbhh-Y8oME2XA"
|
| 6348 |
+
}
|
| 6349 |
+
],
|
| 6350 |
+
"updated": 1741576957670,
|
| 6351 |
+
"link": null,
|
| 6352 |
+
"locked": false,
|
| 6353 |
+
"points": [
|
| 6354 |
+
[
|
| 6355 |
+
0,
|
| 6356 |
+
0
|
| 6357 |
+
],
|
| 6358 |
+
[
|
| 6359 |
+
-305.01656792595077,
|
| 6360 |
+
0
|
| 6361 |
+
],
|
| 6362 |
+
[
|
| 6363 |
+
-305.01656792595077,
|
| 6364 |
+
114.26783576421906
|
| 6365 |
+
]
|
| 6366 |
+
],
|
| 6367 |
+
"lastCommittedPoint": null,
|
| 6368 |
+
"startBinding": {
|
| 6369 |
+
"elementId": "O4ynYwner8EHSv-cUHyx0",
|
| 6370 |
+
"fixedPoint": [
|
| 6371 |
+
-0.010639185722623509,
|
| 6372 |
+
0.4980737633349352
|
| 6373 |
+
],
|
| 6374 |
+
"focus": 0,
|
| 6375 |
+
"gap": 0
|
| 6376 |
+
},
|
| 6377 |
+
"endBinding": {
|
| 6378 |
+
"elementId": "d-Obq1jyhqw7abPHd6_i5",
|
| 6379 |
+
"fixedPoint": [
|
| 6380 |
+
0.4995455908143719,
|
| 6381 |
+
-0.0963118332532381
|
| 6382 |
+
],
|
| 6383 |
+
"focus": 0,
|
| 6384 |
+
"gap": 0
|
| 6385 |
+
},
|
| 6386 |
+
"startArrowhead": null,
|
| 6387 |
+
"endArrowhead": "arrow",
|
| 6388 |
+
"elbowed": true,
|
| 6389 |
+
"fixedSegments": null,
|
| 6390 |
+
"startIsSpecial": null,
|
| 6391 |
+
"endIsSpecial": null
|
| 6392 |
+
},
|
| 6393 |
+
{
|
| 6394 |
+
"id": "TrZYRDh3Zbhh-Y8oME2XA",
|
| 6395 |
+
"type": "text",
|
| 6396 |
+
"x": 6242.311600272514,
|
| 6397 |
+
"y": 301.04688353719985,
|
| 6398 |
+
"width": 201.9359130859375,
|
| 6399 |
+
"height": 30,
|
| 6400 |
+
"angle": 0,
|
| 6401 |
+
"strokeColor": "#1e1e1e",
|
| 6402 |
+
"backgroundColor": "transparent",
|
| 6403 |
+
"fillStyle": "solid",
|
| 6404 |
+
"strokeWidth": 2,
|
| 6405 |
+
"strokeStyle": "solid",
|
| 6406 |
+
"roughness": 1,
|
| 6407 |
+
"opacity": 100,
|
| 6408 |
+
"groupIds": [],
|
| 6409 |
+
"frameId": null,
|
| 6410 |
+
"index": "b2Q",
|
| 6411 |
+
"roundness": null,
|
| 6412 |
+
"seed": 1768413353,
|
| 6413 |
+
"version": 21,
|
| 6414 |
+
"versionNonce": 876256521,
|
| 6415 |
+
"isDeleted": false,
|
| 6416 |
+
"boundElements": [],
|
| 6417 |
+
"updated": 1741576917613,
|
| 6418 |
+
"link": null,
|
| 6419 |
+
"locked": false,
|
| 6420 |
+
"text": "Processes Tasks",
|
| 6421 |
+
"fontSize": 24,
|
| 6422 |
+
"fontFamily": 1,
|
| 6423 |
+
"textAlign": "center",
|
| 6424 |
+
"verticalAlign": "middle",
|
| 6425 |
+
"containerId": "fMbl9KWZiggWCvF5xaMRT",
|
| 6426 |
+
"originalText": "Processes Tasks",
|
| 6427 |
+
"autoResize": true,
|
| 6428 |
+
"lineHeight": 1.25
|
| 6429 |
+
},
|
| 6430 |
+
{
|
| 6431 |
+
"id": "QTMS_iw6t6ynVYi-LOM0h",
|
| 6432 |
+
"type": "arrow",
|
| 6433 |
+
"x": 6763.232639511854,
|
| 6434 |
+
"y": 342.66514912265495,
|
| 6435 |
+
"width": 60.73045401061245,
|
| 6436 |
+
"height": 87.15469412542029,
|
| 6437 |
+
"angle": 0,
|
| 6438 |
+
"strokeColor": "#1e1e1e",
|
| 6439 |
+
"backgroundColor": "transparent",
|
| 6440 |
+
"fillStyle": "solid",
|
| 6441 |
+
"strokeWidth": 2,
|
| 6442 |
+
"strokeStyle": "solid",
|
| 6443 |
+
"roughness": 1,
|
| 6444 |
+
"opacity": 100,
|
| 6445 |
+
"groupIds": [],
|
| 6446 |
+
"frameId": null,
|
| 6447 |
+
"index": "b2R",
|
| 6448 |
+
"roundness": null,
|
| 6449 |
+
"seed": 735937097,
|
| 6450 |
+
"version": 15,
|
| 6451 |
+
"versionNonce": 41839049,
|
| 6452 |
+
"isDeleted": false,
|
| 6453 |
+
"boundElements": [
|
| 6454 |
+
{
|
| 6455 |
+
"type": "text",
|
| 6456 |
+
"id": "izQ2ocL7VnsI6ZPX9Eijt"
|
| 6457 |
+
}
|
| 6458 |
+
],
|
| 6459 |
+
"updated": 1741576957670,
|
| 6460 |
+
"link": null,
|
| 6461 |
+
"locked": false,
|
| 6462 |
+
"points": [
|
| 6463 |
+
[
|
| 6464 |
+
0,
|
| 6465 |
+
0
|
| 6466 |
+
],
|
| 6467 |
+
[
|
| 6468 |
+
0,
|
| 6469 |
+
45.549451425552434
|
| 6470 |
+
],
|
| 6471 |
+
[
|
| 6472 |
+
-60.73045401061245,
|
| 6473 |
+
45.549451425552434
|
| 6474 |
+
],
|
| 6475 |
+
[
|
| 6476 |
+
-60.73045401061245,
|
| 6477 |
+
87.15469412542029
|
| 6478 |
+
]
|
| 6479 |
+
],
|
| 6480 |
+
"lastCommittedPoint": null,
|
| 6481 |
+
"startBinding": {
|
| 6482 |
+
"elementId": "O4ynYwner8EHSv-cUHyx0",
|
| 6483 |
+
"fixedPoint": [
|
| 6484 |
+
0.23392699966809058,
|
| 6485 |
+
1.020337038632418
|
| 6486 |
+
],
|
| 6487 |
+
"focus": 0,
|
| 6488 |
+
"gap": 0
|
| 6489 |
+
},
|
| 6490 |
+
"endBinding": {
|
| 6491 |
+
"elementId": "SLQjJ54OvcwHKgE0saBYW",
|
| 6492 |
+
"fixedPoint": [
|
| 6493 |
+
0.4996645050439854,
|
| 6494 |
+
-0.0963118332532381
|
| 6495 |
+
],
|
| 6496 |
+
"focus": 0,
|
| 6497 |
+
"gap": 0
|
| 6498 |
+
},
|
| 6499 |
+
"startArrowhead": null,
|
| 6500 |
+
"endArrowhead": "arrow",
|
| 6501 |
+
"elbowed": true,
|
| 6502 |
+
"fixedSegments": null,
|
| 6503 |
+
"startIsSpecial": null,
|
| 6504 |
+
"endIsSpecial": null
|
| 6505 |
+
},
|
| 6506 |
+
{
|
| 6507 |
+
"id": "izQ2ocL7VnsI6ZPX9Eijt",
|
| 6508 |
+
"type": "text",
|
| 6509 |
+
"x": 6700.622463165728,
|
| 6510 |
+
"y": 382.45947660155105,
|
| 6511 |
+
"width": 64.48989868164062,
|
| 6512 |
+
"height": 12.5,
|
| 6513 |
+
"angle": 0,
|
| 6514 |
+
"strokeColor": "#1e1e1e",
|
| 6515 |
+
"backgroundColor": "transparent",
|
| 6516 |
+
"fillStyle": "solid",
|
| 6517 |
+
"strokeWidth": 2,
|
| 6518 |
+
"strokeStyle": "solid",
|
| 6519 |
+
"roughness": 1,
|
| 6520 |
+
"opacity": 100,
|
| 6521 |
+
"groupIds": [],
|
| 6522 |
+
"frameId": null,
|
| 6523 |
+
"index": "b2S",
|
| 6524 |
+
"roundness": null,
|
| 6525 |
+
"seed": 1834674953,
|
| 6526 |
+
"version": 47,
|
| 6527 |
+
"versionNonce": 128581577,
|
| 6528 |
+
"isDeleted": false,
|
| 6529 |
+
"boundElements": [],
|
| 6530 |
+
"updated": 1741576945070,
|
| 6531 |
+
"link": null,
|
| 6532 |
+
"locked": false,
|
| 6533 |
+
"text": "Uploads Files",
|
| 6534 |
+
"fontSize": 10,
|
| 6535 |
+
"fontFamily": 1,
|
| 6536 |
+
"textAlign": "center",
|
| 6537 |
+
"verticalAlign": "middle",
|
| 6538 |
+
"containerId": "QTMS_iw6t6ynVYi-LOM0h",
|
| 6539 |
+
"originalText": "Uploads Files",
|
| 6540 |
+
"autoResize": true,
|
| 6541 |
+
"lineHeight": 1.25
|
| 6542 |
+
},
|
| 6543 |
+
{
|
| 6544 |
+
"id": "FCEWIaj7DywBOG0ukJrRU",
|
| 6545 |
+
"type": "arrow",
|
| 6546 |
+
"x": 6967.973318464036,
|
| 6547 |
+
"y": 343.24522078594714,
|
| 6548 |
+
"width": 105.87752356546389,
|
| 6549 |
+
"height": 86.5746224621281,
|
| 6550 |
+
"angle": 0,
|
| 6551 |
+
"strokeColor": "#1e1e1e",
|
| 6552 |
+
"backgroundColor": "transparent",
|
| 6553 |
+
"fillStyle": "solid",
|
| 6554 |
+
"strokeWidth": 2,
|
| 6555 |
+
"strokeStyle": "solid",
|
| 6556 |
+
"roughness": 1,
|
| 6557 |
+
"opacity": 100,
|
| 6558 |
+
"groupIds": [],
|
| 6559 |
+
"frameId": null,
|
| 6560 |
+
"index": "b2T",
|
| 6561 |
+
"roundness": null,
|
| 6562 |
+
"seed": 582166663,
|
| 6563 |
+
"version": 16,
|
| 6564 |
+
"versionNonce": 408267401,
|
| 6565 |
+
"isDeleted": false,
|
| 6566 |
+
"boundElements": [
|
| 6567 |
+
{
|
| 6568 |
+
"type": "text",
|
| 6569 |
+
"id": "iJzD-V1Ye1tztyDSMKCCB"
|
| 6570 |
+
}
|
| 6571 |
+
],
|
| 6572 |
+
"updated": 1741576972987,
|
| 6573 |
+
"link": null,
|
| 6574 |
+
"locked": false,
|
| 6575 |
+
"points": [
|
| 6576 |
+
[
|
| 6577 |
+
0,
|
| 6578 |
+
0
|
| 6579 |
+
],
|
| 6580 |
+
[
|
| 6581 |
+
0,
|
| 6582 |
+
44.969379762260246
|
| 6583 |
+
],
|
| 6584 |
+
[
|
| 6585 |
+
105.87752356546389,
|
| 6586 |
+
44.969379762260246
|
| 6587 |
+
],
|
| 6588 |
+
[
|
| 6589 |
+
105.87752356546389,
|
| 6590 |
+
86.5746224621281
|
| 6591 |
+
]
|
| 6592 |
+
],
|
| 6593 |
+
"lastCommittedPoint": null,
|
| 6594 |
+
"startBinding": {
|
| 6595 |
+
"elementId": "O4ynYwner8EHSv-cUHyx0",
|
| 6596 |
+
"fixedPoint": [
|
| 6597 |
+
0.6695818213377501,
|
| 6598 |
+
1.031510591694403
|
| 6599 |
+
],
|
| 6600 |
+
"focus": 0,
|
| 6601 |
+
"gap": 0
|
| 6602 |
+
},
|
| 6603 |
+
"endBinding": {
|
| 6604 |
+
"elementId": "SmhmRdO5OootjWvVdhZVC",
|
| 6605 |
+
"fixedPoint": [
|
| 6606 |
+
0.4996938546986774,
|
| 6607 |
+
-0.0963118332532381
|
| 6608 |
+
],
|
| 6609 |
+
"focus": 0,
|
| 6610 |
+
"gap": 0
|
| 6611 |
+
},
|
| 6612 |
+
"startArrowhead": null,
|
| 6613 |
+
"endArrowhead": "arrow",
|
| 6614 |
+
"elbowed": true,
|
| 6615 |
+
"fixedSegments": null,
|
| 6616 |
+
"startIsSpecial": null,
|
| 6617 |
+
"endIsSpecial": null
|
| 6618 |
+
},
|
| 6619 |
+
{
|
| 6620 |
+
"id": "iJzD-V1Ye1tztyDSMKCCB",
|
| 6621 |
+
"type": "text",
|
| 6622 |
+
"x": 6951.498681504092,
|
| 6623 |
+
"y": 380.0896005482074,
|
| 6624 |
+
"width": 138.82679748535156,
|
| 6625 |
+
"height": 16.25,
|
| 6626 |
+
"angle": 0,
|
| 6627 |
+
"strokeColor": "#1e1e1e",
|
| 6628 |
+
"backgroundColor": "transparent",
|
| 6629 |
+
"fillStyle": "solid",
|
| 6630 |
+
"strokeWidth": 2,
|
| 6631 |
+
"strokeStyle": "solid",
|
| 6632 |
+
"roughness": 1,
|
| 6633 |
+
"opacity": 100,
|
| 6634 |
+
"groupIds": [],
|
| 6635 |
+
"frameId": null,
|
| 6636 |
+
"index": "b2U",
|
| 6637 |
+
"roundness": null,
|
| 6638 |
+
"seed": 921728519,
|
| 6639 |
+
"version": 34,
|
| 6640 |
+
"versionNonce": 1066665031,
|
| 6641 |
+
"isDeleted": false,
|
| 6642 |
+
"boundElements": [],
|
| 6643 |
+
"updated": 1741576971298,
|
| 6644 |
+
"link": null,
|
| 6645 |
+
"locked": false,
|
| 6646 |
+
"text": "Triggers Notifications",
|
| 6647 |
+
"fontSize": 13,
|
| 6648 |
+
"fontFamily": 1,
|
| 6649 |
+
"textAlign": "center",
|
| 6650 |
+
"verticalAlign": "middle",
|
| 6651 |
+
"containerId": "FCEWIaj7DywBOG0ukJrRU",
|
| 6652 |
+
"originalText": "Triggers Notifications",
|
| 6653 |
+
"autoResize": true,
|
| 6654 |
+
"lineHeight": 1.25
|
| 6655 |
+
},
|
| 6656 |
+
{
|
| 6657 |
+
"id": "vZM--SBjM4zyowOgs3hDv",
|
| 6658 |
+
"type": "arrow",
|
| 6659 |
+
"x": 7128.25690278931,
|
| 6660 |
+
"y": 315.5520074838562,
|
| 6661 |
+
"width": 396.9623963771428,
|
| 6662 |
+
"height": 114.26783576421906,
|
| 6663 |
+
"angle": 0,
|
| 6664 |
+
"strokeColor": "#1e1e1e",
|
| 6665 |
+
"backgroundColor": "transparent",
|
| 6666 |
+
"fillStyle": "solid",
|
| 6667 |
+
"strokeWidth": 2,
|
| 6668 |
+
"strokeStyle": "solid",
|
| 6669 |
+
"roughness": 1,
|
| 6670 |
+
"opacity": 100,
|
| 6671 |
+
"groupIds": [],
|
| 6672 |
+
"frameId": null,
|
| 6673 |
+
"index": "b2V",
|
| 6674 |
+
"roundness": null,
|
| 6675 |
+
"seed": 1147991273,
|
| 6676 |
+
"version": 19,
|
| 6677 |
+
"versionNonce": 675995913,
|
| 6678 |
+
"isDeleted": false,
|
| 6679 |
+
"boundElements": [
|
| 6680 |
+
{
|
| 6681 |
+
"type": "text",
|
| 6682 |
+
"id": "10MDw0etYuPh4CpgAeTg0"
|
| 6683 |
+
}
|
| 6684 |
+
],
|
| 6685 |
+
"updated": 1741577001129,
|
| 6686 |
+
"link": null,
|
| 6687 |
+
"locked": false,
|
| 6688 |
+
"points": [
|
| 6689 |
+
[
|
| 6690 |
+
0,
|
| 6691 |
+
0
|
| 6692 |
+
],
|
| 6693 |
+
[
|
| 6694 |
+
396.9623963771428,
|
| 6695 |
+
0
|
| 6696 |
+
],
|
| 6697 |
+
[
|
| 6698 |
+
396.9623963771428,
|
| 6699 |
+
114.26783576421906
|
| 6700 |
+
]
|
| 6701 |
+
],
|
| 6702 |
+
"lastCommittedPoint": null,
|
| 6703 |
+
"startBinding": {
|
| 6704 |
+
"elementId": "O4ynYwner8EHSv-cUHyx0",
|
| 6705 |
+
"fixedPoint": [
|
| 6706 |
+
1.0106391857226238,
|
| 6707 |
+
0.4980737633349352
|
| 6708 |
+
],
|
| 6709 |
+
"focus": 0,
|
| 6710 |
+
"gap": 0
|
| 6711 |
+
},
|
| 6712 |
+
"endBinding": {
|
| 6713 |
+
"elementId": "-AKHuCF8e7h2_yxWV-Lgt",
|
| 6714 |
+
"fixedPoint": [
|
| 6715 |
+
0.49957777827906663,
|
| 6716 |
+
-0.0963118332532381
|
| 6717 |
+
],
|
| 6718 |
+
"focus": 0,
|
| 6719 |
+
"gap": 0
|
| 6720 |
+
},
|
| 6721 |
+
"startArrowhead": null,
|
| 6722 |
+
"endArrowhead": "arrow",
|
| 6723 |
+
"elbowed": true,
|
| 6724 |
+
"fixedSegments": null,
|
| 6725 |
+
"startIsSpecial": null,
|
| 6726 |
+
"endIsSpecial": null
|
| 6727 |
+
},
|
| 6728 |
+
{
|
| 6729 |
+
"id": "10MDw0etYuPh4CpgAeTg0",
|
| 6730 |
+
"type": "text",
|
| 6731 |
+
"x": 7451.974365084421,
|
| 6732 |
+
"y": 303.6770074838562,
|
| 6733 |
+
"width": 146.4898681640625,
|
| 6734 |
+
"height": 23.75,
|
| 6735 |
+
"angle": 0,
|
| 6736 |
+
"strokeColor": "#1e1e1e",
|
| 6737 |
+
"backgroundColor": "transparent",
|
| 6738 |
+
"fillStyle": "solid",
|
| 6739 |
+
"strokeWidth": 2,
|
| 6740 |
+
"strokeStyle": "solid",
|
| 6741 |
+
"roughness": 1,
|
| 6742 |
+
"opacity": 100,
|
| 6743 |
+
"groupIds": [],
|
| 6744 |
+
"frameId": null,
|
| 6745 |
+
"index": "b2W",
|
| 6746 |
+
"roundness": null,
|
| 6747 |
+
"seed": 2022324935,
|
| 6748 |
+
"version": 27,
|
| 6749 |
+
"versionNonce": 1790563783,
|
| 6750 |
+
"isDeleted": false,
|
| 6751 |
+
"boundElements": [],
|
| 6752 |
+
"updated": 1741576999044,
|
| 6753 |
+
"link": null,
|
| 6754 |
+
"locked": false,
|
| 6755 |
+
"text": "Logs Metadata",
|
| 6756 |
+
"fontSize": 19,
|
| 6757 |
+
"fontFamily": 1,
|
| 6758 |
+
"textAlign": "center",
|
| 6759 |
+
"verticalAlign": "middle",
|
| 6760 |
+
"containerId": "vZM--SBjM4zyowOgs3hDv",
|
| 6761 |
+
"originalText": "Logs Metadata",
|
| 6762 |
+
"autoResize": true,
|
| 6763 |
+
"lineHeight": 1.25
|
| 6764 |
+
},
|
| 6765 |
+
{
|
| 6766 |
+
"id": "F704_xUi4glbWWo1UMSZD",
|
| 6767 |
+
"type": "arrow",
|
| 6768 |
+
"x": 7401.898105937576,
|
| 6769 |
+
"y": 460.6771936125586,
|
| 6770 |
+
"width": 35.66286187147671,
|
| 6771 |
+
"height": 93.75823172077577,
|
| 6772 |
+
"angle": 0,
|
| 6773 |
+
"strokeColor": "#1e1e1e",
|
| 6774 |
+
"backgroundColor": "transparent",
|
| 6775 |
+
"fillStyle": "solid",
|
| 6776 |
+
"strokeWidth": 2,
|
| 6777 |
+
"strokeStyle": "solid",
|
| 6778 |
+
"roughness": 1,
|
| 6779 |
+
"opacity": 100,
|
| 6780 |
+
"groupIds": [],
|
| 6781 |
+
"frameId": null,
|
| 6782 |
+
"index": "b2X",
|
| 6783 |
+
"roundness": null,
|
| 6784 |
+
"seed": 495535463,
|
| 6785 |
+
"version": 157,
|
| 6786 |
+
"versionNonce": 772975431,
|
| 6787 |
+
"isDeleted": false,
|
| 6788 |
+
"boundElements": [],
|
| 6789 |
+
"updated": 1741577022658,
|
| 6790 |
+
"link": null,
|
| 6791 |
+
"locked": false,
|
| 6792 |
+
"points": [
|
| 6793 |
+
[
|
| 6794 |
+
0,
|
| 6795 |
+
0
|
| 6796 |
+
],
|
| 6797 |
+
[
|
| 6798 |
+
-35,
|
| 6799 |
+
0
|
| 6800 |
+
],
|
| 6801 |
+
[
|
| 6802 |
+
-35.66286187147671,
|
| 6803 |
+
93.75823172077577
|
| 6804 |
+
],
|
| 6805 |
+
[
|
| 6806 |
+
-0.9613871411438595,
|
| 6807 |
+
93.75823172077577
|
| 6808 |
+
]
|
| 6809 |
+
],
|
| 6810 |
+
"lastCommittedPoint": null,
|
| 6811 |
+
"startBinding": {
|
| 6812 |
+
"elementId": "-AKHuCF8e7h2_yxWV-Lgt",
|
| 6813 |
+
"fixedPoint": [
|
| 6814 |
+
-0.021111086046634895,
|
| 6815 |
+
0.4980737633349352
|
| 6816 |
+
],
|
| 6817 |
+
"focus": 0,
|
| 6818 |
+
"gap": 0
|
| 6819 |
+
},
|
| 6820 |
+
"endBinding": {
|
| 6821 |
+
"elementId": "zkIptPJSdTc7fyDulV4Nc",
|
| 6822 |
+
"fixedPoint": [
|
| 6823 |
+
-0.02242466713384723,
|
| 6824 |
+
0.4977272727272722
|
| 6825 |
+
],
|
| 6826 |
+
"focus": 0,
|
| 6827 |
+
"gap": 1
|
| 6828 |
+
},
|
| 6829 |
+
"startArrowhead": null,
|
| 6830 |
+
"endArrowhead": "arrow",
|
| 6831 |
+
"elbowed": true,
|
| 6832 |
+
"fixedSegments": null,
|
| 6833 |
+
"startIsSpecial": null,
|
| 6834 |
+
"endIsSpecial": null
|
| 6835 |
+
},
|
| 6836 |
+
{
|
| 6837 |
+
"id": "uuVJhiL1l0abY9oNZiD10",
|
| 6838 |
+
"type": "arrow",
|
| 6839 |
+
"x": 6954.423001342112,
|
| 6840 |
+
"y": 734.6366477728008,
|
| 6841 |
+
"width": 782.0369581018585,
|
| 6842 |
+
"height": 877.5492649993116,
|
| 6843 |
+
"angle": 0,
|
| 6844 |
+
"strokeColor": "#1e1e1e",
|
| 6845 |
+
"backgroundColor": "transparent",
|
| 6846 |
+
"fillStyle": "solid",
|
| 6847 |
+
"strokeWidth": 2,
|
| 6848 |
+
"strokeStyle": "solid",
|
| 6849 |
+
"roughness": 1,
|
| 6850 |
+
"opacity": 100,
|
| 6851 |
+
"groupIds": [],
|
| 6852 |
+
"frameId": null,
|
| 6853 |
+
"index": "b2Y",
|
| 6854 |
+
"roundness": null,
|
| 6855 |
+
"seed": 1564607801,
|
| 6856 |
+
"version": 173,
|
| 6857 |
+
"versionNonce": 1278975575,
|
| 6858 |
+
"isDeleted": false,
|
| 6859 |
+
"boundElements": [
|
| 6860 |
+
{
|
| 6861 |
+
"type": "text",
|
| 6862 |
+
"id": "_MRgU8xvGUK87XtjZCVdB"
|
| 6863 |
+
}
|
| 6864 |
+
],
|
| 6865 |
+
"updated": 1741577087661,
|
| 6866 |
+
"link": null,
|
| 6867 |
+
"locked": false,
|
| 6868 |
+
"points": [
|
| 6869 |
+
[
|
| 6870 |
+
0,
|
| 6871 |
+
0
|
| 6872 |
+
],
|
| 6873 |
+
[
|
| 6874 |
+
766.9953999550553,
|
| 6875 |
+
0
|
| 6876 |
+
],
|
| 6877 |
+
[
|
| 6878 |
+
766.9953999550553,
|
| 6879 |
+
-877.5492649993116
|
| 6880 |
+
],
|
| 6881 |
+
[
|
| 6882 |
+
-15.041558146803254,
|
| 6883 |
+
-877.5492649993116
|
| 6884 |
+
],
|
| 6885 |
+
[
|
| 6886 |
+
-15.041558146803254,
|
| 6887 |
+
-816.8845344422608
|
| 6888 |
+
]
|
| 6889 |
+
],
|
| 6890 |
+
"lastCommittedPoint": null,
|
| 6891 |
+
"startBinding": {
|
| 6892 |
+
"elementId": "hh-vJczGSvSCpkhjc-ZFd",
|
| 6893 |
+
"fixedPoint": [
|
| 6894 |
+
1.077294685990338,
|
| 6895 |
+
0.4977272727272722
|
| 6896 |
+
],
|
| 6897 |
+
"focus": 0,
|
| 6898 |
+
"gap": 0
|
| 6899 |
+
},
|
| 6900 |
+
"endBinding": {
|
| 6901 |
+
"elementId": "5rQwEFQ0OLt7lCCh6ROrl",
|
| 6902 |
+
"fixedPoint": [
|
| 6903 |
+
0.49959225118778955,
|
| 6904 |
+
-0.09631183325323865
|
| 6905 |
+
],
|
| 6906 |
+
"focus": 0,
|
| 6907 |
+
"gap": 0
|
| 6908 |
+
},
|
| 6909 |
+
"startArrowhead": null,
|
| 6910 |
+
"endArrowhead": "arrow",
|
| 6911 |
+
"elbowed": true,
|
| 6912 |
+
"fixedSegments": [
|
| 6913 |
+
{
|
| 6914 |
+
"index": 2,
|
| 6915 |
+
"start": [
|
| 6916 |
+
766.9953999550553,
|
| 6917 |
+
0
|
| 6918 |
+
],
|
| 6919 |
+
"end": [
|
| 6920 |
+
766.9953999550553,
|
| 6921 |
+
-877.5492649993116
|
| 6922 |
+
]
|
| 6923 |
+
},
|
| 6924 |
+
{
|
| 6925 |
+
"index": 3,
|
| 6926 |
+
"start": [
|
| 6927 |
+
766.9953999550553,
|
| 6928 |
+
-877.5492649993116
|
| 6929 |
+
],
|
| 6930 |
+
"end": [
|
| 6931 |
+
-15.041558146803254,
|
| 6932 |
+
-877.5492649993116
|
| 6933 |
+
]
|
| 6934 |
+
}
|
| 6935 |
+
],
|
| 6936 |
+
"startIsSpecial": false,
|
| 6937 |
+
"endIsSpecial": false
|
| 6938 |
+
},
|
| 6939 |
+
{
|
| 6940 |
+
"id": "_MRgU8xvGUK87XtjZCVdB",
|
| 6941 |
+
"type": "text",
|
| 6942 |
+
"x": 7603.199948843554,
|
| 6943 |
+
"y": -162.28761722651086,
|
| 6944 |
+
"width": 236.43690490722656,
|
| 6945 |
+
"height": 38.75,
|
| 6946 |
+
"angle": 0,
|
| 6947 |
+
"strokeColor": "#1e1e1e",
|
| 6948 |
+
"backgroundColor": "transparent",
|
| 6949 |
+
"fillStyle": "solid",
|
| 6950 |
+
"strokeWidth": 2,
|
| 6951 |
+
"strokeStyle": "solid",
|
| 6952 |
+
"roughness": 1,
|
| 6953 |
+
"opacity": 100,
|
| 6954 |
+
"groupIds": [],
|
| 6955 |
+
"frameId": null,
|
| 6956 |
+
"index": "b2Z",
|
| 6957 |
+
"roundness": null,
|
| 6958 |
+
"seed": 345379383,
|
| 6959 |
+
"version": 26,
|
| 6960 |
+
"versionNonce": 1436350681,
|
| 6961 |
+
"isDeleted": false,
|
| 6962 |
+
"boundElements": null,
|
| 6963 |
+
"updated": 1741577085801,
|
| 6964 |
+
"link": null,
|
| 6965 |
+
"locked": false,
|
| 6966 |
+
"text": "Research Query",
|
| 6967 |
+
"fontSize": 31,
|
| 6968 |
+
"fontFamily": 1,
|
| 6969 |
+
"textAlign": "center",
|
| 6970 |
+
"verticalAlign": "middle",
|
| 6971 |
+
"containerId": "uuVJhiL1l0abY9oNZiD10",
|
| 6972 |
+
"originalText": "Research Query",
|
| 6973 |
+
"autoResize": true,
|
| 6974 |
+
"lineHeight": 1.25
|
| 6975 |
+
},
|
| 6976 |
+
{
|
| 6977 |
+
"id": "RG4hRN73PDSPg9TvjEABI",
|
| 6978 |
+
"type": "arrow",
|
| 6979 |
+
"x": 6702.502185501241,
|
| 6980 |
+
"y": 636.8597301057443,
|
| 6981 |
+
"width": 177.233315840871,
|
| 6982 |
+
"height": 97.77691766705641,
|
| 6983 |
+
"angle": 0,
|
| 6984 |
+
"strokeColor": "#1e1e1e",
|
| 6985 |
+
"backgroundColor": "transparent",
|
| 6986 |
+
"fillStyle": "solid",
|
| 6987 |
+
"strokeWidth": 2,
|
| 6988 |
+
"strokeStyle": "solid",
|
| 6989 |
+
"roughness": 1,
|
| 6990 |
+
"opacity": 100,
|
| 6991 |
+
"groupIds": [],
|
| 6992 |
+
"frameId": null,
|
| 6993 |
+
"index": "b2a",
|
| 6994 |
+
"roundness": null,
|
| 6995 |
+
"seed": 316962359,
|
| 6996 |
+
"version": 30,
|
| 6997 |
+
"versionNonce": 1935335577,
|
| 6998 |
+
"isDeleted": false,
|
| 6999 |
+
"boundElements": [
|
| 7000 |
+
{
|
| 7001 |
+
"type": "text",
|
| 7002 |
+
"id": "p7DsZCuKDJztbRiwgmN-N"
|
| 7003 |
+
}
|
| 7004 |
+
],
|
| 7005 |
+
"updated": 1741577120714,
|
| 7006 |
+
"link": null,
|
| 7007 |
+
"locked": false,
|
| 7008 |
+
"points": [
|
| 7009 |
+
[
|
| 7010 |
+
0,
|
| 7011 |
+
0
|
| 7012 |
+
],
|
| 7013 |
+
[
|
| 7014 |
+
0,
|
| 7015 |
+
97.77691766705641
|
| 7016 |
+
],
|
| 7017 |
+
[
|
| 7018 |
+
177.233315840871,
|
| 7019 |
+
97.77691766705641
|
| 7020 |
+
]
|
| 7021 |
+
],
|
| 7022 |
+
"lastCommittedPoint": null,
|
| 7023 |
+
"startBinding": {
|
| 7024 |
+
"elementId": "W854nPSz0p4lut302JyEo",
|
| 7025 |
+
"fixedPoint": [
|
| 7026 |
+
0.49967859913202245,
|
| 7027 |
+
1.0963118332532389
|
| 7028 |
+
],
|
| 7029 |
+
"focus": 0,
|
| 7030 |
+
"gap": 0
|
| 7031 |
+
},
|
| 7032 |
+
"endBinding": {
|
| 7033 |
+
"elementId": "hh-vJczGSvSCpkhjc-ZFd",
|
| 7034 |
+
"fixedPoint": [
|
| 7035 |
+
-0.07729468599033816,
|
| 7036 |
+
0.4977272727272722
|
| 7037 |
+
],
|
| 7038 |
+
"focus": 0,
|
| 7039 |
+
"gap": 0
|
| 7040 |
+
},
|
| 7041 |
+
"startArrowhead": null,
|
| 7042 |
+
"endArrowhead": "arrow",
|
| 7043 |
+
"elbowed": true,
|
| 7044 |
+
"fixedSegments": null,
|
| 7045 |
+
"startIsSpecial": null,
|
| 7046 |
+
"endIsSpecial": null
|
| 7047 |
+
},
|
| 7048 |
+
{
|
| 7049 |
+
"id": "p7DsZCuKDJztbRiwgmN-N",
|
| 7050 |
+
"type": "text",
|
| 7051 |
+
"x": 6600.489734329366,
|
| 7052 |
+
"y": 719.0116477728008,
|
| 7053 |
+
"width": 204.02490234375,
|
| 7054 |
+
"height": 31.25,
|
| 7055 |
+
"angle": 0,
|
| 7056 |
+
"strokeColor": "#1e1e1e",
|
| 7057 |
+
"backgroundColor": "transparent",
|
| 7058 |
+
"fillStyle": "solid",
|
| 7059 |
+
"strokeWidth": 2,
|
| 7060 |
+
"strokeStyle": "solid",
|
| 7061 |
+
"roughness": 1,
|
| 7062 |
+
"opacity": 100,
|
| 7063 |
+
"groupIds": [],
|
| 7064 |
+
"frameId": null,
|
| 7065 |
+
"index": "b2b",
|
| 7066 |
+
"roundness": null,
|
| 7067 |
+
"seed": 701652887,
|
| 7068 |
+
"version": 22,
|
| 7069 |
+
"versionNonce": 973861687,
|
| 7070 |
+
"isDeleted": false,
|
| 7071 |
+
"boundElements": null,
|
| 7072 |
+
"updated": 1741577118639,
|
| 7073 |
+
"link": null,
|
| 7074 |
+
"locked": false,
|
| 7075 |
+
"text": "Delivers Content",
|
| 7076 |
+
"fontSize": 25,
|
| 7077 |
+
"fontFamily": 1,
|
| 7078 |
+
"textAlign": "center",
|
| 7079 |
+
"verticalAlign": "middle",
|
| 7080 |
+
"containerId": "RG4hRN73PDSPg9TvjEABI",
|
| 7081 |
+
"originalText": "Delivers Content",
|
| 7082 |
+
"autoResize": true,
|
| 7083 |
+
"lineHeight": 1.25
|
| 7084 |
+
},
|
| 7085 |
+
{
|
| 7086 |
+
"id": "upANbd9gbvPyDVaixKhNb",
|
| 7087 |
+
"type": "text",
|
| 7088 |
+
"x": 6551.937202258377,
|
| 7089 |
+
"y": -259.4249996465235,
|
| 7090 |
+
"width": 171.4839630126953,
|
| 7091 |
+
"height": 53.75,
|
| 7092 |
+
"angle": 0,
|
| 7093 |
+
"strokeColor": "#1e1e1e",
|
| 7094 |
+
"backgroundColor": "transparent",
|
| 7095 |
+
"fillStyle": "solid",
|
| 7096 |
+
"strokeWidth": 2,
|
| 7097 |
+
"strokeStyle": "solid",
|
| 7098 |
+
"roughness": 1,
|
| 7099 |
+
"opacity": 100,
|
| 7100 |
+
"groupIds": [],
|
| 7101 |
+
"frameId": null,
|
| 7102 |
+
"index": "b2c",
|
| 7103 |
+
"roundness": null,
|
| 7104 |
+
"seed": 631705881,
|
| 7105 |
+
"version": 58,
|
| 7106 |
+
"versionNonce": 1454583383,
|
| 7107 |
+
"isDeleted": false,
|
| 7108 |
+
"boundElements": null,
|
| 7109 |
+
"updated": 1741577165072,
|
| 7110 |
+
"link": null,
|
| 7111 |
+
"locked": false,
|
| 7112 |
+
"text": "Ex: AWS",
|
| 7113 |
+
"fontSize": 43,
|
| 7114 |
+
"fontFamily": 1,
|
| 7115 |
+
"textAlign": "left",
|
| 7116 |
+
"verticalAlign": "top",
|
| 7117 |
+
"containerId": null,
|
| 7118 |
+
"originalText": "Ex: AWS",
|
| 7119 |
+
"autoResize": true,
|
| 7120 |
+
"lineHeight": 1.25
|
| 7121 |
}
|
| 7122 |
],
|
| 7123 |
"appState": {
|
backend/.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
downloads/*
|
|
|
|
|
|
backend/app.py
CHANGED
|
@@ -1,20 +1,21 @@
|
|
| 1 |
# pip install asyncio eventlet
|
| 2 |
# pip install google-genai beautifulsoup4 selenium newspaper3k lxml_html_clean
|
| 3 |
from fastapi import FastAPI
|
|
|
|
| 4 |
import socketio
|
| 5 |
import json, logging
|
| 6 |
from knet import KNet
|
| 7 |
from scraper import CrawlForAIScraper, WebScraper
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
load_dotenv()
|
| 10 |
-
import asyncio
|
| 11 |
|
| 12 |
# Configure logging
|
| 13 |
logging.basicConfig(level=logging.INFO)
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
| 16 |
app = FastAPI()
|
| 17 |
-
|
|
|
|
| 18 |
sio = socketio.AsyncServer(cors_allowed_origins="*", ping_timeout=60, ping_interval=10, async_mode="asgi")
|
| 19 |
app.mount('/', socketio.ASGIApp(sio))
|
| 20 |
|
|
@@ -43,7 +44,7 @@ async def health_check(sid, data):
|
|
| 43 |
@sio.event
|
| 44 |
async def start_research(sid, data):
|
| 45 |
try:
|
| 46 |
-
data = json.loads(data)
|
| 47 |
topic = data.get("topic")
|
| 48 |
session_id = sid
|
| 49 |
logger.info(f"Starting research for client {session_id} on topic: {topic}")
|
|
@@ -56,25 +57,19 @@ async def start_research(sid, data):
|
|
| 56 |
logger.error(f"Error in progress callback: {str(e)}")
|
| 57 |
raise e
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
await sio.emit("research_complete", research_results, room=session_id)
|
| 63 |
-
except Exception as e:
|
| 64 |
-
logger.error(f"Research error: {str(e)}")
|
| 65 |
-
await sio.emit("error", {"message": str(e)}, room=session_id)
|
| 66 |
-
raise e
|
| 67 |
|
| 68 |
except Exception as e:
|
| 69 |
-
logger.error(f"
|
| 70 |
-
await sio.emit("error", {"message": str(e)}, room=
|
| 71 |
-
raise e
|
| 72 |
|
| 73 |
|
| 74 |
@sio.event
|
| 75 |
async def test(sid, data):
|
| 76 |
print("Testing...")
|
| 77 |
-
data = json.loads(data)
|
| 78 |
res = await knet.scraper._scrape_page(data["url"])
|
| 79 |
print(json.dumps(res, indent=2))
|
| 80 |
await scraper_instance.close()
|
|
|
|
| 1 |
# pip install asyncio eventlet
|
| 2 |
# pip install google-genai beautifulsoup4 selenium newspaper3k lxml_html_clean
|
| 3 |
from fastapi import FastAPI
|
| 4 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
import socketio
|
| 6 |
import json, logging
|
| 7 |
from knet import KNet
|
| 8 |
from scraper import CrawlForAIScraper, WebScraper
|
| 9 |
from dotenv import load_dotenv
|
| 10 |
load_dotenv()
|
|
|
|
| 11 |
|
| 12 |
# Configure logging
|
| 13 |
logging.basicConfig(level=logging.INFO)
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
| 16 |
app = FastAPI()
|
| 17 |
+
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
|
| 18 |
+
|
| 19 |
sio = socketio.AsyncServer(cors_allowed_origins="*", ping_timeout=60, ping_interval=10, async_mode="asgi")
|
| 20 |
app.mount('/', socketio.ASGIApp(sio))
|
| 21 |
|
|
|
|
| 44 |
@sio.event
|
| 45 |
async def start_research(sid, data):
|
| 46 |
try:
|
| 47 |
+
data = json.loads(data) if type(data) != dict else data
|
| 48 |
topic = data.get("topic")
|
| 49 |
session_id = sid
|
| 50 |
logger.info(f"Starting research for client {session_id} on topic: {topic}")
|
|
|
|
| 57 |
logger.error(f"Error in progress callback: {str(e)}")
|
| 58 |
raise e
|
| 59 |
|
| 60 |
+
research_results = await knet.conduct_research(topic, progress_callback)
|
| 61 |
+
logger.info(f"Research completed for topic: {topic}")
|
| 62 |
+
await sio.emit("research_complete", research_results, room=session_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
except Exception as e:
|
| 65 |
+
logger.error(f"Research error: {str(e)}")
|
| 66 |
+
await sio.emit("error", {"message": str(e)}, room=session_id)
|
|
|
|
| 67 |
|
| 68 |
|
| 69 |
@sio.event
|
| 70 |
async def test(sid, data):
|
| 71 |
print("Testing...")
|
| 72 |
+
data = json.loads(data) if type(data) != dict else data
|
| 73 |
res = await knet.scraper._scrape_page(data["url"])
|
| 74 |
print(json.dumps(res, indent=2))
|
| 75 |
await scraper_instance.close()
|
backend/knet.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
-
from typing import Dict, List,
|
|
|
|
| 2 |
import google.generativeai as genai
|
| 3 |
from google.ai.generativelanguage_v1beta.types import content
|
| 4 |
import logging
|
|
@@ -8,7 +9,6 @@ from datetime import datetime
|
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
from research_node import ResearchNode
|
| 10 |
from collections import deque
|
| 11 |
-
import asyncio
|
| 12 |
|
| 13 |
# Load environment variables
|
| 14 |
load_dotenv()
|
|
@@ -36,15 +36,40 @@ class KNet:
|
|
| 36 |
genai.configure(api_key=self.api_key)
|
| 37 |
|
| 38 |
# Keep both models with original configurations
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
self.llm = genai.GenerativeModel(
|
| 40 |
"gemini-2.0-flash-lite-preview-02-05",
|
| 41 |
-
generation_config=
|
|
|
|
| 42 |
)
|
| 43 |
self.ctx_researcher = []
|
| 44 |
|
| 45 |
self.research_manager = genai.GenerativeModel(
|
| 46 |
"gemini-2.0-flash-lite-preview-02-05",
|
| 47 |
-
generation_config=
|
|
|
|
| 48 |
)
|
| 49 |
self.ctx_manager = []
|
| 50 |
|
|
@@ -118,30 +143,37 @@ class KNet:
|
|
| 118 |
def _track_tokens(self, tokens: int) -> None:
|
| 119 |
self.token_count += tokens
|
| 120 |
|
| 121 |
-
def _should_branch_deeper(self, node: ResearchNode, topic: str) -> bool:
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
self._track_tokens(response.usage_metadata.total_token_count)
|
| 127 |
-
|
| 128 |
-
self.
|
| 129 |
-
|
| 130 |
-
# Research manager takes decision to proceed or not
|
| 131 |
-
prompt = self.branch_decision_prompt.format(
|
| 132 |
-
query=node.query,
|
| 133 |
-
depth=node.depth,
|
| 134 |
-
path=" -> ".join(node.get_path_to_root()),
|
| 135 |
-
findings="\n".join(self.ctx_manager),
|
| 136 |
-
)
|
| 137 |
-
response = self.research_manager.generate_content(
|
| 138 |
-
prompt, generation_config={**self.branch_schema}
|
| 139 |
-
)
|
| 140 |
-
self._track_tokens(response.usage_metadata.total_token_count)
|
| 141 |
-
result = json.loads(response.text)
|
| 142 |
-
self.logger.info(f"Branch decision for '{node.query}': {result['decision']}")
|
| 143 |
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
async def conduct_research(self, topic: str, progress_callback=None) -> Dict[str, Any]:
|
| 147 |
self.token_count = 0
|
|
@@ -184,7 +216,7 @@ class KNet:
|
|
| 184 |
self.logger.info(f"Research completed. Explored {len(explored_queries)} queries across {root_node.max_depth()} levels")
|
| 185 |
await progress.update(100, "Research complete!")
|
| 186 |
|
| 187 |
-
with open("output.json", "
|
| 188 |
json.dump(final_report, f, indent=2)
|
| 189 |
return final_report
|
| 190 |
|
|
@@ -192,23 +224,23 @@ class KNet:
|
|
| 192 |
self.logger.error(f"Research failed: {str(e)}")
|
| 193 |
raise e
|
| 194 |
|
| 195 |
-
def _analyze_and_branch(self, node: ResearchNode, topic: str) -> List[ResearchNode]:
|
| 196 |
-
|
| 197 |
-
|
|
|
|
| 198 |
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
|
| 208 |
-
|
| 209 |
-
|
| 210 |
|
| 211 |
-
try:
|
| 212 |
response = self.research_manager.generate_content(
|
| 213 |
analysis_prompt, generation_config={**self.analysis_schema}
|
| 214 |
)
|
|
@@ -227,56 +259,65 @@ class KNet:
|
|
| 227 |
return new_nodes
|
| 228 |
|
| 229 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
| 230 |
self.logger.error(f"Branch analysis failed: {str(e)}")
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
def _generate_final_report(self, root_node: ResearchNode) -> Dict[str, Any]:
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
return {
|
| 265 |
-
"
|
| 266 |
-
"
|
| 267 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
}
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
"
|
| 274 |
-
|
| 275 |
-
"research_tree": build_tree_structure(root_node),
|
| 276 |
-
"metadata": {
|
| 277 |
-
"total_queries": root_node.total_children(),
|
| 278 |
-
"total_sources": len(all_sources_data),
|
| 279 |
-
"max_depth_reached": root_node.max_depth(),
|
| 280 |
-
"total_tokens": self.token_count,
|
| 281 |
-
},
|
| 282 |
-
}
|
|
|
|
| 1 |
+
from typing import Dict, List, Any
|
| 2 |
+
from textwrap import dedent
|
| 3 |
import google.generativeai as genai
|
| 4 |
from google.ai.generativelanguage_v1beta.types import content
|
| 5 |
import logging
|
|
|
|
| 9 |
from dotenv import load_dotenv
|
| 10 |
from research_node import ResearchNode
|
| 11 |
from collections import deque
|
|
|
|
| 12 |
|
| 13 |
# Load environment variables
|
| 14 |
load_dotenv()
|
|
|
|
| 36 |
genai.configure(api_key=self.api_key)
|
| 37 |
|
| 38 |
# Keep both models with original configurations
|
| 39 |
+
generation_config = {"temperature": 0.9}
|
| 40 |
+
safe = [
|
| 41 |
+
{
|
| 42 |
+
"category": "HARM_CATEGORY_DANGEROUS",
|
| 43 |
+
"threshold": "BLOCK_NONE",
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"category": "HARM_CATEGORY_HARASSMENT",
|
| 47 |
+
"threshold": "BLOCK_NONE",
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"category": "HARM_CATEGORY_HATE_SPEECH",
|
| 51 |
+
"threshold": "BLOCK_NONE",
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
| 55 |
+
"threshold": "BLOCK_NONE",
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
| 59 |
+
"threshold": "BLOCK_NONE",
|
| 60 |
+
},
|
| 61 |
+
]
|
| 62 |
self.llm = genai.GenerativeModel(
|
| 63 |
"gemini-2.0-flash-lite-preview-02-05",
|
| 64 |
+
generation_config=generation_config,
|
| 65 |
+
safety_settings=safe,
|
| 66 |
)
|
| 67 |
self.ctx_researcher = []
|
| 68 |
|
| 69 |
self.research_manager = genai.GenerativeModel(
|
| 70 |
"gemini-2.0-flash-lite-preview-02-05",
|
| 71 |
+
generation_config=generation_config,
|
| 72 |
+
safety_settings=safe,
|
| 73 |
)
|
| 74 |
self.ctx_manager = []
|
| 75 |
|
|
|
|
| 143 |
def _track_tokens(self, tokens: int) -> None:
|
| 144 |
self.token_count += tokens
|
| 145 |
|
| 146 |
+
def _should_branch_deeper(self, node: ResearchNode, topic: str, retry_count=0) -> bool:
|
| 147 |
+
try:
|
| 148 |
+
# Generate summary of key findings into research_manager's context
|
| 149 |
+
if node.data:
|
| 150 |
+
findings = ("\n" + "-"*10 + "Next data" + "-"*10 + "\n").join([json.dumps(d, indent=2) for d in node.data])
|
| 151 |
+
response = self.llm.generate_content(f"Extract key findings from the following data related to the topic '{topic}':\n{findings}")
|
| 152 |
+
self._track_tokens(response.usage_metadata.total_token_count)
|
| 153 |
+
findings = response.text
|
| 154 |
+
self.ctx_manager.append(findings)
|
| 155 |
+
|
| 156 |
+
# Research manager takes decision to proceed or not
|
| 157 |
+
prompt = self.branch_decision_prompt.format(
|
| 158 |
+
query=node.query,
|
| 159 |
+
depth=node.depth,
|
| 160 |
+
path=" -> ".join(node.get_path_to_root()),
|
| 161 |
+
findings="\n".join(self.ctx_manager),
|
| 162 |
+
)
|
| 163 |
+
response = self.research_manager.generate_content(
|
| 164 |
+
prompt, generation_config={**self.branch_schema}
|
| 165 |
+
)
|
| 166 |
self._track_tokens(response.usage_metadata.total_token_count)
|
| 167 |
+
result = json.loads(response.text)
|
| 168 |
+
self.logger.info(f"Branch decision for '{node.query}': {result['decision']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
|
| 170 |
+
return result["decision"]
|
| 171 |
+
except Exception as e:
|
| 172 |
+
if result["candidates"][0]["finishReason"] == "RECITATION":
|
| 173 |
+
self.logger.error(f"Retrying branch decision: {str(e)}\nC:{retry_count/3}")
|
| 174 |
+
self._should_branch_deeper(node, topic, retry_count+1)
|
| 175 |
+
self.logger.error(f"Branch decision failed: {str(e)}")
|
| 176 |
+
raise e
|
| 177 |
|
| 178 |
async def conduct_research(self, topic: str, progress_callback=None) -> Dict[str, Any]:
|
| 179 |
self.token_count = 0
|
|
|
|
| 216 |
self.logger.info(f"Research completed. Explored {len(explored_queries)} queries across {root_node.max_depth()} levels")
|
| 217 |
await progress.update(100, "Research complete!")
|
| 218 |
|
| 219 |
+
with open("output.json", "a") as f:
|
| 220 |
json.dump(final_report, f, indent=2)
|
| 221 |
return final_report
|
| 222 |
|
|
|
|
| 224 |
self.logger.error(f"Research failed: {str(e)}")
|
| 225 |
raise e
|
| 226 |
|
| 227 |
+
def _analyze_and_branch(self, node: ResearchNode, topic: str, retry_count=0) -> List[ResearchNode]:
|
| 228 |
+
try:
|
| 229 |
+
if not node.data:
|
| 230 |
+
return []
|
| 231 |
|
| 232 |
+
analysis_prompt = dedent(f"""Based on the following findings about "{topic}", suggest new research directions.
|
| 233 |
+
Findings:
|
| 234 |
+
{json.dumps(self.ctx_manager, indent=2)}
|
| 235 |
|
| 236 |
+
Suggest up to {self.max_breadth} specific google search queries that would help data which:
|
| 237 |
+
- Builds upon these findings
|
| 238 |
+
- Explores different aspects
|
| 239 |
+
- Goes deeper into important details
|
| 240 |
|
| 241 |
+
Return as JSON array of objects with properties:
|
| 242 |
+
- query (string)""")
|
| 243 |
|
|
|
|
| 244 |
response = self.research_manager.generate_content(
|
| 245 |
analysis_prompt, generation_config={**self.analysis_schema}
|
| 246 |
)
|
|
|
|
| 259 |
return new_nodes
|
| 260 |
|
| 261 |
except Exception as e:
|
| 262 |
+
if result["candidates"][0]["finishReason"] == "RECITATION" and retry_count <= 3:
|
| 263 |
+
self.logger.error(f"Retrying analysis: {str(e)}\nC:{retry_count/3}")
|
| 264 |
+
self._analyze_and_branch(node, topic, retry_count+1)
|
| 265 |
self.logger.error(f"Branch analysis failed: {str(e)}")
|
| 266 |
+
raise e
|
| 267 |
+
|
| 268 |
+
def _generate_final_report(self, root_node: ResearchNode, retry_count=0) -> Dict[str, Any]:
|
| 269 |
+
try:
|
| 270 |
+
findings = "\n".join(self.ctx_manager)
|
| 271 |
+
with open("output.json", "w") as f:
|
| 272 |
+
f.write(findings)
|
| 273 |
+
prompt = f"""Generate a comprehensive report on the topic "{root_node.query}" based on the following research findings:
|
| 274 |
+
{findings}
|
| 275 |
+
"""
|
| 276 |
+
response = self.research_manager.generate_content(prompt)
|
| 277 |
+
self._track_tokens(response.usage_metadata.total_token_count)
|
| 278 |
+
|
| 279 |
+
# Collate multimedia content
|
| 280 |
+
media_content = {"images": [], "videos": [], "links": [], "references": []}
|
| 281 |
+
all_sources_data = root_node.get_all_data()
|
| 282 |
+
for data in all_sources_data:
|
| 283 |
+
if data.get("images"):
|
| 284 |
+
media_content["images"].extend(data["images"])
|
| 285 |
+
if data.get("videos"):
|
| 286 |
+
media_content["videos"].extend(data["videos"])
|
| 287 |
+
if data.get("links"):
|
| 288 |
+
media_content["links"].extend([{"url": l["href"], "text": l["text"]} for l in data["links"]])
|
| 289 |
+
# Deduplicate
|
| 290 |
+
media_content["images"] = list(set(media_content["images"]))
|
| 291 |
+
media_content["videos"] = list(set(media_content["videos"]))
|
| 292 |
+
media_content["links"] = list({json.dumps(d, sort_keys=True) for d in media_content["links"]})
|
| 293 |
+
media_content["links"] = [json.loads(d) for d in media_content["links"]]
|
| 294 |
+
|
| 295 |
+
# Build research tree structure
|
| 296 |
+
def build_tree_structure(node: ResearchNode) -> Dict:
|
| 297 |
+
if not node:
|
| 298 |
+
return {}
|
| 299 |
+
return {
|
| 300 |
+
"query": node.query,
|
| 301 |
+
"depth": node.depth,
|
| 302 |
+
"children": [build_tree_structure(child) for child in node.children],
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
return {
|
| 306 |
+
"topic": root_node.query,
|
| 307 |
+
"timestamp": datetime.now().isoformat(),
|
| 308 |
+
"content": response.text,
|
| 309 |
+
"media": media_content,
|
| 310 |
+
"research_tree": build_tree_structure(root_node),
|
| 311 |
+
"metadata": {
|
| 312 |
+
"total_queries": root_node.total_children(),
|
| 313 |
+
"total_sources": len(all_sources_data),
|
| 314 |
+
"max_depth_reached": root_node.max_depth(),
|
| 315 |
+
"total_tokens": self.token_count,
|
| 316 |
+
},
|
| 317 |
}
|
| 318 |
+
except Exception as e:
|
| 319 |
+
if response["candidates"][0]["finishReason"] == "RECITATION":
|
| 320 |
+
self.logger.error(f"Retrying final report: {str(e)}\nC:{retry_count/3}")
|
| 321 |
+
self._generate_final_report(root_node, retry_count+1)
|
| 322 |
+
self.logger.error(f"Error generating final report: {str(e)}")
|
| 323 |
+
raise e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/output.json
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/bun.lock
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/components.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
| 3 |
+
"style": "new-york",
|
| 4 |
+
"rsc": true,
|
| 5 |
+
"tsx": true,
|
| 6 |
+
"tailwind": {
|
| 7 |
+
"config": "tailwind.config.ts",
|
| 8 |
+
"css": "src/app/globals.css",
|
| 9 |
+
"baseColor": "slate",
|
| 10 |
+
"cssVariables": true,
|
| 11 |
+
"prefix": ""
|
| 12 |
+
},
|
| 13 |
+
"aliases": {
|
| 14 |
+
"components": "@/components",
|
| 15 |
+
"utils": "@/lib/utils",
|
| 16 |
+
"ui": "@/components/ui",
|
| 17 |
+
"lib": "@/lib",
|
| 18 |
+
"hooks": "@/hooks"
|
| 19 |
+
},
|
| 20 |
+
"iconLibrary": "lucide"
|
| 21 |
+
}
|
frontend/package.json
CHANGED
|
@@ -9,16 +9,40 @@
|
|
| 9 |
"lint": "next lint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
"react": "^18",
|
| 13 |
"react-dom": "^18",
|
| 14 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
},
|
| 16 |
"devDependencies": {
|
| 17 |
-
"typescript": "^5",
|
| 18 |
"@types/node": "^20",
|
| 19 |
"@types/react": "^18",
|
| 20 |
"@types/react-dom": "^18",
|
|
|
|
| 21 |
"postcss": "^8",
|
| 22 |
-
"tailwindcss": "^3.4.1"
|
|
|
|
| 23 |
}
|
| 24 |
}
|
|
|
|
| 9 |
"lint": "next lint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
+
"@radix-ui/react-avatar": "^1.1.3",
|
| 13 |
+
"@radix-ui/react-checkbox": "^1.1.4",
|
| 14 |
+
"@radix-ui/react-dialog": "^1.1.6",
|
| 15 |
+
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
| 16 |
+
"@radix-ui/react-label": "^2.1.2",
|
| 17 |
+
"@radix-ui/react-scroll-area": "^1.2.3",
|
| 18 |
+
"@radix-ui/react-select": "^2.1.6",
|
| 19 |
+
"@radix-ui/react-separator": "^1.1.2",
|
| 20 |
+
"@radix-ui/react-slot": "^1.1.2",
|
| 21 |
+
"@radix-ui/react-tabs": "^1.1.3",
|
| 22 |
+
"@radix-ui/react-tooltip": "^1.1.8",
|
| 23 |
+
"class-variance-authority": "^0.7.1",
|
| 24 |
+
"clsx": "^2.1.1",
|
| 25 |
+
"lucide-react": "^0.479.0",
|
| 26 |
+
"next": "14.2.24",
|
| 27 |
+
"next-themes": "^0.4.5",
|
| 28 |
"react": "^18",
|
| 29 |
"react-dom": "^18",
|
| 30 |
+
"react-markdown": "^10.1.0",
|
| 31 |
+
"react-resizable-panels": "^2.1.7",
|
| 32 |
+
"remark-gfm": "^4.0.1",
|
| 33 |
+
"shadcn": "^2.4.0-canary.12",
|
| 34 |
+
"socket.io-client": "^4.8.1",
|
| 35 |
+
"tailwind-merge": "^3.0.2",
|
| 36 |
+
"tailwindcss-animate": "^1.0.7",
|
| 37 |
+
"uuid": "^11.1.0"
|
| 38 |
},
|
| 39 |
"devDependencies": {
|
|
|
|
| 40 |
"@types/node": "^20",
|
| 41 |
"@types/react": "^18",
|
| 42 |
"@types/react-dom": "^18",
|
| 43 |
+
"@types/uuid": "^10.0.0",
|
| 44 |
"postcss": "^8",
|
| 45 |
+
"tailwindcss": "^3.4.1",
|
| 46 |
+
"typescript": "^5"
|
| 47 |
}
|
| 48 |
}
|
frontend/src/app/fonts/GeistMonoVF.woff
DELETED
|
Binary file (67.9 kB)
|
|
|
frontend/src/app/fonts/GeistVF.woff
DELETED
|
Binary file (66.3 kB)
|
|
|
frontend/src/app/globals.css
CHANGED
|
@@ -2,26 +2,78 @@
|
|
| 2 |
@tailwind components;
|
| 3 |
@tailwind utilities;
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
|
|
|
| 8 |
}
|
| 9 |
|
| 10 |
-
|
|
|
|
| 11 |
:root {
|
| 12 |
-
--background:
|
| 13 |
-
--foreground:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
}
|
| 15 |
-
}
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
| 26 |
}
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
@tailwind components;
|
| 3 |
@tailwind utilities;
|
| 4 |
|
| 5 |
+
@layer utilities {
|
| 6 |
+
.text-balance {
|
| 7 |
+
text-wrap: balance;
|
| 8 |
+
}
|
| 9 |
}
|
| 10 |
|
| 11 |
+
|
| 12 |
+
@layer base {
|
| 13 |
:root {
|
| 14 |
+
--background: 0 0% 100%;
|
| 15 |
+
--foreground: 222.2 84% 4.9%;
|
| 16 |
+
--card: 0 0% 100%;
|
| 17 |
+
--card-foreground: 222.2 84% 4.9%;
|
| 18 |
+
--popover: 0 0% 100%;
|
| 19 |
+
--popover-foreground: 222.2 84% 4.9%;
|
| 20 |
+
--primary: 222.2 47.4% 11.2%;
|
| 21 |
+
--primary-foreground: 210 40% 98%;
|
| 22 |
+
--secondary: 210 40% 96.1%;
|
| 23 |
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
| 24 |
+
--muted: 210 40% 96.1%;
|
| 25 |
+
--muted-foreground: 215.4 16.3% 46.9%;
|
| 26 |
+
--accent: 210 40% 96.1%;
|
| 27 |
+
--accent-foreground: 222.2 47.4% 11.2%;
|
| 28 |
+
--destructive: 0 84.2% 60.2%;
|
| 29 |
+
--destructive-foreground: 210 40% 98%;
|
| 30 |
+
--border: 214.3 31.8% 91.4%;
|
| 31 |
+
--input: 214.3 31.8% 91.4%;
|
| 32 |
+
--ring: 222.2 84% 4.9%;
|
| 33 |
+
--chart-1: 12 76% 61%;
|
| 34 |
+
--chart-2: 173 58% 39%;
|
| 35 |
+
--chart-3: 197 37% 24%;
|
| 36 |
+
--chart-4: 43 74% 66%;
|
| 37 |
+
--chart-5: 27 87% 67%;
|
| 38 |
+
--radius: 0.5rem;
|
| 39 |
}
|
|
|
|
| 40 |
|
| 41 |
+
.dark {
|
| 42 |
+
--background: 222.2 84% 4.9%;
|
| 43 |
+
--foreground: 210 40% 98%;
|
| 44 |
+
--card: 222.2 84% 4.9%;
|
| 45 |
+
--card-foreground: 210 40% 98%;
|
| 46 |
+
--popover: 222.2 84% 4.9%;
|
| 47 |
+
--popover-foreground: 210 40% 98%;
|
| 48 |
+
--primary: 210 40% 98%;
|
| 49 |
+
--primary-foreground: 222.2 47.4% 11.2%;
|
| 50 |
+
--secondary: 217.2 32.6% 17.5%;
|
| 51 |
+
--secondary-foreground: 210 40% 98%;
|
| 52 |
+
--muted: 217.2 32.6% 17.5%;
|
| 53 |
+
--muted-foreground: 215 20.2% 65.1%;
|
| 54 |
+
--accent: 217.2 32.6% 17.5%;
|
| 55 |
+
--accent-foreground: 210 40% 98%;
|
| 56 |
+
--destructive: 0 62.8% 30.6%;
|
| 57 |
+
--destructive-foreground: 210 40% 98%;
|
| 58 |
+
--border: 217.2 32.6% 17.5%;
|
| 59 |
+
--input: 217.2 32.6% 17.5%;
|
| 60 |
+
--ring: 212.7 26.8% 83.9%;
|
| 61 |
+
--chart-1: 220 70% 50%;
|
| 62 |
+
--chart-2: 160 60% 45%;
|
| 63 |
+
--chart-3: 30 80% 55%;
|
| 64 |
+
--chart-4: 280 65% 60%;
|
| 65 |
+
--chart-5: 340 75% 55%;
|
| 66 |
+
}
|
| 67 |
}
|
| 68 |
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@layer base {
|
| 72 |
+
* {
|
| 73 |
+
@apply border-border;
|
| 74 |
}
|
| 75 |
+
|
| 76 |
+
body {
|
| 77 |
+
@apply bg-background text-foreground;
|
| 78 |
+
}
|
| 79 |
+
}
|
frontend/src/app/layout.tsx
CHANGED
|
@@ -1,34 +1,19 @@
|
|
|
|
|
| 1 |
import type { Metadata } from "next";
|
| 2 |
-
import localFont from "next/font/local";
|
| 3 |
import "./globals.css";
|
| 4 |
|
| 5 |
-
const geistSans = localFont({
|
| 6 |
-
src: "./fonts/GeistVF.woff",
|
| 7 |
-
variable: "--font-geist-sans",
|
| 8 |
-
weight: "100 900",
|
| 9 |
-
});
|
| 10 |
-
const geistMono = localFont({
|
| 11 |
-
src: "./fonts/GeistMonoVF.woff",
|
| 12 |
-
variable: "--font-geist-mono",
|
| 13 |
-
weight: "100 900",
|
| 14 |
-
});
|
| 15 |
-
|
| 16 |
export const metadata: Metadata = {
|
| 17 |
-
title: "
|
| 18 |
-
description: "
|
| 19 |
};
|
| 20 |
|
| 21 |
-
export default function RootLayout({
|
| 22 |
-
children,
|
| 23 |
-
}: Readonly<{
|
| 24 |
-
children: React.ReactNode;
|
| 25 |
-
}>) {
|
| 26 |
return (
|
| 27 |
-
<html lang="en">
|
| 28 |
-
<body
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
</body>
|
| 33 |
</html>
|
| 34 |
);
|
|
|
|
| 1 |
+
import { ThemeProvider } from "@/components/theme-provider";
|
| 2 |
import type { Metadata } from "next";
|
|
|
|
| 3 |
import "./globals.css";
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
export const metadata: Metadata = {
|
| 6 |
+
title: "Research Chatbot",
|
| 7 |
+
description: "Deep research assistant with real-time updates",
|
| 8 |
};
|
| 9 |
|
| 10 |
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
return (
|
| 12 |
+
<html lang="en" suppressHydrationWarning>
|
| 13 |
+
<body>
|
| 14 |
+
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
| 15 |
+
{children}
|
| 16 |
+
</ThemeProvider>
|
| 17 |
</body>
|
| 18 |
</html>
|
| 19 |
);
|
frontend/src/app/page.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
export default function Home() {
|
| 2 |
-
return
|
| 3 |
-
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"></div>
|
| 4 |
-
);
|
| 5 |
}
|
|
|
|
| 1 |
+
import ChatInterface from '@/components/ChatInterface';
|
| 2 |
+
|
| 3 |
export default function Home() {
|
| 4 |
+
return <ChatInterface />;
|
|
|
|
|
|
|
| 5 |
}
|
frontend/src/components/ChatHistory.tsx
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
| 3 |
+
import { Message as MessageType } from "@/lib/types";
|
| 4 |
+
import { Loader2 } from "lucide-react";
|
| 5 |
+
import React, { useEffect, useRef } from "react";
|
| 6 |
+
import Message from "./Message";
|
| 7 |
+
|
| 8 |
+
interface ChatHistoryProps {
|
| 9 |
+
messages: MessageType[];
|
| 10 |
+
isLoading: boolean;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
const ChatHistory: React.FC<ChatHistoryProps> = ({ messages, isLoading }) => {
|
| 14 |
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
| 15 |
+
|
| 16 |
+
useEffect(() => {
|
| 17 |
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
| 18 |
+
}, [messages]);
|
| 19 |
+
|
| 20 |
+
return (
|
| 21 |
+
<ScrollArea tabIndex={-1}>
|
| 22 |
+
<div className="pb-24 focus-visible:outline-0" tabIndex={-1}>
|
| 23 |
+
{messages.length === 0 ? (
|
| 24 |
+
<div className="h-full flex flex-col items-center justify-center p-8 text-center">
|
| 25 |
+
<div className="max-w-md space-y-2">
|
| 26 |
+
<h2 className="text-2xl font-bold">Welcome to Deep Research</h2>
|
| 27 |
+
<p className="text-muted-foreground">Ask any research question to get started. The assistant will provide detailed answers backed by research and sources.</p>
|
| 28 |
+
</div>
|
| 29 |
+
</div>
|
| 30 |
+
) : (
|
| 31 |
+
messages.map((message) => <Message key={message.id} message={message} />)
|
| 32 |
+
)}
|
| 33 |
+
|
| 34 |
+
{isLoading && (
|
| 35 |
+
<div className="my-2 mx-4">
|
| 36 |
+
<div className="max-w-2xl mx-auto flex gap-4 relative">
|
| 37 |
+
<div className="h-8 w-8 rounded-full shrink-0 bg-muted flex items-center justify-center absolute -left-12 top-0">
|
| 38 |
+
<Loader2 className="h-5 w-5 animate-spin" />
|
| 39 |
+
</div>
|
| 40 |
+
<div className="flex-1">
|
| 41 |
+
<div className="flex items-center gap-2 mb-1">
|
| 42 |
+
<div className="text-xs text-muted-foreground">Just now</div>
|
| 43 |
+
<div className="font-medium">Research Assistant</div>
|
| 44 |
+
</div>
|
| 45 |
+
<div className="mt-1 bg-muted/50 p-3 rounded-2xl rounded-tl-sm">
|
| 46 |
+
<div className="flex space-x-2">
|
| 47 |
+
<div className="w-2 h-2 rounded-full bg-muted-foreground/30 animate-pulse"></div>
|
| 48 |
+
<div className="w-2 h-2 rounded-full bg-muted-foreground/30 animate-pulse" style={{ animationDelay: "300ms" }}></div>
|
| 49 |
+
<div className="w-2 h-2 rounded-full bg-muted-foreground/30 animate-pulse" style={{ animationDelay: "600ms" }}></div>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
</div>
|
| 55 |
+
)}
|
| 56 |
+
|
| 57 |
+
<div ref={messagesEndRef} />
|
| 58 |
+
</div>
|
| 59 |
+
</ScrollArea>
|
| 60 |
+
);
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
+
export default ChatHistory;
|
frontend/src/components/ChatInterface.tsx
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
| 3 |
+
import { disconnectSocket, getSocket, initializeSocket } from "@/lib/socket";
|
| 4 |
+
import { ChatData, ChatState, Conversation, Message, ResearchOptions, ResearchResults, StatusUpdate } from "@/lib/types";
|
| 5 |
+
import { useEffect, useState } from "react";
|
| 6 |
+
import { v4 as uuidv4 } from "uuid";
|
| 7 |
+
import ChatHistory from "./ChatHistory";
|
| 8 |
+
import MessageInput from "./MessageInput";
|
| 9 |
+
import ResearchControls from "./ResearchControls";
|
| 10 |
+
import ChatLayout from "./ui/ChatLayout";
|
| 11 |
+
import ConversationList from "./ui/ConversationList";
|
| 12 |
+
|
| 13 |
+
const saveToStorage = (data: ChatData) => {
|
| 14 |
+
if (typeof window !== "undefined") {
|
| 15 |
+
localStorage.setItem("chatData", JSON.stringify(data));
|
| 16 |
+
}
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
const loadFromStorage = (): ChatData => {
|
| 20 |
+
if (typeof window === "undefined") {
|
| 21 |
+
return { conversations: [], currentConversationId: null };
|
| 22 |
+
}
|
| 23 |
+
const data = localStorage.getItem("chatData");
|
| 24 |
+
if (!data) {
|
| 25 |
+
return { conversations: [], currentConversationId: null };
|
| 26 |
+
}
|
| 27 |
+
try {
|
| 28 |
+
const parsed = JSON.parse(data);
|
| 29 |
+
return {
|
| 30 |
+
conversations: Array.isArray(parsed.conversations) ? parsed.conversations : [],
|
| 31 |
+
currentConversationId: parsed.currentConversationId,
|
| 32 |
+
};
|
| 33 |
+
} catch (e) {
|
| 34 |
+
return { conversations: [], currentConversationId: null };
|
| 35 |
+
}
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
const ChatInterface = () => {
|
| 39 |
+
const [chatState, setChatState] = useState<ChatState>({ messages: [], isLoading: false, error: null });
|
| 40 |
+
const [conversations, setConversations] = useState<Conversation[]>([]);
|
| 41 |
+
const [currentConversationId, setCurrentConversationId] = useState<string | null>(null);
|
| 42 |
+
|
| 43 |
+
const [researchOptions, setResearchOptions] = useState<ResearchOptions>({
|
| 44 |
+
depth: "basic",
|
| 45 |
+
sources: true,
|
| 46 |
+
citations: false,
|
| 47 |
+
});
|
| 48 |
+
|
| 49 |
+
// Initialize socket once
|
| 50 |
+
useEffect(() => {
|
| 51 |
+
const socket = initializeSocket();
|
| 52 |
+
|
| 53 |
+
socket.on("connect", () => {
|
| 54 |
+
console.log("Connected to research server");
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
socket.on("disconnect", () => {
|
| 58 |
+
console.log("Disconnected from research server");
|
| 59 |
+
});
|
| 60 |
+
|
| 61 |
+
socket.on("status", (data: StatusUpdate) => {
|
| 62 |
+
setChatState((prevState) => {
|
| 63 |
+
const messages = [...prevState.messages];
|
| 64 |
+
const progressText = `(${data.progress}%) ${data.message}`;
|
| 65 |
+
|
| 66 |
+
// Find the last assistant message that is a progress update
|
| 67 |
+
const lastProgressIndex = messages.findLastIndex((msg) => msg.role === "assistant" && msg.content.includes("%)"));
|
| 68 |
+
|
| 69 |
+
if (lastProgressIndex !== -1) {
|
| 70 |
+
// Update existing progress message
|
| 71 |
+
messages[lastProgressIndex] = {
|
| 72 |
+
...messages[lastProgressIndex],
|
| 73 |
+
content: progressText,
|
| 74 |
+
};
|
| 75 |
+
} else {
|
| 76 |
+
// Add new progress message
|
| 77 |
+
messages.push({
|
| 78 |
+
id: uuidv4(),
|
| 79 |
+
content: progressText,
|
| 80 |
+
role: "assistant",
|
| 81 |
+
timestamp: new Date(),
|
| 82 |
+
});
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
return {
|
| 86 |
+
...prevState,
|
| 87 |
+
messages,
|
| 88 |
+
isLoading: true,
|
| 89 |
+
};
|
| 90 |
+
});
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
socket.on("research_complete", (results: ResearchResults) => {
|
| 94 |
+
setChatState((prevState) => {
|
| 95 |
+
const messages = [...prevState.messages];
|
| 96 |
+
|
| 97 |
+
// Remove the last progress message if it exists
|
| 98 |
+
const lastProgressIndex = messages.findLastIndex((msg) => msg.role === "assistant" && msg.content.includes("%)"));
|
| 99 |
+
if (lastProgressIndex !== -1) {
|
| 100 |
+
messages.splice(lastProgressIndex, 1);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
// Format research stats and response
|
| 104 |
+
const stats = [`Total Queries: ${results.metadata.total_queries}`, `Sources Used: ${results.metadata.total_sources}`, `Search Depth: ${results.metadata.max_depth_reached}`].join(" | ");
|
| 105 |
+
|
| 106 |
+
const formattedResponse = [results.content, `\n\n---\n**Research Stats:**\n${stats}`, results.media?.images?.length ? `\n\n**Relevant Images:**\n${results.media.images.join("\n")}` : ""].join("");
|
| 107 |
+
|
| 108 |
+
const newMessages = [
|
| 109 |
+
...messages,
|
| 110 |
+
{
|
| 111 |
+
id: uuidv4(),
|
| 112 |
+
content: formattedResponse,
|
| 113 |
+
role: "assistant" as const,
|
| 114 |
+
timestamp: new Date(results.timestamp),
|
| 115 |
+
},
|
| 116 |
+
];
|
| 117 |
+
|
| 118 |
+
return {
|
| 119 |
+
...prevState,
|
| 120 |
+
isLoading: false,
|
| 121 |
+
messages: newMessages,
|
| 122 |
+
};
|
| 123 |
+
});
|
| 124 |
+
});
|
| 125 |
+
|
| 126 |
+
socket.on("error", (error: { message: string }) => {
|
| 127 |
+
setChatState((prevState) => ({
|
| 128 |
+
...prevState,
|
| 129 |
+
error: error.message,
|
| 130 |
+
isLoading: false,
|
| 131 |
+
}));
|
| 132 |
+
});
|
| 133 |
+
|
| 134 |
+
return () => {
|
| 135 |
+
disconnectSocket();
|
| 136 |
+
};
|
| 137 |
+
}, []); // Empty dependency array
|
| 138 |
+
|
| 139 |
+
// Load initial data
|
| 140 |
+
useEffect(() => {
|
| 141 |
+
const data = loadFromStorage();
|
| 142 |
+
setConversations(data.conversations);
|
| 143 |
+
setCurrentConversationId(data.currentConversationId);
|
| 144 |
+
|
| 145 |
+
if (data.currentConversationId) {
|
| 146 |
+
const currentConv = data.conversations.find((c) => c.id === data.currentConversationId);
|
| 147 |
+
if (currentConv) {
|
| 148 |
+
setChatState((prev) => ({ ...prev, messages: currentConv.messages }));
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
}, []);
|
| 152 |
+
|
| 153 |
+
useEffect(() => {
|
| 154 |
+
const currentConv = conversations.find((c) => c.id === currentConversationId);
|
| 155 |
+
|
| 156 |
+
const data: ChatData = {
|
| 157 |
+
conversations: conversations.map((conv) => ({
|
| 158 |
+
...conv,
|
| 159 |
+
messages: conv.id === currentConversationId ? chatState.messages : conv.messages || [],
|
| 160 |
+
})),
|
| 161 |
+
currentConversationId,
|
| 162 |
+
};
|
| 163 |
+
|
| 164 |
+
saveToStorage(data);
|
| 165 |
+
}, [conversations, currentConversationId, chatState.messages]);
|
| 166 |
+
|
| 167 |
+
const handleSendMessage = (content: string) => {
|
| 168 |
+
if (!content.trim()) return;
|
| 169 |
+
|
| 170 |
+
let conversationId = currentConversationId;
|
| 171 |
+
const newMessage: Message = {
|
| 172 |
+
id: uuidv4(),
|
| 173 |
+
content,
|
| 174 |
+
role: "user",
|
| 175 |
+
timestamp: new Date(),
|
| 176 |
+
};
|
| 177 |
+
|
| 178 |
+
// Create a new conversation if none exists
|
| 179 |
+
if (!conversationId) {
|
| 180 |
+
conversationId = uuidv4();
|
| 181 |
+
setCurrentConversationId(conversationId);
|
| 182 |
+
setConversations((prev) => [
|
| 183 |
+
{
|
| 184 |
+
id: conversationId as string,
|
| 185 |
+
title: content.length > 30 ? `${content.substring(0, 30)}...` : content,
|
| 186 |
+
lastUpdated: new Date().toISOString(),
|
| 187 |
+
messages: [newMessage],
|
| 188 |
+
active: true,
|
| 189 |
+
},
|
| 190 |
+
...prev.map((c) => ({ ...c, active: false })),
|
| 191 |
+
]);
|
| 192 |
+
} else {
|
| 193 |
+
// Update the existing conversation
|
| 194 |
+
setConversations((prev) =>
|
| 195 |
+
prev.map((conv) => ({
|
| 196 |
+
...conv,
|
| 197 |
+
lastUpdated: conv.id === conversationId ? new Date().toISOString() : conv.lastUpdated,
|
| 198 |
+
active: conv.id === conversationId,
|
| 199 |
+
messages: conv.id === conversationId ? [...(conv.messages || []), newMessage] : conv.messages || [],
|
| 200 |
+
}))
|
| 201 |
+
);
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
setChatState((prevState) => ({
|
| 205 |
+
...prevState,
|
| 206 |
+
messages: [...prevState.messages, newMessage],
|
| 207 |
+
isLoading: true,
|
| 208 |
+
error: null,
|
| 209 |
+
}));
|
| 210 |
+
|
| 211 |
+
// Send message to server via socket
|
| 212 |
+
try {
|
| 213 |
+
const socket = getSocket();
|
| 214 |
+
socket.emit("start_research", {
|
| 215 |
+
topic: content,
|
| 216 |
+
});
|
| 217 |
+
} catch (error) {
|
| 218 |
+
setChatState((prevState) => ({
|
| 219 |
+
...prevState,
|
| 220 |
+
error: "Failed to connect to research server",
|
| 221 |
+
isLoading: false,
|
| 222 |
+
}));
|
| 223 |
+
}
|
| 224 |
+
};
|
| 225 |
+
|
| 226 |
+
const handleNewConversation = () => {
|
| 227 |
+
setCurrentConversationId(null);
|
| 228 |
+
setChatState({
|
| 229 |
+
messages: [],
|
| 230 |
+
isLoading: false,
|
| 231 |
+
error: null,
|
| 232 |
+
});
|
| 233 |
+
};
|
| 234 |
+
|
| 235 |
+
const handleSelectConversation = (id: string) => {
|
| 236 |
+
const data = loadFromStorage();
|
| 237 |
+
const conversation = data.conversations.find((c) => c.id === id);
|
| 238 |
+
|
| 239 |
+
setCurrentConversationId(id);
|
| 240 |
+
setChatState((prev) => ({
|
| 241 |
+
...prev,
|
| 242 |
+
messages: conversation?.messages || [],
|
| 243 |
+
isLoading: false,
|
| 244 |
+
error: null,
|
| 245 |
+
}));
|
| 246 |
+
setConversations((prev) =>
|
| 247 |
+
prev.map((conv) => ({
|
| 248 |
+
...conv,
|
| 249 |
+
active: conv.id === id,
|
| 250 |
+
}))
|
| 251 |
+
);
|
| 252 |
+
};
|
| 253 |
+
|
| 254 |
+
const sidebar = <ConversationList conversations={conversations} onNewConversation={handleNewConversation} onSelectConversation={handleSelectConversation} />;
|
| 255 |
+
|
| 256 |
+
const mainContent = (
|
| 257 |
+
<div className="flex flex-col w-full h-full relative" tabIndex={-1}>
|
| 258 |
+
<ChatHistory messages={chatState.messages} isLoading={chatState.isLoading} />
|
| 259 |
+
|
| 260 |
+
{chatState.error && (
|
| 261 |
+
<Alert variant="destructive" className="mx-2 my-2 mb-28 w-90 bg-red-950 text-red-50">
|
| 262 |
+
<AlertTitle>Error</AlertTitle>
|
| 263 |
+
<AlertDescription>{chatState.error}</AlertDescription>
|
| 264 |
+
</Alert>
|
| 265 |
+
)}
|
| 266 |
+
|
| 267 |
+
<MessageInput onSendMessage={handleSendMessage} isLoading={chatState.isLoading} />
|
| 268 |
+
</div>
|
| 269 |
+
);
|
| 270 |
+
|
| 271 |
+
return <ChatLayout sidebar={sidebar} mainContent={mainContent} settingsPanel={<ResearchControls options={researchOptions} onOptionChange={setResearchOptions} />} />;
|
| 272 |
+
};
|
| 273 |
+
|
| 274 |
+
export default ChatInterface;
|
frontend/src/components/Message.tsx
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Avatar } from "@/components/ui/avatar";
|
| 2 |
+
import { Button } from "@/components/ui/button";
|
| 3 |
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
| 4 |
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
| 5 |
+
import { Message as MessageType } from "@/lib/types";
|
| 6 |
+
import { Bot, Copy, MoreHorizontal, User2 } from "lucide-react";
|
| 7 |
+
import React from "react";
|
| 8 |
+
import ReactMarkdown from "react-markdown";
|
| 9 |
+
import remarkGfm from "remark-gfm";
|
| 10 |
+
|
| 11 |
+
const MarkdownComponents: Record<string, React.ComponentType<any>> = {
|
| 12 |
+
h1: ({ children }) => <h1 className="text-2xl font-bold mb-4">{children}</h1>,
|
| 13 |
+
h2: ({ children }) => <h2 className="text-xl font-bold mb-3">{children}</h2>,
|
| 14 |
+
h3: ({ children }) => <h3 className="text-lg font-bold mb-3">{children}</h3>,
|
| 15 |
+
p: ({ children }) => <p className="mb-4 last:mb-0">{children}</p>,
|
| 16 |
+
ul: ({ children }) => <ul className="list-disc ml-6 mb-4">{children}</ul>,
|
| 17 |
+
ol: ({ children }) => <ol className="list-decimal ml-6 mb-4">{children}</ol>,
|
| 18 |
+
li: ({ children }) => <li className="mb-1">{children}</li>,
|
| 19 |
+
code: ({ node, inline, className, children, ...props }) => (
|
| 20 |
+
<code className={`${inline ? "bg-muted px-1 py-0.5 rounded-md text-sm" : "block bg-muted/50 p-3 rounded-lg text-sm overflow-x-auto"}`} {...props}>
|
| 21 |
+
{children}
|
| 22 |
+
</code>
|
| 23 |
+
),
|
| 24 |
+
pre: ({ children }) => <pre className="bg-transparent p-0">{children}</pre>,
|
| 25 |
+
a: ({ children, href }) => (
|
| 26 |
+
<a href={href} className="text-primary hover:underline" target="_blank" rel="noopener noreferrer">
|
| 27 |
+
{children}
|
| 28 |
+
</a>
|
| 29 |
+
),
|
| 30 |
+
blockquote: ({ children }) => <blockquote className="border-l-4 border-border pl-4 italic my-4">{children}</blockquote>,
|
| 31 |
+
table: ({ children }) => <table className="w-full border-collapse my-4 border-slate-300 dark:border-slate-200">{children}</table>,
|
| 32 |
+
thead: ({ children }) => <thead className="bg-muted/50">{children}</thead>,
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
interface MessageProps {
|
| 36 |
+
message: MessageType;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
const Message: React.FC<MessageProps> = ({ message }) => {
|
| 40 |
+
const isUser = message.role === "user";
|
| 41 |
+
const isProgress = message.content.includes("%)") && message.role === "assistant";
|
| 42 |
+
|
| 43 |
+
const copyToClipboard = () => {
|
| 44 |
+
navigator.clipboard.writeText(message.content);
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
return (
|
| 48 |
+
<div className="py-2 px-4">
|
| 49 |
+
<div className={`max-w-2xl mx-auto flex gap-4 relative ${isUser ? "flex-row-reverse" : ""}`}>
|
| 50 |
+
<Avatar className={`h-8 w-8 shrink-0 absolute justify-center item-center ${isUser ? "-right-12" : "-left-12"} top-0`}>{isUser ? <User2 className="h-5 w-5" /> : <Bot className="h-5 w-5" />}</Avatar>
|
| 51 |
+
|
| 52 |
+
<div className={`flex-1 ${isUser ? "items-end" : "items-start"}`}>
|
| 53 |
+
<div className={`flex items-center gap-2 mb-1 ${isUser ? "justify-end" : "justify-start"}`}>
|
| 54 |
+
<div className="text-xs text-muted-foreground">{new Date(message.timestamp).toLocaleTimeString()}</div>
|
| 55 |
+
<div className="font-medium">{isUser ? "You" : "Research Assistant"}</div>
|
| 56 |
+
|
| 57 |
+
{!isUser && (
|
| 58 |
+
<div className="ml-auto flex items-center gap-2">
|
| 59 |
+
<TooltipProvider>
|
| 60 |
+
<Tooltip>
|
| 61 |
+
<TooltipTrigger asChild>
|
| 62 |
+
<Button size="icon" variant="ghost" className="h-8 w-8" onClick={copyToClipboard}>
|
| 63 |
+
<Copy className="h-4 w-4" />
|
| 64 |
+
</Button>
|
| 65 |
+
</TooltipTrigger>
|
| 66 |
+
<TooltipContent>Copy to clipboard</TooltipContent>
|
| 67 |
+
</Tooltip>
|
| 68 |
+
</TooltipProvider>
|
| 69 |
+
|
| 70 |
+
<DropdownMenu>
|
| 71 |
+
<DropdownMenuTrigger asChild>
|
| 72 |
+
<Button size="icon" variant="ghost" className="h-8 w-8">
|
| 73 |
+
<MoreHorizontal className="h-4 w-4" />
|
| 74 |
+
</Button>
|
| 75 |
+
</DropdownMenuTrigger>
|
| 76 |
+
<DropdownMenuContent align="end">
|
| 77 |
+
<DropdownMenuItem onClick={copyToClipboard}>Copy text</DropdownMenuItem>
|
| 78 |
+
<DropdownMenuItem>Show sources</DropdownMenuItem>
|
| 79 |
+
<DropdownMenuItem>View in visualizations</DropdownMenuItem>
|
| 80 |
+
</DropdownMenuContent>
|
| 81 |
+
</DropdownMenu>
|
| 82 |
+
</div>
|
| 83 |
+
)}
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
<div className={`mt-1 max-w-none ${isUser ? "bg-slate-300 dark:bg-slate-200 dark:text-background text-foreground" : isProgress ? "bg-muted/30" : "bg-muted/50"} p-3 rounded-2xl ${isUser ? "rounded-tr-sm" : "rounded-tl-sm"}`} style={{ overflowWrap: "anywhere" }}>
|
| 87 |
+
<ReactMarkdown remarkPlugins={[remarkGfm]} components={MarkdownComponents}>
|
| 88 |
+
{message.content}
|
| 89 |
+
</ReactMarkdown>
|
| 90 |
+
</div>
|
| 91 |
+
</div>
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
);
|
| 95 |
+
};
|
| 96 |
+
|
| 97 |
+
export default Message;
|
frontend/src/components/MessageInput.tsx
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { Button } from "@/components/ui/button";
|
| 3 |
+
import { AutosizeTextarea } from "@/components/ui/textarea";
|
| 4 |
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
| 5 |
+
import { Mic, Paperclip, Send } from "lucide-react";
|
| 6 |
+
import React, { useState } from "react";
|
| 7 |
+
|
| 8 |
+
interface MessageInputProps {
|
| 9 |
+
onSendMessage: (content: string) => void;
|
| 10 |
+
isLoading: boolean;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
const MessageInput: React.FC<MessageInputProps> = ({ onSendMessage, isLoading }) => {
|
| 14 |
+
const [message, setMessage] = useState("");
|
| 15 |
+
|
| 16 |
+
const handleMessageChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
| 17 |
+
setMessage(e.target.value);
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
const handleSubmit = (e: React.FormEvent) => {
|
| 21 |
+
e.preventDefault();
|
| 22 |
+
if (message.trim() && !isLoading) {
|
| 23 |
+
onSendMessage(message);
|
| 24 |
+
setMessage("");
|
| 25 |
+
}
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
| 29 |
+
if (e.key === "Enter" && !e.shiftKey) {
|
| 30 |
+
e.preventDefault();
|
| 31 |
+
handleSubmit(e);
|
| 32 |
+
}
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
return (
|
| 36 |
+
<div className="p-4 absolute bottom-0 left-0 right-0 bg-transparent mb-4">
|
| 37 |
+
<form onSubmit={handleSubmit} className="max-w-4xl mx-auto">
|
| 38 |
+
<div className="relative flex items-center bg-background shadow-lg rounded-[2rem] border overflow-hidden h-full">
|
| 39 |
+
<AutosizeTextarea placeholder="Ask a research question..." maxHeight={500} minHeight={52} className="pr-36 pl-6 py-4 font-medium border-none h-auto resize-none" value={message} onChange={handleMessageChange} onKeyDown={handleKeyDown} disabled={isLoading} rows={1} autoFocus />
|
| 40 |
+
|
| 41 |
+
<div className="absolute right-3 flex items-center gap-2 h-full">
|
| 42 |
+
<TooltipProvider>
|
| 43 |
+
<Tooltip>
|
| 44 |
+
<TooltipTrigger asChild>
|
| 45 |
+
<Button type="button" size="icon" variant="ghost" className="h-9 w-9" disabled={isLoading}>
|
| 46 |
+
<Paperclip className="h-4 w-4" />
|
| 47 |
+
</Button>
|
| 48 |
+
</TooltipTrigger>
|
| 49 |
+
<TooltipContent>Attach files (coming soon)</TooltipContent>
|
| 50 |
+
</Tooltip>
|
| 51 |
+
</TooltipProvider>
|
| 52 |
+
|
| 53 |
+
<TooltipProvider>
|
| 54 |
+
<Tooltip>
|
| 55 |
+
<TooltipTrigger asChild>
|
| 56 |
+
<Button type="button" size="icon" variant="ghost" className="h-9 w-9" disabled={isLoading}>
|
| 57 |
+
<Mic className="h-4 w-4" />
|
| 58 |
+
</Button>
|
| 59 |
+
</TooltipTrigger>
|
| 60 |
+
<TooltipContent>Voice input (coming soon)</TooltipContent>
|
| 61 |
+
</Tooltip>
|
| 62 |
+
</TooltipProvider>
|
| 63 |
+
|
| 64 |
+
<Button type="submit" size="icon" className="h-9 w-9 rounded-full" disabled={isLoading || !message.trim()}>
|
| 65 |
+
<Send className="h-4 w-4" />
|
| 66 |
+
<span className="sr-only">Send message</span>
|
| 67 |
+
</Button>
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
</form>
|
| 71 |
+
</div>
|
| 72 |
+
);
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
export default MessageInput;
|
frontend/src/components/ResearchControls.tsx
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Checkbox } from "@/components/ui/checkbox";
|
| 2 |
+
import { Label } from "@/components/ui/label";
|
| 3 |
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
| 4 |
+
import { Separator } from "@/components/ui/separator";
|
| 5 |
+
import { ResearchOptions } from "@/lib/types";
|
| 6 |
+
import React from "react";
|
| 7 |
+
|
| 8 |
+
interface ResearchControlsProps {
|
| 9 |
+
options: ResearchOptions;
|
| 10 |
+
onOptionChange: (options: ResearchOptions) => void;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
const ResearchControls: React.FC<ResearchControlsProps> = ({ options, onOptionChange }) => {
|
| 14 |
+
return (
|
| 15 |
+
<div className="space-y-6">
|
| 16 |
+
<div className="space-y-3">
|
| 17 |
+
<Label htmlFor="research-depth">Research Depth</Label>
|
| 18 |
+
<Select value={options.depth} onValueChange={(value: ResearchOptions["depth"]) => onOptionChange({ ...options, depth: value })}>
|
| 19 |
+
<SelectTrigger id="research-depth" className="w-full">
|
| 20 |
+
<SelectValue placeholder="Select depth" />
|
| 21 |
+
</SelectTrigger>
|
| 22 |
+
<SelectContent>
|
| 23 |
+
<SelectItem value="basic">Basic</SelectItem>
|
| 24 |
+
<SelectItem value="intermediate">Intermediate</SelectItem>
|
| 25 |
+
<SelectItem value="deep">Deep</SelectItem>
|
| 26 |
+
</SelectContent>
|
| 27 |
+
</Select>
|
| 28 |
+
<p className="text-xs text-muted-foreground">Determines how extensively the assistant will research your query.</p>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
<Separator />
|
| 32 |
+
|
| 33 |
+
<div className="space-y-3">
|
| 34 |
+
<Label>Inclusions</Label>
|
| 35 |
+
|
| 36 |
+
<div className="flex items-center space-x-2">
|
| 37 |
+
<Checkbox id="sources" checked={options.sources} onCheckedChange={(checked) => onOptionChange({ ...options, sources: checked as boolean })} />
|
| 38 |
+
<Label htmlFor="sources" className="text-sm font-normal">
|
| 39 |
+
Include sources
|
| 40 |
+
</Label>
|
| 41 |
+
</div>
|
| 42 |
+
|
| 43 |
+
<div className="flex items-center space-x-2">
|
| 44 |
+
<Checkbox id="citations" checked={options.citations} onCheckedChange={(checked) => onOptionChange({ ...options, citations: checked as boolean })} />
|
| 45 |
+
<Label htmlFor="citations" className="text-sm font-normal">
|
| 46 |
+
Include citations
|
| 47 |
+
</Label>
|
| 48 |
+
</div>
|
| 49 |
+
</div>
|
| 50 |
+
|
| 51 |
+
<Separator />
|
| 52 |
+
|
| 53 |
+
<div className="space-y-3">
|
| 54 |
+
<Label>Coming Soon</Label>
|
| 55 |
+
<div className="flex items-center space-x-2">
|
| 56 |
+
<Checkbox id="visualize" disabled />
|
| 57 |
+
<Label htmlFor="visualize" className="text-sm font-normal text-muted-foreground">
|
| 58 |
+
Generate visualizations
|
| 59 |
+
</Label>
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<div className="flex items-center space-x-2">
|
| 63 |
+
<Checkbox id="video-content" disabled />
|
| 64 |
+
<Label htmlFor="video-content" className="text-sm font-normal text-muted-foreground">
|
| 65 |
+
Include video content
|
| 66 |
+
</Label>
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
);
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
export default ResearchControls;
|
frontend/src/components/theme-provider.tsx
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
| 4 |
+
import * as React from "react";
|
| 5 |
+
|
| 6 |
+
export function ThemeProvider({ children, ...props }: React.ComponentProps<typeof NextThemesProvider>) {
|
| 7 |
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
| 8 |
+
}
|
frontend/src/components/ui/ChatLayout.tsx
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Button } from "@/components/ui/button";
|
| 2 |
+
import { Card } from "@/components/ui/card";
|
| 3 |
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
| 4 |
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
| 5 |
+
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
| 6 |
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
| 7 |
+
import { LayoutGrid, MessageCircle, Settings } from "lucide-react";
|
| 8 |
+
import React from "react";
|
| 9 |
+
import { ThemeToggle } from "./ThemeToggle";
|
| 10 |
+
|
| 11 |
+
interface ChatLayoutProps {
|
| 12 |
+
sidebar: React.ReactNode;
|
| 13 |
+
mainContent: React.ReactNode;
|
| 14 |
+
settingsPanel: React.ReactNode;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsPanel }) => {
|
| 18 |
+
return (
|
| 19 |
+
<div className="h-screen flex flex-col">
|
| 20 |
+
<header className="border-b-2 h-14 flex items-center px-6">
|
| 21 |
+
<h1 className="text-xl font-semibold">Deep Research Assistant</h1>
|
| 22 |
+
<div className="flex-1" />
|
| 23 |
+
<ThemeToggle />
|
| 24 |
+
<Sheet>
|
| 25 |
+
<SheetTrigger asChild>
|
| 26 |
+
<Button variant="ghost" size="icon">
|
| 27 |
+
<Settings size={20} />
|
| 28 |
+
</Button>
|
| 29 |
+
</SheetTrigger>
|
| 30 |
+
<SheetContent>
|
| 31 |
+
<div className="py-4">
|
| 32 |
+
<h2 className="text-lg font-semibold mb-4">Research Settings</h2>
|
| 33 |
+
{settingsPanel}
|
| 34 |
+
</div>
|
| 35 |
+
</SheetContent>
|
| 36 |
+
</Sheet>
|
| 37 |
+
</header>
|
| 38 |
+
|
| 39 |
+
<div className="flex-1 overflow-hidden">
|
| 40 |
+
<ResizablePanelGroup direction="horizontal">
|
| 41 |
+
<ResizablePanel defaultSize={20} minSize={15} maxSize={30} className="hidden md:block">
|
| 42 |
+
<Card className="h-full rounded-none border-r border-t-0 border-l-0 border-b-0">
|
| 43 |
+
<ScrollArea className="h-full">{sidebar}</ScrollArea>
|
| 44 |
+
</Card>
|
| 45 |
+
</ResizablePanel>
|
| 46 |
+
|
| 47 |
+
<ResizableHandle withHandle className="hidden md:flex" />
|
| 48 |
+
|
| 49 |
+
<ResizablePanel defaultSize={80}>
|
| 50 |
+
<Tabs defaultValue="chat" className="h-full flex flex-col">
|
| 51 |
+
<div className="p-4">
|
| 52 |
+
<TabsList className="">
|
| 53 |
+
<TabsTrigger value="chat" className="flex items-center gap-2">
|
| 54 |
+
<MessageCircle size={16} />
|
| 55 |
+
<span>Chat</span>
|
| 56 |
+
</TabsTrigger>
|
| 57 |
+
<TabsTrigger value="visualizations" className="flex items-center gap-2">
|
| 58 |
+
<LayoutGrid size={16} />
|
| 59 |
+
<span>Visualizations</span>
|
| 60 |
+
</TabsTrigger>
|
| 61 |
+
</TabsList>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<TabsContent value="chat" className="overflow-auto flex flex-1" tabIndex={-1}>
|
| 65 |
+
{mainContent}
|
| 66 |
+
</TabsContent>
|
| 67 |
+
|
| 68 |
+
<TabsContent value="visualizations" className="p-4 flex-1">
|
| 69 |
+
<div className="h-full flex items-center justify-center text-muted-foreground">
|
| 70 |
+
<div className="text-center">
|
| 71 |
+
<LayoutGrid className="mx-auto h-12 w-12 mb-4 opacity-30" />
|
| 72 |
+
<h3 className="text-lg font-medium mb-2">Visualizations Coming Soon</h3>
|
| 73 |
+
<p>View graphs, charts, and other visual representations of your research data.</p>
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
</TabsContent>
|
| 77 |
+
</Tabs>
|
| 78 |
+
</ResizablePanel>
|
| 79 |
+
</ResizablePanelGroup>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
);
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
export default ChatLayout;
|
frontend/src/components/ui/ConversationList.tsx
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { Button } from "@/components/ui/button";
|
| 3 |
+
import { MessageSquare, PlusCircle } from "lucide-react";
|
| 4 |
+
import React from "react";
|
| 5 |
+
|
| 6 |
+
interface Conversation {
|
| 7 |
+
id: string;
|
| 8 |
+
title: string;
|
| 9 |
+
lastUpdated: string;
|
| 10 |
+
active?: boolean;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
interface ConversationListProps {
|
| 14 |
+
conversations: Conversation[];
|
| 15 |
+
onNewConversation: () => void;
|
| 16 |
+
onSelectConversation: (id: string) => void;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
const ConversationList: React.FC<ConversationListProps> = ({ conversations, onNewConversation, onSelectConversation }) => {
|
| 20 |
+
const handleSelectConversation = (id: string) => {
|
| 21 |
+
onSelectConversation(id);
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
+
return (
|
| 25 |
+
<div className="flex flex-col h-full py-2">
|
| 26 |
+
<div className="px-4 py-2">
|
| 27 |
+
<Button onClick={onNewConversation} className="w-full justify-start" variant="outline">
|
| 28 |
+
<PlusCircle className="mr-2 h-4 w-4" />
|
| 29 |
+
New Research
|
| 30 |
+
</Button>
|
| 31 |
+
</div>
|
| 32 |
+
|
| 33 |
+
<div className="px-2 py-2">
|
| 34 |
+
<h2 className="text-sm font-semibold px-2 mb-2">Recent Research</h2>
|
| 35 |
+
<div className="space-y-1">
|
| 36 |
+
{conversations.length === 0 ? (
|
| 37 |
+
<p className="text-sm text-muted-foreground px-2">No conversations yet</p>
|
| 38 |
+
) : (
|
| 39 |
+
conversations.map((conversation) => (
|
| 40 |
+
<Button key={conversation.id} variant={conversation.active ? "secondary" : "ghost"} className={`w-full justify-start text-left truncate ${conversation.active ? "bg-accent" : ""}`} onClick={() => handleSelectConversation(conversation.id)}>
|
| 41 |
+
<MessageSquare className="mr-2 h-4 w-4 shrink-0" />
|
| 42 |
+
<span className="truncate">{conversation.title}</span>
|
| 43 |
+
</Button>
|
| 44 |
+
))
|
| 45 |
+
)}
|
| 46 |
+
</div>
|
| 47 |
+
</div>
|
| 48 |
+
</div>
|
| 49 |
+
);
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
export default ConversationList;
|
frontend/src/components/ui/ThemeToggle.tsx
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Button } from "@/components/ui/button";
|
| 2 |
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
| 3 |
+
import { Laptop, Moon, Sun } from "lucide-react";
|
| 4 |
+
import { useTheme } from "next-themes";
|
| 5 |
+
|
| 6 |
+
export function ThemeToggle() {
|
| 7 |
+
const { setTheme } = useTheme();
|
| 8 |
+
|
| 9 |
+
return (
|
| 10 |
+
<DropdownMenu>
|
| 11 |
+
<DropdownMenuTrigger asChild>
|
| 12 |
+
<Button variant="ghost" size="icon">
|
| 13 |
+
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 transition-all dark:-rotate-90 dark:hidden" />
|
| 14 |
+
<Moon className="absolute h-[1.2rem] w-[1.2rem] hidden rotate-90 transition-all dark:rotate-0 dark:block" />
|
| 15 |
+
<span className="sr-only">Toggle theme</span>
|
| 16 |
+
</Button>
|
| 17 |
+
</DropdownMenuTrigger>
|
| 18 |
+
<DropdownMenuContent align="end">
|
| 19 |
+
<DropdownMenuItem onClick={() => setTheme("light")}>
|
| 20 |
+
<Sun className="mr-2 h-4 w-4" />
|
| 21 |
+
<span>Light</span>
|
| 22 |
+
</DropdownMenuItem>
|
| 23 |
+
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
| 24 |
+
<Moon className="mr-2 h-4 w-4" />
|
| 25 |
+
<span>Dark</span>
|
| 26 |
+
</DropdownMenuItem>
|
| 27 |
+
<DropdownMenuItem onClick={() => setTheme("system")}>
|
| 28 |
+
<Laptop className="mr-2 h-4 w-4" />
|
| 29 |
+
<span>System</span>
|
| 30 |
+
</DropdownMenuItem>
|
| 31 |
+
</DropdownMenuContent>
|
| 32 |
+
</DropdownMenu>
|
| 33 |
+
);
|
| 34 |
+
}
|
frontend/src/components/ui/alert.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { cva, type VariantProps } from "class-variance-authority";
|
| 2 |
+
import * as React from "react";
|
| 3 |
+
|
| 4 |
+
import { cn } from "@/lib/utils";
|
| 5 |
+
|
| 6 |
+
const alertVariants = cva("relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", {
|
| 7 |
+
variants: {
|
| 8 |
+
variant: {
|
| 9 |
+
default: "bg-background text-foreground",
|
| 10 |
+
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
| 11 |
+
},
|
| 12 |
+
},
|
| 13 |
+
defaultVariants: {
|
| 14 |
+
variant: "default",
|
| 15 |
+
},
|
| 16 |
+
});
|
| 17 |
+
|
| 18 |
+
const Alert = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>>(({ className, variant, ...props }, ref) => <div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />);
|
| 19 |
+
Alert.displayName = "Alert";
|
| 20 |
+
|
| 21 |
+
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(({ className, ...props }, ref) => <h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />);
|
| 22 |
+
AlertTitle.displayName = "AlertTitle";
|
| 23 |
+
|
| 24 |
+
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(({ className, ...props }, ref) => <div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />);
|
| 25 |
+
AlertDescription.displayName = "AlertDescription";
|
| 26 |
+
|
| 27 |
+
export { Alert, AlertDescription, AlertTitle };
|
frontend/src/components/ui/avatar.tsx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
const Avatar = React.forwardRef<
|
| 9 |
+
React.ElementRef<typeof AvatarPrimitive.Root>,
|
| 10 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
| 11 |
+
>(({ className, ...props }, ref) => (
|
| 12 |
+
<AvatarPrimitive.Root
|
| 13 |
+
ref={ref}
|
| 14 |
+
className={cn(
|
| 15 |
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
| 16 |
+
className
|
| 17 |
+
)}
|
| 18 |
+
{...props}
|
| 19 |
+
/>
|
| 20 |
+
))
|
| 21 |
+
Avatar.displayName = AvatarPrimitive.Root.displayName
|
| 22 |
+
|
| 23 |
+
const AvatarImage = React.forwardRef<
|
| 24 |
+
React.ElementRef<typeof AvatarPrimitive.Image>,
|
| 25 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
| 26 |
+
>(({ className, ...props }, ref) => (
|
| 27 |
+
<AvatarPrimitive.Image
|
| 28 |
+
ref={ref}
|
| 29 |
+
className={cn("aspect-square h-full w-full", className)}
|
| 30 |
+
{...props}
|
| 31 |
+
/>
|
| 32 |
+
))
|
| 33 |
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
| 34 |
+
|
| 35 |
+
const AvatarFallback = React.forwardRef<
|
| 36 |
+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
| 37 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
| 38 |
+
>(({ className, ...props }, ref) => (
|
| 39 |
+
<AvatarPrimitive.Fallback
|
| 40 |
+
ref={ref}
|
| 41 |
+
className={cn(
|
| 42 |
+
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
| 43 |
+
className
|
| 44 |
+
)}
|
| 45 |
+
{...props}
|
| 46 |
+
/>
|
| 47 |
+
))
|
| 48 |
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
| 49 |
+
|
| 50 |
+
export { Avatar, AvatarImage, AvatarFallback }
|
frontend/src/components/ui/button.tsx
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import { Slot } from "@radix-ui/react-slot"
|
| 3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 4 |
+
|
| 5 |
+
import { cn } from "@/lib/utils"
|
| 6 |
+
|
| 7 |
+
const buttonVariants = cva(
|
| 8 |
+
"inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
| 9 |
+
{
|
| 10 |
+
variants: {
|
| 11 |
+
variant: {
|
| 12 |
+
default:
|
| 13 |
+
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
| 14 |
+
destructive:
|
| 15 |
+
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
| 16 |
+
outline:
|
| 17 |
+
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
| 18 |
+
secondary:
|
| 19 |
+
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
| 20 |
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
| 21 |
+
link: "text-primary underline-offset-4 hover:underline",
|
| 22 |
+
},
|
| 23 |
+
size: {
|
| 24 |
+
default: "h-9 px-4 py-2",
|
| 25 |
+
sm: "h-8 rounded-md px-3 text-xs",
|
| 26 |
+
lg: "h-10 rounded-md px-8",
|
| 27 |
+
icon: "h-9 w-9",
|
| 28 |
+
},
|
| 29 |
+
},
|
| 30 |
+
defaultVariants: {
|
| 31 |
+
variant: "default",
|
| 32 |
+
size: "default",
|
| 33 |
+
},
|
| 34 |
+
}
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
export interface ButtonProps
|
| 38 |
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
| 39 |
+
VariantProps<typeof buttonVariants> {
|
| 40 |
+
asChild?: boolean
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
| 44 |
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
| 45 |
+
const Comp = asChild ? Slot : "button"
|
| 46 |
+
return (
|
| 47 |
+
<Comp
|
| 48 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
| 49 |
+
ref={ref}
|
| 50 |
+
{...props}
|
| 51 |
+
/>
|
| 52 |
+
)
|
| 53 |
+
}
|
| 54 |
+
)
|
| 55 |
+
Button.displayName = "Button"
|
| 56 |
+
|
| 57 |
+
export { Button, buttonVariants }
|
frontend/src/components/ui/card.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
const Card = React.forwardRef<
|
| 6 |
+
HTMLDivElement,
|
| 7 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 8 |
+
>(({ className, ...props }, ref) => (
|
| 9 |
+
<div
|
| 10 |
+
ref={ref}
|
| 11 |
+
className={cn(
|
| 12 |
+
"rounded-xl border bg-card text-card-foreground shadow",
|
| 13 |
+
className
|
| 14 |
+
)}
|
| 15 |
+
{...props}
|
| 16 |
+
/>
|
| 17 |
+
))
|
| 18 |
+
Card.displayName = "Card"
|
| 19 |
+
|
| 20 |
+
const CardHeader = React.forwardRef<
|
| 21 |
+
HTMLDivElement,
|
| 22 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 23 |
+
>(({ className, ...props }, ref) => (
|
| 24 |
+
<div
|
| 25 |
+
ref={ref}
|
| 26 |
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
| 27 |
+
{...props}
|
| 28 |
+
/>
|
| 29 |
+
))
|
| 30 |
+
CardHeader.displayName = "CardHeader"
|
| 31 |
+
|
| 32 |
+
const CardTitle = React.forwardRef<
|
| 33 |
+
HTMLDivElement,
|
| 34 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 35 |
+
>(({ className, ...props }, ref) => (
|
| 36 |
+
<div
|
| 37 |
+
ref={ref}
|
| 38 |
+
className={cn("font-semibold leading-none tracking-tight", className)}
|
| 39 |
+
{...props}
|
| 40 |
+
/>
|
| 41 |
+
))
|
| 42 |
+
CardTitle.displayName = "CardTitle"
|
| 43 |
+
|
| 44 |
+
const CardDescription = React.forwardRef<
|
| 45 |
+
HTMLDivElement,
|
| 46 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 47 |
+
>(({ className, ...props }, ref) => (
|
| 48 |
+
<div
|
| 49 |
+
ref={ref}
|
| 50 |
+
className={cn("text-sm text-muted-foreground", className)}
|
| 51 |
+
{...props}
|
| 52 |
+
/>
|
| 53 |
+
))
|
| 54 |
+
CardDescription.displayName = "CardDescription"
|
| 55 |
+
|
| 56 |
+
const CardContent = React.forwardRef<
|
| 57 |
+
HTMLDivElement,
|
| 58 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 59 |
+
>(({ className, ...props }, ref) => (
|
| 60 |
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
| 61 |
+
))
|
| 62 |
+
CardContent.displayName = "CardContent"
|
| 63 |
+
|
| 64 |
+
const CardFooter = React.forwardRef<
|
| 65 |
+
HTMLDivElement,
|
| 66 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 67 |
+
>(({ className, ...props }, ref) => (
|
| 68 |
+
<div
|
| 69 |
+
ref={ref}
|
| 70 |
+
className={cn("flex items-center p-6 pt-0", className)}
|
| 71 |
+
{...props}
|
| 72 |
+
/>
|
| 73 |
+
))
|
| 74 |
+
CardFooter.displayName = "CardFooter"
|
| 75 |
+
|
| 76 |
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
frontend/src/components/ui/checkbox.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
| 5 |
+
import { Check } from "lucide-react"
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils"
|
| 8 |
+
|
| 9 |
+
const Checkbox = React.forwardRef<
|
| 10 |
+
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
| 11 |
+
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
| 12 |
+
>(({ className, ...props }, ref) => (
|
| 13 |
+
<CheckboxPrimitive.Root
|
| 14 |
+
ref={ref}
|
| 15 |
+
className={cn(
|
| 16 |
+
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
| 17 |
+
className
|
| 18 |
+
)}
|
| 19 |
+
{...props}
|
| 20 |
+
>
|
| 21 |
+
<CheckboxPrimitive.Indicator
|
| 22 |
+
className={cn("flex items-center justify-center text-current")}
|
| 23 |
+
>
|
| 24 |
+
<Check className="h-4 w-4" />
|
| 25 |
+
</CheckboxPrimitive.Indicator>
|
| 26 |
+
</CheckboxPrimitive.Root>
|
| 27 |
+
))
|
| 28 |
+
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
| 29 |
+
|
| 30 |
+
export { Checkbox }
|
frontend/src/components/ui/dialog.tsx
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
| 5 |
+
import { X } from "lucide-react"
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils"
|
| 8 |
+
|
| 9 |
+
const Dialog = DialogPrimitive.Root
|
| 10 |
+
|
| 11 |
+
const DialogTrigger = DialogPrimitive.Trigger
|
| 12 |
+
|
| 13 |
+
const DialogPortal = DialogPrimitive.Portal
|
| 14 |
+
|
| 15 |
+
const DialogClose = DialogPrimitive.Close
|
| 16 |
+
|
| 17 |
+
const DialogOverlay = React.forwardRef<
|
| 18 |
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
| 19 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
| 20 |
+
>(({ className, ...props }, ref) => (
|
| 21 |
+
<DialogPrimitive.Overlay
|
| 22 |
+
ref={ref}
|
| 23 |
+
className={cn(
|
| 24 |
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
| 25 |
+
className
|
| 26 |
+
)}
|
| 27 |
+
{...props}
|
| 28 |
+
/>
|
| 29 |
+
))
|
| 30 |
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
| 31 |
+
|
| 32 |
+
const DialogContent = React.forwardRef<
|
| 33 |
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
| 34 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
| 35 |
+
>(({ className, children, ...props }, ref) => (
|
| 36 |
+
<DialogPortal>
|
| 37 |
+
<DialogOverlay />
|
| 38 |
+
<DialogPrimitive.Content
|
| 39 |
+
ref={ref}
|
| 40 |
+
className={cn(
|
| 41 |
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
| 42 |
+
className
|
| 43 |
+
)}
|
| 44 |
+
{...props}
|
| 45 |
+
>
|
| 46 |
+
{children}
|
| 47 |
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
| 48 |
+
<X className="h-4 w-4" />
|
| 49 |
+
<span className="sr-only">Close</span>
|
| 50 |
+
</DialogPrimitive.Close>
|
| 51 |
+
</DialogPrimitive.Content>
|
| 52 |
+
</DialogPortal>
|
| 53 |
+
))
|
| 54 |
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
| 55 |
+
|
| 56 |
+
const DialogHeader = ({
|
| 57 |
+
className,
|
| 58 |
+
...props
|
| 59 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
| 60 |
+
<div
|
| 61 |
+
className={cn(
|
| 62 |
+
"flex flex-col space-y-1.5 text-center sm:text-left",
|
| 63 |
+
className
|
| 64 |
+
)}
|
| 65 |
+
{...props}
|
| 66 |
+
/>
|
| 67 |
+
)
|
| 68 |
+
DialogHeader.displayName = "DialogHeader"
|
| 69 |
+
|
| 70 |
+
const DialogFooter = ({
|
| 71 |
+
className,
|
| 72 |
+
...props
|
| 73 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
| 74 |
+
<div
|
| 75 |
+
className={cn(
|
| 76 |
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
| 77 |
+
className
|
| 78 |
+
)}
|
| 79 |
+
{...props}
|
| 80 |
+
/>
|
| 81 |
+
)
|
| 82 |
+
DialogFooter.displayName = "DialogFooter"
|
| 83 |
+
|
| 84 |
+
const DialogTitle = React.forwardRef<
|
| 85 |
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
| 86 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
| 87 |
+
>(({ className, ...props }, ref) => (
|
| 88 |
+
<DialogPrimitive.Title
|
| 89 |
+
ref={ref}
|
| 90 |
+
className={cn(
|
| 91 |
+
"text-lg font-semibold leading-none tracking-tight",
|
| 92 |
+
className
|
| 93 |
+
)}
|
| 94 |
+
{...props}
|
| 95 |
+
/>
|
| 96 |
+
))
|
| 97 |
+
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
| 98 |
+
|
| 99 |
+
const DialogDescription = React.forwardRef<
|
| 100 |
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
| 101 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
| 102 |
+
>(({ className, ...props }, ref) => (
|
| 103 |
+
<DialogPrimitive.Description
|
| 104 |
+
ref={ref}
|
| 105 |
+
className={cn("text-sm text-muted-foreground", className)}
|
| 106 |
+
{...props}
|
| 107 |
+
/>
|
| 108 |
+
))
|
| 109 |
+
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
| 110 |
+
|
| 111 |
+
export {
|
| 112 |
+
Dialog,
|
| 113 |
+
DialogPortal,
|
| 114 |
+
DialogOverlay,
|
| 115 |
+
DialogTrigger,
|
| 116 |
+
DialogClose,
|
| 117 |
+
DialogContent,
|
| 118 |
+
DialogHeader,
|
| 119 |
+
DialogFooter,
|
| 120 |
+
DialogTitle,
|
| 121 |
+
DialogDescription,
|
| 122 |
+
}
|
frontend/src/components/ui/dropdown-menu.tsx
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
| 5 |
+
import { Check, ChevronRight, Circle } from "lucide-react"
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils"
|
| 8 |
+
|
| 9 |
+
const DropdownMenu = DropdownMenuPrimitive.Root
|
| 10 |
+
|
| 11 |
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
| 12 |
+
|
| 13 |
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
| 14 |
+
|
| 15 |
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
| 16 |
+
|
| 17 |
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
| 18 |
+
|
| 19 |
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
| 20 |
+
|
| 21 |
+
const DropdownMenuSubTrigger = React.forwardRef<
|
| 22 |
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
| 23 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
| 24 |
+
inset?: boolean
|
| 25 |
+
}
|
| 26 |
+
>(({ className, inset, children, ...props }, ref) => (
|
| 27 |
+
<DropdownMenuPrimitive.SubTrigger
|
| 28 |
+
ref={ref}
|
| 29 |
+
className={cn(
|
| 30 |
+
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
| 31 |
+
inset && "pl-8",
|
| 32 |
+
className
|
| 33 |
+
)}
|
| 34 |
+
{...props}
|
| 35 |
+
>
|
| 36 |
+
{children}
|
| 37 |
+
<ChevronRight className="ml-auto h-4 w-4" />
|
| 38 |
+
</DropdownMenuPrimitive.SubTrigger>
|
| 39 |
+
))
|
| 40 |
+
DropdownMenuSubTrigger.displayName =
|
| 41 |
+
DropdownMenuPrimitive.SubTrigger.displayName
|
| 42 |
+
|
| 43 |
+
const DropdownMenuSubContent = React.forwardRef<
|
| 44 |
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
| 45 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
| 46 |
+
>(({ className, ...props }, ref) => (
|
| 47 |
+
<DropdownMenuPrimitive.SubContent
|
| 48 |
+
ref={ref}
|
| 49 |
+
className={cn(
|
| 50 |
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
| 51 |
+
className
|
| 52 |
+
)}
|
| 53 |
+
{...props}
|
| 54 |
+
/>
|
| 55 |
+
))
|
| 56 |
+
DropdownMenuSubContent.displayName =
|
| 57 |
+
DropdownMenuPrimitive.SubContent.displayName
|
| 58 |
+
|
| 59 |
+
const DropdownMenuContent = React.forwardRef<
|
| 60 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
| 61 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
| 62 |
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
| 63 |
+
<DropdownMenuPrimitive.Portal>
|
| 64 |
+
<DropdownMenuPrimitive.Content
|
| 65 |
+
ref={ref}
|
| 66 |
+
sideOffset={sideOffset}
|
| 67 |
+
className={cn(
|
| 68 |
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
| 69 |
+
className
|
| 70 |
+
)}
|
| 71 |
+
{...props}
|
| 72 |
+
/>
|
| 73 |
+
</DropdownMenuPrimitive.Portal>
|
| 74 |
+
))
|
| 75 |
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
| 76 |
+
|
| 77 |
+
const DropdownMenuItem = React.forwardRef<
|
| 78 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
| 79 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
| 80 |
+
inset?: boolean
|
| 81 |
+
}
|
| 82 |
+
>(({ className, inset, ...props }, ref) => (
|
| 83 |
+
<DropdownMenuPrimitive.Item
|
| 84 |
+
ref={ref}
|
| 85 |
+
className={cn(
|
| 86 |
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
| 87 |
+
inset && "pl-8",
|
| 88 |
+
className
|
| 89 |
+
)}
|
| 90 |
+
{...props}
|
| 91 |
+
/>
|
| 92 |
+
))
|
| 93 |
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
| 94 |
+
|
| 95 |
+
const DropdownMenuCheckboxItem = React.forwardRef<
|
| 96 |
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
| 97 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
| 98 |
+
>(({ className, children, checked, ...props }, ref) => (
|
| 99 |
+
<DropdownMenuPrimitive.CheckboxItem
|
| 100 |
+
ref={ref}
|
| 101 |
+
className={cn(
|
| 102 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
| 103 |
+
className
|
| 104 |
+
)}
|
| 105 |
+
checked={checked}
|
| 106 |
+
{...props}
|
| 107 |
+
>
|
| 108 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
| 109 |
+
<DropdownMenuPrimitive.ItemIndicator>
|
| 110 |
+
<Check className="h-4 w-4" />
|
| 111 |
+
</DropdownMenuPrimitive.ItemIndicator>
|
| 112 |
+
</span>
|
| 113 |
+
{children}
|
| 114 |
+
</DropdownMenuPrimitive.CheckboxItem>
|
| 115 |
+
))
|
| 116 |
+
DropdownMenuCheckboxItem.displayName =
|
| 117 |
+
DropdownMenuPrimitive.CheckboxItem.displayName
|
| 118 |
+
|
| 119 |
+
const DropdownMenuRadioItem = React.forwardRef<
|
| 120 |
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
| 121 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
| 122 |
+
>(({ className, children, ...props }, ref) => (
|
| 123 |
+
<DropdownMenuPrimitive.RadioItem
|
| 124 |
+
ref={ref}
|
| 125 |
+
className={cn(
|
| 126 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
| 127 |
+
className
|
| 128 |
+
)}
|
| 129 |
+
{...props}
|
| 130 |
+
>
|
| 131 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
| 132 |
+
<DropdownMenuPrimitive.ItemIndicator>
|
| 133 |
+
<Circle className="h-2 w-2 fill-current" />
|
| 134 |
+
</DropdownMenuPrimitive.ItemIndicator>
|
| 135 |
+
</span>
|
| 136 |
+
{children}
|
| 137 |
+
</DropdownMenuPrimitive.RadioItem>
|
| 138 |
+
))
|
| 139 |
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
| 140 |
+
|
| 141 |
+
const DropdownMenuLabel = React.forwardRef<
|
| 142 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
| 143 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
| 144 |
+
inset?: boolean
|
| 145 |
+
}
|
| 146 |
+
>(({ className, inset, ...props }, ref) => (
|
| 147 |
+
<DropdownMenuPrimitive.Label
|
| 148 |
+
ref={ref}
|
| 149 |
+
className={cn(
|
| 150 |
+
"px-2 py-1.5 text-sm font-semibold",
|
| 151 |
+
inset && "pl-8",
|
| 152 |
+
className
|
| 153 |
+
)}
|
| 154 |
+
{...props}
|
| 155 |
+
/>
|
| 156 |
+
))
|
| 157 |
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
| 158 |
+
|
| 159 |
+
const DropdownMenuSeparator = React.forwardRef<
|
| 160 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
| 161 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
| 162 |
+
>(({ className, ...props }, ref) => (
|
| 163 |
+
<DropdownMenuPrimitive.Separator
|
| 164 |
+
ref={ref}
|
| 165 |
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
| 166 |
+
{...props}
|
| 167 |
+
/>
|
| 168 |
+
))
|
| 169 |
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
| 170 |
+
|
| 171 |
+
const DropdownMenuShortcut = ({
|
| 172 |
+
className,
|
| 173 |
+
...props
|
| 174 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
| 175 |
+
return (
|
| 176 |
+
<span
|
| 177 |
+
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
| 178 |
+
{...props}
|
| 179 |
+
/>
|
| 180 |
+
)
|
| 181 |
+
}
|
| 182 |
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
| 183 |
+
|
| 184 |
+
export {
|
| 185 |
+
DropdownMenu,
|
| 186 |
+
DropdownMenuTrigger,
|
| 187 |
+
DropdownMenuContent,
|
| 188 |
+
DropdownMenuItem,
|
| 189 |
+
DropdownMenuCheckboxItem,
|
| 190 |
+
DropdownMenuRadioItem,
|
| 191 |
+
DropdownMenuLabel,
|
| 192 |
+
DropdownMenuSeparator,
|
| 193 |
+
DropdownMenuShortcut,
|
| 194 |
+
DropdownMenuGroup,
|
| 195 |
+
DropdownMenuPortal,
|
| 196 |
+
DropdownMenuSub,
|
| 197 |
+
DropdownMenuSubContent,
|
| 198 |
+
DropdownMenuSubTrigger,
|
| 199 |
+
DropdownMenuRadioGroup,
|
| 200 |
+
}
|
frontend/src/components/ui/input.tsx
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
| 6 |
+
({ className, type, ...props }, ref) => {
|
| 7 |
+
return (
|
| 8 |
+
<input
|
| 9 |
+
type={type}
|
| 10 |
+
className={cn(
|
| 11 |
+
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
| 12 |
+
className
|
| 13 |
+
)}
|
| 14 |
+
ref={ref}
|
| 15 |
+
{...props}
|
| 16 |
+
/>
|
| 17 |
+
)
|
| 18 |
+
}
|
| 19 |
+
)
|
| 20 |
+
Input.displayName = "Input"
|
| 21 |
+
|
| 22 |
+
export { Input }
|
frontend/src/components/ui/label.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as LabelPrimitive from "@radix-ui/react-label"
|
| 5 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils"
|
| 8 |
+
|
| 9 |
+
const labelVariants = cva(
|
| 10 |
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
const Label = React.forwardRef<
|
| 14 |
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
| 15 |
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
| 16 |
+
VariantProps<typeof labelVariants>
|
| 17 |
+
>(({ className, ...props }, ref) => (
|
| 18 |
+
<LabelPrimitive.Root
|
| 19 |
+
ref={ref}
|
| 20 |
+
className={cn(labelVariants(), className)}
|
| 21 |
+
{...props}
|
| 22 |
+
/>
|
| 23 |
+
))
|
| 24 |
+
Label.displayName = LabelPrimitive.Root.displayName
|
| 25 |
+
|
| 26 |
+
export { Label }
|
frontend/src/components/ui/resizable.tsx
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { GripVertical } from "lucide-react"
|
| 4 |
+
import * as ResizablePrimitive from "react-resizable-panels"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
const ResizablePanelGroup = ({
|
| 9 |
+
className,
|
| 10 |
+
...props
|
| 11 |
+
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
|
| 12 |
+
<ResizablePrimitive.PanelGroup
|
| 13 |
+
className={cn(
|
| 14 |
+
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
| 15 |
+
className
|
| 16 |
+
)}
|
| 17 |
+
{...props}
|
| 18 |
+
/>
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
const ResizablePanel = ResizablePrimitive.Panel
|
| 22 |
+
|
| 23 |
+
const ResizableHandle = ({
|
| 24 |
+
withHandle,
|
| 25 |
+
className,
|
| 26 |
+
...props
|
| 27 |
+
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
| 28 |
+
withHandle?: boolean
|
| 29 |
+
}) => (
|
| 30 |
+
<ResizablePrimitive.PanelResizeHandle
|
| 31 |
+
className={cn(
|
| 32 |
+
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
| 33 |
+
className
|
| 34 |
+
)}
|
| 35 |
+
{...props}
|
| 36 |
+
>
|
| 37 |
+
{withHandle && (
|
| 38 |
+
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
| 39 |
+
<GripVertical className="h-2.5 w-2.5" />
|
| 40 |
+
</div>
|
| 41 |
+
)}
|
| 42 |
+
</ResizablePrimitive.PanelResizeHandle>
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
frontend/src/components/ui/scroll-area.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
| 4 |
+
import * as React from "react";
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils";
|
| 7 |
+
|
| 8 |
+
const ScrollArea = React.forwardRef<React.ElementRef<typeof ScrollAreaPrimitive.Root>, React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>>(({ className, children, ...props }, ref) => (
|
| 9 |
+
<ScrollAreaPrimitive.Root ref={ref} className={cn("relative overflow-hidden", className)} {...props}>
|
| 10 |
+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">{children}</ScrollAreaPrimitive.Viewport>
|
| 11 |
+
<ScrollBar />
|
| 12 |
+
<ScrollAreaPrimitive.Corner />
|
| 13 |
+
</ScrollAreaPrimitive.Root>
|
| 14 |
+
));
|
| 15 |
+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
| 16 |
+
|
| 17 |
+
const ScrollBar = React.forwardRef<React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>>(({ className, orientation = "vertical", ...props }, ref) => (
|
| 18 |
+
<ScrollAreaPrimitive.ScrollAreaScrollbar ref={ref} orientation={orientation} className={cn("flex touch-none select-none transition-colors", orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]", orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]", className)} {...props}>
|
| 19 |
+
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
| 20 |
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
| 21 |
+
));
|
| 22 |
+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
| 23 |
+
|
| 24 |
+
export { ScrollArea, ScrollBar };
|
frontend/src/components/ui/select.tsx
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as SelectPrimitive from "@radix-ui/react-select"
|
| 5 |
+
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils"
|
| 8 |
+
|
| 9 |
+
const Select = SelectPrimitive.Root
|
| 10 |
+
|
| 11 |
+
const SelectGroup = SelectPrimitive.Group
|
| 12 |
+
|
| 13 |
+
const SelectValue = SelectPrimitive.Value
|
| 14 |
+
|
| 15 |
+
const SelectTrigger = React.forwardRef<
|
| 16 |
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
| 17 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
| 18 |
+
>(({ className, children, ...props }, ref) => (
|
| 19 |
+
<SelectPrimitive.Trigger
|
| 20 |
+
ref={ref}
|
| 21 |
+
className={cn(
|
| 22 |
+
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
| 23 |
+
className
|
| 24 |
+
)}
|
| 25 |
+
{...props}
|
| 26 |
+
>
|
| 27 |
+
{children}
|
| 28 |
+
<SelectPrimitive.Icon asChild>
|
| 29 |
+
<ChevronDown className="h-4 w-4 opacity-50" />
|
| 30 |
+
</SelectPrimitive.Icon>
|
| 31 |
+
</SelectPrimitive.Trigger>
|
| 32 |
+
))
|
| 33 |
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
| 34 |
+
|
| 35 |
+
const SelectScrollUpButton = React.forwardRef<
|
| 36 |
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
| 37 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
| 38 |
+
>(({ className, ...props }, ref) => (
|
| 39 |
+
<SelectPrimitive.ScrollUpButton
|
| 40 |
+
ref={ref}
|
| 41 |
+
className={cn(
|
| 42 |
+
"flex cursor-default items-center justify-center py-1",
|
| 43 |
+
className
|
| 44 |
+
)}
|
| 45 |
+
{...props}
|
| 46 |
+
>
|
| 47 |
+
<ChevronUp className="h-4 w-4" />
|
| 48 |
+
</SelectPrimitive.ScrollUpButton>
|
| 49 |
+
))
|
| 50 |
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
| 51 |
+
|
| 52 |
+
const SelectScrollDownButton = React.forwardRef<
|
| 53 |
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
| 54 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
| 55 |
+
>(({ className, ...props }, ref) => (
|
| 56 |
+
<SelectPrimitive.ScrollDownButton
|
| 57 |
+
ref={ref}
|
| 58 |
+
className={cn(
|
| 59 |
+
"flex cursor-default items-center justify-center py-1",
|
| 60 |
+
className
|
| 61 |
+
)}
|
| 62 |
+
{...props}
|
| 63 |
+
>
|
| 64 |
+
<ChevronDown className="h-4 w-4" />
|
| 65 |
+
</SelectPrimitive.ScrollDownButton>
|
| 66 |
+
))
|
| 67 |
+
SelectScrollDownButton.displayName =
|
| 68 |
+
SelectPrimitive.ScrollDownButton.displayName
|
| 69 |
+
|
| 70 |
+
const SelectContent = React.forwardRef<
|
| 71 |
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
| 72 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
| 73 |
+
>(({ className, children, position = "popper", ...props }, ref) => (
|
| 74 |
+
<SelectPrimitive.Portal>
|
| 75 |
+
<SelectPrimitive.Content
|
| 76 |
+
ref={ref}
|
| 77 |
+
className={cn(
|
| 78 |
+
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
| 79 |
+
position === "popper" &&
|
| 80 |
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
| 81 |
+
className
|
| 82 |
+
)}
|
| 83 |
+
position={position}
|
| 84 |
+
{...props}
|
| 85 |
+
>
|
| 86 |
+
<SelectScrollUpButton />
|
| 87 |
+
<SelectPrimitive.Viewport
|
| 88 |
+
className={cn(
|
| 89 |
+
"p-1",
|
| 90 |
+
position === "popper" &&
|
| 91 |
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
| 92 |
+
)}
|
| 93 |
+
>
|
| 94 |
+
{children}
|
| 95 |
+
</SelectPrimitive.Viewport>
|
| 96 |
+
<SelectScrollDownButton />
|
| 97 |
+
</SelectPrimitive.Content>
|
| 98 |
+
</SelectPrimitive.Portal>
|
| 99 |
+
))
|
| 100 |
+
SelectContent.displayName = SelectPrimitive.Content.displayName
|
| 101 |
+
|
| 102 |
+
const SelectLabel = React.forwardRef<
|
| 103 |
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
| 104 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
| 105 |
+
>(({ className, ...props }, ref) => (
|
| 106 |
+
<SelectPrimitive.Label
|
| 107 |
+
ref={ref}
|
| 108 |
+
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
| 109 |
+
{...props}
|
| 110 |
+
/>
|
| 111 |
+
))
|
| 112 |
+
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
| 113 |
+
|
| 114 |
+
const SelectItem = React.forwardRef<
|
| 115 |
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
| 116 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
| 117 |
+
>(({ className, children, ...props }, ref) => (
|
| 118 |
+
<SelectPrimitive.Item
|
| 119 |
+
ref={ref}
|
| 120 |
+
className={cn(
|
| 121 |
+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
| 122 |
+
className
|
| 123 |
+
)}
|
| 124 |
+
{...props}
|
| 125 |
+
>
|
| 126 |
+
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
| 127 |
+
<SelectPrimitive.ItemIndicator>
|
| 128 |
+
<Check className="h-4 w-4" />
|
| 129 |
+
</SelectPrimitive.ItemIndicator>
|
| 130 |
+
</span>
|
| 131 |
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
| 132 |
+
</SelectPrimitive.Item>
|
| 133 |
+
))
|
| 134 |
+
SelectItem.displayName = SelectPrimitive.Item.displayName
|
| 135 |
+
|
| 136 |
+
const SelectSeparator = React.forwardRef<
|
| 137 |
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
| 138 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
| 139 |
+
>(({ className, ...props }, ref) => (
|
| 140 |
+
<SelectPrimitive.Separator
|
| 141 |
+
ref={ref}
|
| 142 |
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
| 143 |
+
{...props}
|
| 144 |
+
/>
|
| 145 |
+
))
|
| 146 |
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
| 147 |
+
|
| 148 |
+
export {
|
| 149 |
+
Select,
|
| 150 |
+
SelectGroup,
|
| 151 |
+
SelectValue,
|
| 152 |
+
SelectTrigger,
|
| 153 |
+
SelectContent,
|
| 154 |
+
SelectLabel,
|
| 155 |
+
SelectItem,
|
| 156 |
+
SelectSeparator,
|
| 157 |
+
SelectScrollUpButton,
|
| 158 |
+
SelectScrollDownButton,
|
| 159 |
+
}
|
frontend/src/components/ui/separator.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
const Separator = React.forwardRef<
|
| 9 |
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
| 10 |
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
| 11 |
+
>(
|
| 12 |
+
(
|
| 13 |
+
{ className, orientation = "horizontal", decorative = true, ...props },
|
| 14 |
+
ref
|
| 15 |
+
) => (
|
| 16 |
+
<SeparatorPrimitive.Root
|
| 17 |
+
ref={ref}
|
| 18 |
+
decorative={decorative}
|
| 19 |
+
orientation={orientation}
|
| 20 |
+
className={cn(
|
| 21 |
+
"shrink-0 bg-border",
|
| 22 |
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
| 23 |
+
className
|
| 24 |
+
)}
|
| 25 |
+
{...props}
|
| 26 |
+
/>
|
| 27 |
+
)
|
| 28 |
+
)
|
| 29 |
+
Separator.displayName = SeparatorPrimitive.Root.displayName
|
| 30 |
+
|
| 31 |
+
export { Separator }
|
frontend/src/components/ui/sheet.tsx
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
| 5 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 6 |
+
import { X } from "lucide-react"
|
| 7 |
+
|
| 8 |
+
import { cn } from "@/lib/utils"
|
| 9 |
+
|
| 10 |
+
const Sheet = SheetPrimitive.Root
|
| 11 |
+
|
| 12 |
+
const SheetTrigger = SheetPrimitive.Trigger
|
| 13 |
+
|
| 14 |
+
const SheetClose = SheetPrimitive.Close
|
| 15 |
+
|
| 16 |
+
const SheetPortal = SheetPrimitive.Portal
|
| 17 |
+
|
| 18 |
+
const SheetOverlay = React.forwardRef<
|
| 19 |
+
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
| 20 |
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
| 21 |
+
>(({ className, ...props }, ref) => (
|
| 22 |
+
<SheetPrimitive.Overlay
|
| 23 |
+
className={cn(
|
| 24 |
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
| 25 |
+
className
|
| 26 |
+
)}
|
| 27 |
+
{...props}
|
| 28 |
+
ref={ref}
|
| 29 |
+
/>
|
| 30 |
+
))
|
| 31 |
+
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
| 32 |
+
|
| 33 |
+
const sheetVariants = cva(
|
| 34 |
+
"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",
|
| 35 |
+
{
|
| 36 |
+
variants: {
|
| 37 |
+
side: {
|
| 38 |
+
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
| 39 |
+
bottom:
|
| 40 |
+
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
| 41 |
+
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",
|
| 42 |
+
right:
|
| 43 |
+
"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",
|
| 44 |
+
},
|
| 45 |
+
},
|
| 46 |
+
defaultVariants: {
|
| 47 |
+
side: "right",
|
| 48 |
+
},
|
| 49 |
+
}
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
interface SheetContentProps
|
| 53 |
+
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
| 54 |
+
VariantProps<typeof sheetVariants> {}
|
| 55 |
+
|
| 56 |
+
const SheetContent = React.forwardRef<
|
| 57 |
+
React.ElementRef<typeof SheetPrimitive.Content>,
|
| 58 |
+
SheetContentProps
|
| 59 |
+
>(({ side = "right", className, children, ...props }, ref) => (
|
| 60 |
+
<SheetPortal>
|
| 61 |
+
<SheetOverlay />
|
| 62 |
+
<SheetPrimitive.Content
|
| 63 |
+
ref={ref}
|
| 64 |
+
className={cn(sheetVariants({ side }), className)}
|
| 65 |
+
{...props}
|
| 66 |
+
>
|
| 67 |
+
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
| 68 |
+
<X className="h-4 w-4" />
|
| 69 |
+
<span className="sr-only">Close</span>
|
| 70 |
+
</SheetPrimitive.Close>
|
| 71 |
+
{children}
|
| 72 |
+
</SheetPrimitive.Content>
|
| 73 |
+
</SheetPortal>
|
| 74 |
+
))
|
| 75 |
+
SheetContent.displayName = SheetPrimitive.Content.displayName
|
| 76 |
+
|
| 77 |
+
const SheetHeader = ({
|
| 78 |
+
className,
|
| 79 |
+
...props
|
| 80 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
| 81 |
+
<div
|
| 82 |
+
className={cn(
|
| 83 |
+
"flex flex-col space-y-2 text-center sm:text-left",
|
| 84 |
+
className
|
| 85 |
+
)}
|
| 86 |
+
{...props}
|
| 87 |
+
/>
|
| 88 |
+
)
|
| 89 |
+
SheetHeader.displayName = "SheetHeader"
|
| 90 |
+
|
| 91 |
+
const SheetFooter = ({
|
| 92 |
+
className,
|
| 93 |
+
...props
|
| 94 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
| 95 |
+
<div
|
| 96 |
+
className={cn(
|
| 97 |
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
| 98 |
+
className
|
| 99 |
+
)}
|
| 100 |
+
{...props}
|
| 101 |
+
/>
|
| 102 |
+
)
|
| 103 |
+
SheetFooter.displayName = "SheetFooter"
|
| 104 |
+
|
| 105 |
+
const SheetTitle = React.forwardRef<
|
| 106 |
+
React.ElementRef<typeof SheetPrimitive.Title>,
|
| 107 |
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
| 108 |
+
>(({ className, ...props }, ref) => (
|
| 109 |
+
<SheetPrimitive.Title
|
| 110 |
+
ref={ref}
|
| 111 |
+
className={cn("text-lg font-semibold text-foreground", className)}
|
| 112 |
+
{...props}
|
| 113 |
+
/>
|
| 114 |
+
))
|
| 115 |
+
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
| 116 |
+
|
| 117 |
+
const SheetDescription = React.forwardRef<
|
| 118 |
+
React.ElementRef<typeof SheetPrimitive.Description>,
|
| 119 |
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
| 120 |
+
>(({ className, ...props }, ref) => (
|
| 121 |
+
<SheetPrimitive.Description
|
| 122 |
+
ref={ref}
|
| 123 |
+
className={cn("text-sm text-muted-foreground", className)}
|
| 124 |
+
{...props}
|
| 125 |
+
/>
|
| 126 |
+
))
|
| 127 |
+
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
| 128 |
+
|
| 129 |
+
export {
|
| 130 |
+
Sheet,
|
| 131 |
+
SheetPortal,
|
| 132 |
+
SheetOverlay,
|
| 133 |
+
SheetTrigger,
|
| 134 |
+
SheetClose,
|
| 135 |
+
SheetContent,
|
| 136 |
+
SheetHeader,
|
| 137 |
+
SheetFooter,
|
| 138 |
+
SheetTitle,
|
| 139 |
+
SheetDescription,
|
| 140 |
+
}
|
frontend/src/components/ui/tabs.tsx
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
| 4 |
+
import * as React from "react";
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils";
|
| 7 |
+
|
| 8 |
+
const Tabs = TabsPrimitive.Root;
|
| 9 |
+
|
| 10 |
+
const TabsList = React.forwardRef<React.ElementRef<typeof TabsPrimitive.List>, React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>>(({ className, ...props }, ref) => <TabsPrimitive.List ref={ref} className={cn("inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", className)} {...props} />);
|
| 11 |
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
| 12 |
+
|
| 13 |
+
const TabsTrigger = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Trigger>, React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>>(({ className, ...props }, ref) => (
|
| 14 |
+
<TabsPrimitive.Trigger ref={ref} className={cn("inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow", className)} {...props} />
|
| 15 |
+
));
|
| 16 |
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
| 17 |
+
|
| 18 |
+
const TabsContent = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Content>, React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>>(({ className, ...props }, ref) => <TabsPrimitive.Content ref={ref} className={cn("mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className)} {...props} />);
|
| 19 |
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
| 20 |
+
|
| 21 |
+
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
| 22 |
+
|
frontend/src/components/ui/textarea.tsx
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react";
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils";
|
| 4 |
+
import { useImperativeHandle } from "react";
|
| 5 |
+
|
| 6 |
+
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
| 7 |
+
|
| 8 |
+
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => {
|
| 9 |
+
return <textarea className={cn("flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className)} ref={ref} {...props} />;
|
| 10 |
+
});
|
| 11 |
+
Textarea.displayName = "Textarea";
|
| 12 |
+
|
| 13 |
+
export { Textarea };
|
| 14 |
+
|
| 15 |
+
interface UseAutosizeTextAreaProps {
|
| 16 |
+
textAreaRef: React.MutableRefObject<HTMLTextAreaElement | null>;
|
| 17 |
+
minHeight?: number;
|
| 18 |
+
maxHeight?: number;
|
| 19 |
+
triggerAutoSize: string;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export const useAutosizeTextArea = ({ textAreaRef, triggerAutoSize, maxHeight = Number.MAX_SAFE_INTEGER, minHeight = 0 }: UseAutosizeTextAreaProps) => {
|
| 23 |
+
const [init, setInit] = React.useState(true);
|
| 24 |
+
React.useEffect(() => {
|
| 25 |
+
// We need to reset the height momentarily to get the correct scrollHeight for the textarea
|
| 26 |
+
const offsetBorder = 0;
|
| 27 |
+
const textAreaElement = textAreaRef.current;
|
| 28 |
+
if (textAreaElement) {
|
| 29 |
+
if (init) {
|
| 30 |
+
textAreaElement.style.minHeight = `${minHeight + offsetBorder}px`;
|
| 31 |
+
if (maxHeight > minHeight) {
|
| 32 |
+
textAreaElement.style.maxHeight = `${maxHeight}px`;
|
| 33 |
+
}
|
| 34 |
+
setInit(false);
|
| 35 |
+
}
|
| 36 |
+
textAreaElement.style.height = `${minHeight + offsetBorder}px`;
|
| 37 |
+
const scrollHeight = textAreaElement.scrollHeight;
|
| 38 |
+
// We then set the height directly, outside of the render loop
|
| 39 |
+
// Trying to set this with state or a ref will product an incorrect value.
|
| 40 |
+
if (scrollHeight > maxHeight) {
|
| 41 |
+
textAreaElement.style.height = `${maxHeight}px`;
|
| 42 |
+
} else {
|
| 43 |
+
textAreaElement.style.height = `${scrollHeight + offsetBorder}px`;
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
}, [textAreaRef.current, triggerAutoSize]);
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
export type AutosizeTextAreaRef = {
|
| 50 |
+
textArea: HTMLTextAreaElement;
|
| 51 |
+
maxHeight: number;
|
| 52 |
+
minHeight: number;
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
type AutosizeTextAreaProps = {
|
| 56 |
+
maxHeight?: number;
|
| 57 |
+
minHeight?: number;
|
| 58 |
+
} & React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
| 59 |
+
|
| 60 |
+
export const AutosizeTextarea = React.forwardRef<AutosizeTextAreaRef, AutosizeTextAreaProps>(({ maxHeight = Number.MAX_SAFE_INTEGER, minHeight = 52, className, onChange, value, ...props }: AutosizeTextAreaProps, ref: React.Ref<AutosizeTextAreaRef>) => {
|
| 61 |
+
const textAreaRef = React.useRef<HTMLTextAreaElement | null>(null);
|
| 62 |
+
const [triggerAutoSize, setTriggerAutoSize] = React.useState("");
|
| 63 |
+
|
| 64 |
+
useAutosizeTextArea({
|
| 65 |
+
textAreaRef,
|
| 66 |
+
triggerAutoSize: triggerAutoSize,
|
| 67 |
+
maxHeight,
|
| 68 |
+
minHeight,
|
| 69 |
+
});
|
| 70 |
+
|
| 71 |
+
useImperativeHandle(ref, () => ({
|
| 72 |
+
textArea: textAreaRef.current as HTMLTextAreaElement,
|
| 73 |
+
focus: () => textAreaRef?.current?.focus(),
|
| 74 |
+
maxHeight,
|
| 75 |
+
minHeight,
|
| 76 |
+
}));
|
| 77 |
+
|
| 78 |
+
React.useEffect(() => {
|
| 79 |
+
setTriggerAutoSize(value as string);
|
| 80 |
+
}, [props?.defaultValue, value]);
|
| 81 |
+
|
| 82 |
+
return (
|
| 83 |
+
<textarea
|
| 84 |
+
{...props}
|
| 85 |
+
value={value}
|
| 86 |
+
ref={textAreaRef}
|
| 87 |
+
className={cn("flex w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className)}
|
| 88 |
+
onChange={(e) => {
|
| 89 |
+
setTriggerAutoSize(e.target.value);
|
| 90 |
+
onChange?.(e);
|
| 91 |
+
}}
|
| 92 |
+
/>
|
| 93 |
+
);
|
| 94 |
+
});
|
| 95 |
+
AutosizeTextarea.displayName = "AutosizeTextarea";
|
frontend/src/components/ui/tooltip.tsx
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
const TooltipProvider = TooltipPrimitive.Provider
|
| 9 |
+
|
| 10 |
+
const Tooltip = TooltipPrimitive.Root
|
| 11 |
+
|
| 12 |
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
| 13 |
+
|
| 14 |
+
const TooltipContent = React.forwardRef<
|
| 15 |
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
| 16 |
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
| 17 |
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
| 18 |
+
<TooltipPrimitive.Portal>
|
| 19 |
+
<TooltipPrimitive.Content
|
| 20 |
+
ref={ref}
|
| 21 |
+
sideOffset={sideOffset}
|
| 22 |
+
className={cn(
|
| 23 |
+
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
| 24 |
+
className
|
| 25 |
+
)}
|
| 26 |
+
{...props}
|
| 27 |
+
/>
|
| 28 |
+
</TooltipPrimitive.Portal>
|
| 29 |
+
))
|
| 30 |
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
| 31 |
+
|
| 32 |
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
frontend/src/lib/socket.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { io, Socket } from "socket.io-client";
|
| 2 |
+
|
| 3 |
+
let socket: Socket | null = null;
|
| 4 |
+
|
| 5 |
+
export const initializeSocket = (url: string = process.env.NEXT_PUBLIC_KNET_BACKEND!) => {
|
| 6 |
+
url = process.env.NEXT_PUBLIC_KNET_BACKEND || "http://127.0.0.1:5000";
|
| 7 |
+
if (!socket) {
|
| 8 |
+
socket = io(url, {
|
| 9 |
+
transports: ["websocket"],
|
| 10 |
+
reconnection: true,
|
| 11 |
+
reconnectionAttempts: 5,
|
| 12 |
+
reconnectionDelay: 1000,
|
| 13 |
+
});
|
| 14 |
+
}
|
| 15 |
+
return socket;
|
| 16 |
+
};
|
| 17 |
+
|
| 18 |
+
export const getSocket = () => {
|
| 19 |
+
if (!socket) {
|
| 20 |
+
throw new Error("Socket not initialized. Call initializeSocket first.");
|
| 21 |
+
}
|
| 22 |
+
return socket;
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
export const disconnectSocket = () => {
|
| 26 |
+
if (socket) {
|
| 27 |
+
socket.disconnect();
|
| 28 |
+
socket = null;
|
| 29 |
+
}
|
| 30 |
+
};
|
frontend/src/lib/types.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface Message {
|
| 2 |
+
id: string;
|
| 3 |
+
content: string;
|
| 4 |
+
role: "user" | "assistant" | "system";
|
| 5 |
+
timestamp: Date;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export interface ChatState {
|
| 9 |
+
messages: Message[];
|
| 10 |
+
isLoading: boolean;
|
| 11 |
+
error: string | null;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export interface ResearchOptions {
|
| 15 |
+
depth: "basic" | "intermediate" | "deep";
|
| 16 |
+
sources: boolean;
|
| 17 |
+
citations: boolean;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
export interface ChatData {
|
| 21 |
+
conversations: Conversation[];
|
| 22 |
+
currentConversationId: string | null;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export interface Conversation {
|
| 26 |
+
id: string;
|
| 27 |
+
title: string;
|
| 28 |
+
lastUpdated: string;
|
| 29 |
+
messages: Message[];
|
| 30 |
+
active: boolean;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
export interface StatusUpdate {
|
| 34 |
+
message: string;
|
| 35 |
+
progress: number;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
export interface ResearchNode {
|
| 39 |
+
query: string;
|
| 40 |
+
children?: ResearchNode[];
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
export interface ResearchMetadata {
|
| 44 |
+
total_queries: number;
|
| 45 |
+
total_sources: number;
|
| 46 |
+
max_depth_reached: number;
|
| 47 |
+
total_tokens: number;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
export interface ResearchResults {
|
| 51 |
+
topic: string;
|
| 52 |
+
timestamp: string;
|
| 53 |
+
content: string;
|
| 54 |
+
media?: {
|
| 55 |
+
images?: string[];
|
| 56 |
+
videos?: string[];
|
| 57 |
+
};
|
| 58 |
+
research_tree: ResearchNode;
|
| 59 |
+
metadata: ResearchMetadata;
|
| 60 |
+
}
|
frontend/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { clsx, type ClassValue } from "clsx"
|
| 2 |
+
import { twMerge } from "tailwind-merge"
|
| 3 |
+
|
| 4 |
+
export function cn(...inputs: ClassValue[]) {
|
| 5 |
+
return twMerge(clsx(inputs))
|
| 6 |
+
}
|
frontend/tailwind.config.ts
CHANGED
|
@@ -1,19 +1,63 @@
|
|
| 1 |
import type { Config } from "tailwindcss";
|
| 2 |
|
| 3 |
const config: Config = {
|
| 4 |
-
|
|
|
|
| 5 |
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
| 6 |
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
| 7 |
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
| 8 |
],
|
| 9 |
theme: {
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
},
|
| 17 |
-
plugins: [],
|
| 18 |
};
|
| 19 |
export default config;
|
|
|
|
| 1 |
import type { Config } from "tailwindcss";
|
| 2 |
|
| 3 |
const config: Config = {
|
| 4 |
+
darkMode: ["class"],
|
| 5 |
+
content: [
|
| 6 |
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
| 7 |
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
| 8 |
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
| 9 |
],
|
| 10 |
theme: {
|
| 11 |
+
extend: {
|
| 12 |
+
colors: {
|
| 13 |
+
background: 'hsl(var(--background))',
|
| 14 |
+
foreground: 'hsl(var(--foreground))',
|
| 15 |
+
card: {
|
| 16 |
+
DEFAULT: 'hsl(var(--card))',
|
| 17 |
+
foreground: 'hsl(var(--card-foreground))'
|
| 18 |
+
},
|
| 19 |
+
popover: {
|
| 20 |
+
DEFAULT: 'hsl(var(--popover))',
|
| 21 |
+
foreground: 'hsl(var(--popover-foreground))'
|
| 22 |
+
},
|
| 23 |
+
primary: {
|
| 24 |
+
DEFAULT: 'hsl(var(--primary))',
|
| 25 |
+
foreground: 'hsl(var(--primary-foreground))'
|
| 26 |
+
},
|
| 27 |
+
secondary: {
|
| 28 |
+
DEFAULT: 'hsl(var(--secondary))',
|
| 29 |
+
foreground: 'hsl(var(--secondary-foreground))'
|
| 30 |
+
},
|
| 31 |
+
muted: {
|
| 32 |
+
DEFAULT: 'hsl(var(--muted))',
|
| 33 |
+
foreground: 'hsl(var(--muted-foreground))'
|
| 34 |
+
},
|
| 35 |
+
accent: {
|
| 36 |
+
DEFAULT: 'hsl(var(--accent))',
|
| 37 |
+
foreground: 'hsl(var(--accent-foreground))'
|
| 38 |
+
},
|
| 39 |
+
destructive: {
|
| 40 |
+
DEFAULT: 'hsl(var(--destructive))',
|
| 41 |
+
foreground: 'hsl(var(--destructive-foreground))'
|
| 42 |
+
},
|
| 43 |
+
border: 'hsl(var(--border))',
|
| 44 |
+
input: 'hsl(var(--input))',
|
| 45 |
+
ring: 'hsl(var(--ring))',
|
| 46 |
+
chart: {
|
| 47 |
+
'1': 'hsl(var(--chart-1))',
|
| 48 |
+
'2': 'hsl(var(--chart-2))',
|
| 49 |
+
'3': 'hsl(var(--chart-3))',
|
| 50 |
+
'4': 'hsl(var(--chart-4))',
|
| 51 |
+
'5': 'hsl(var(--chart-5))'
|
| 52 |
+
}
|
| 53 |
+
},
|
| 54 |
+
borderRadius: {
|
| 55 |
+
lg: 'var(--radius)',
|
| 56 |
+
md: 'calc(var(--radius) - 2px)',
|
| 57 |
+
sm: 'calc(var(--radius) - 4px)'
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
},
|
| 61 |
+
plugins: [require("tailwindcss-animate")],
|
| 62 |
};
|
| 63 |
export default config;
|