Spaces:
Paused
Paused
Soham Waghmare
commited on
Commit
·
b636e8f
1
Parent(s):
514da67
feat: emit statespace graph for explainability
Browse files- backend/knet.py +16 -28
- backend/research_node.py +10 -6
- frontend/bun.lock +145 -0
- frontend/package.json +3 -0
- frontend/src/components/ui/ChatLayout.tsx +28 -12
- frontend/src/components/visualizations/ResearchGraph.tsx +244 -0
- frontend/src/lib/store/ChatContext.tsx +10 -15
- frontend/src/lib/types.ts +8 -7
backend/knet.py
CHANGED
|
@@ -149,17 +149,18 @@ class Schema:
|
|
| 149 |
|
| 150 |
|
| 151 |
class ResearchProgress:
|
| 152 |
-
def __init__(self, callback):
|
| 153 |
self.progress = 0
|
| 154 |
self.callback = callback
|
|
|
|
| 155 |
|
| 156 |
async def update(self, progress: int, message: str):
|
| 157 |
self.progress = int(min(100, self.progress + progress)) # max 100
|
| 158 |
-
await self.callback({"progress": self.progress, "message": message})
|
| 159 |
|
| 160 |
async def setter(self, progress: int, message: str):
|
| 161 |
self.progress = int(min(100, progress)) # max 100
|
| 162 |
-
await self.callback({"progress": self.progress, "message": message})
|
| 163 |
|
| 164 |
|
| 165 |
class KNet:
|
|
@@ -180,6 +181,7 @@ class KNet:
|
|
| 180 |
self.num_sites_per_query = num_sites_per_query
|
| 181 |
|
| 182 |
# Global State
|
|
|
|
| 183 |
self.research_plan: list[str] = []
|
| 184 |
self.idx_research_plan: int = 0
|
| 185 |
self.ctx_researcher: list[str] = []
|
|
@@ -188,7 +190,7 @@ class KNet:
|
|
| 188 |
|
| 189 |
async def conduct_research(self, topic: str, progress_callback, max_depth: int, num_sites_per_query: int) -> dict | bool:
|
| 190 |
# Local Runtime State
|
| 191 |
-
self.progress = ResearchProgress(progress_callback)
|
| 192 |
self.max_depth = max_depth
|
| 193 |
self.num_sites_per_query = num_sites_per_query
|
| 194 |
|
|
@@ -209,8 +211,6 @@ class KNet:
|
|
| 209 |
]
|
| 210 |
self.logger.info(f"Research plan:\n{json.dumps(self.research_plan, indent=2)}")
|
| 211 |
|
| 212 |
-
master_node = ResearchNode()
|
| 213 |
-
|
| 214 |
await self.progress.update(0, "Starting research...")
|
| 215 |
|
| 216 |
# Iterate on research plan
|
|
@@ -227,7 +227,7 @@ class KNet:
|
|
| 227 |
)["branches"][0]
|
| 228 |
|
| 229 |
root_node = ResearchNode(query)
|
| 230 |
-
master_node.add_child(root_node.query, node=root_node)
|
| 231 |
to_explore = deque([(root_node, 1)]) # (node, depth) pairs
|
| 232 |
explored_queries = set() # {string, string, ...}
|
| 233 |
|
|
@@ -261,9 +261,9 @@ class KNet:
|
|
| 261 |
|
| 262 |
# Generate final report
|
| 263 |
await self.progress.update(100 / (len(self.research_plan) + 1), "Generating final report...")
|
| 264 |
-
final_report = await self._generate_final_report(
|
| 265 |
|
| 266 |
-
self.logger.info(f"Research completed. Explored {len(explored_queries)} queries across {master_node.max_depth()} levels")
|
| 267 |
await self.progress.update(100, "Research complete!")
|
| 268 |
|
| 269 |
with open("output.log.json", "w", encoding="utf-8") as f:
|
|
@@ -282,7 +282,7 @@ class KNet:
|
|
| 282 |
if asyncio.current_task() and asyncio.current_task().cancelled():
|
| 283 |
raise asyncio.CancelledError("Research task was cancelled")
|
| 284 |
|
| 285 |
-
async def _generate_final_report(self,
|
| 286 |
try:
|
| 287 |
self._check_cancelled()
|
| 288 |
|
|
@@ -322,7 +322,7 @@ class KNet:
|
|
| 322 |
|
| 323 |
# Collate multimedia content
|
| 324 |
media_content = {"images": [], "videos": [], "links": []}
|
| 325 |
-
all_sources_data =
|
| 326 |
for data in all_sources_data:
|
| 327 |
if data.get("images"):
|
| 328 |
media_content["images"].extend(data["images"])
|
|
@@ -336,28 +336,16 @@ class KNet:
|
|
| 336 |
media_content["links"] = list({json.dumps(d, sort_keys=True) for d in media_content["links"]})
|
| 337 |
media_content["links"] = [json.loads(d) for d in media_content["links"]]
|
| 338 |
|
| 339 |
-
# Build research tree structure
|
| 340 |
-
def build_tree_structure(node: ResearchNode) -> Dict:
|
| 341 |
-
if not node:
|
| 342 |
-
return {}
|
| 343 |
-
sources = [d["url"] for d in node.data if d.get("url")]
|
| 344 |
-
return {
|
| 345 |
-
"query": node.query,
|
| 346 |
-
"depth": node.depth,
|
| 347 |
-
"sources": sources,
|
| 348 |
-
"children": [build_tree_structure(child) for child in node.children],
|
| 349 |
-
}
|
| 350 |
-
|
| 351 |
return {
|
| 352 |
"topic": topic,
|
| 353 |
"timestamp": datetime.now().isoformat(),
|
| 354 |
"content": raster_report,
|
| 355 |
"media": media_content,
|
| 356 |
-
"research_tree": build_tree_structure(
|
| 357 |
"metadata": {
|
| 358 |
-
"total_queries":
|
| 359 |
"total_sources": len(all_sources_data),
|
| 360 |
-
"max_depth_reached":
|
| 361 |
"total_tokens": self.token_count,
|
| 362 |
},
|
| 363 |
}
|
|
@@ -369,7 +357,7 @@ class KNet:
|
|
| 369 |
self.logger.error("GEMINI_RECITATION or NO_RESPONSE")
|
| 370 |
if retry_count < 3:
|
| 371 |
self.logger.error(f"Retrying final report:C:{retry_count} / 3", exc_info=True)
|
| 372 |
-
return await self._generate_final_report(
|
| 373 |
self.logger.error("Error generating final report", exc_info=True)
|
| 374 |
raise
|
| 375 |
|
|
@@ -470,7 +458,7 @@ class KNet:
|
|
| 470 |
raise
|
| 471 |
|
| 472 |
async def test(self, topic: str, progress_callback):
|
| 473 |
-
self.progress = ResearchProgress(progress_callback)
|
| 474 |
try:
|
| 475 |
for i in range(5):
|
| 476 |
self._check_cancelled()
|
|
|
|
| 149 |
|
| 150 |
|
| 151 |
class ResearchProgress:
|
| 152 |
+
def __init__(self, callback, master_node: ResearchNode):
|
| 153 |
self.progress = 0
|
| 154 |
self.callback = callback
|
| 155 |
+
self.master_node = master_node
|
| 156 |
|
| 157 |
async def update(self, progress: int, message: str):
|
| 158 |
self.progress = int(min(100, self.progress + progress)) # max 100
|
| 159 |
+
await self.callback({"progress": self.progress, "message": message, "research_tree": self.master_node.build_tree_structure()})
|
| 160 |
|
| 161 |
async def setter(self, progress: int, message: str):
|
| 162 |
self.progress = int(min(100, progress)) # max 100
|
| 163 |
+
await self.callback({"progress": self.progress, "message": message, "research_tree": self.master_node.build_tree_structure()})
|
| 164 |
|
| 165 |
|
| 166 |
class KNet:
|
|
|
|
| 181 |
self.num_sites_per_query = num_sites_per_query
|
| 182 |
|
| 183 |
# Global State
|
| 184 |
+
self.master_node = ResearchNode()
|
| 185 |
self.research_plan: list[str] = []
|
| 186 |
self.idx_research_plan: int = 0
|
| 187 |
self.ctx_researcher: list[str] = []
|
|
|
|
| 190 |
|
| 191 |
async def conduct_research(self, topic: str, progress_callback, max_depth: int, num_sites_per_query: int) -> dict | bool:
|
| 192 |
# Local Runtime State
|
| 193 |
+
self.progress = ResearchProgress(progress_callback, self.master_node)
|
| 194 |
self.max_depth = max_depth
|
| 195 |
self.num_sites_per_query = num_sites_per_query
|
| 196 |
|
|
|
|
| 211 |
]
|
| 212 |
self.logger.info(f"Research plan:\n{json.dumps(self.research_plan, indent=2)}")
|
| 213 |
|
|
|
|
|
|
|
| 214 |
await self.progress.update(0, "Starting research...")
|
| 215 |
|
| 216 |
# Iterate on research plan
|
|
|
|
| 227 |
)["branches"][0]
|
| 228 |
|
| 229 |
root_node = ResearchNode(query)
|
| 230 |
+
self.master_node.add_child(root_node.query, node=root_node)
|
| 231 |
to_explore = deque([(root_node, 1)]) # (node, depth) pairs
|
| 232 |
explored_queries = set() # {string, string, ...}
|
| 233 |
|
|
|
|
| 261 |
|
| 262 |
# Generate final report
|
| 263 |
await self.progress.update(100 / (len(self.research_plan) + 1), "Generating final report...")
|
| 264 |
+
final_report = await self._generate_final_report(topic)
|
| 265 |
|
| 266 |
+
self.logger.info(f"Research completed. Explored {len(explored_queries)} queries across {self.master_node.max_depth()} levels")
|
| 267 |
await self.progress.update(100, "Research complete!")
|
| 268 |
|
| 269 |
with open("output.log.json", "w", encoding="utf-8") as f:
|
|
|
|
| 282 |
if asyncio.current_task() and asyncio.current_task().cancelled():
|
| 283 |
raise asyncio.CancelledError("Research task was cancelled")
|
| 284 |
|
| 285 |
+
async def _generate_final_report(self, topic: str, retry_count: int = 1) -> Dict[str, Any]:
|
| 286 |
try:
|
| 287 |
self._check_cancelled()
|
| 288 |
|
|
|
|
| 322 |
|
| 323 |
# Collate multimedia content
|
| 324 |
media_content = {"images": [], "videos": [], "links": []}
|
| 325 |
+
all_sources_data = self.master_node.get_all_data()
|
| 326 |
for data in all_sources_data:
|
| 327 |
if data.get("images"):
|
| 328 |
media_content["images"].extend(data["images"])
|
|
|
|
| 336 |
media_content["links"] = list({json.dumps(d, sort_keys=True) for d in media_content["links"]})
|
| 337 |
media_content["links"] = [json.loads(d) for d in media_content["links"]]
|
| 338 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
return {
|
| 340 |
"topic": topic,
|
| 341 |
"timestamp": datetime.now().isoformat(),
|
| 342 |
"content": raster_report,
|
| 343 |
"media": media_content,
|
| 344 |
+
"research_tree": self.master_node.build_tree_structure(),
|
| 345 |
"metadata": {
|
| 346 |
+
"total_queries": self.master_node.total_children(),
|
| 347 |
"total_sources": len(all_sources_data),
|
| 348 |
+
"max_depth_reached": self.master_node.max_depth(),
|
| 349 |
"total_tokens": self.token_count,
|
| 350 |
},
|
| 351 |
}
|
|
|
|
| 357 |
self.logger.error("GEMINI_RECITATION or NO_RESPONSE")
|
| 358 |
if retry_count < 3:
|
| 359 |
self.logger.error(f"Retrying final report:C:{retry_count} / 3", exc_info=True)
|
| 360 |
+
return await self._generate_final_report(topic, retry_count + 1)
|
| 361 |
self.logger.error("Error generating final report", exc_info=True)
|
| 362 |
raise
|
| 363 |
|
|
|
|
| 458 |
raise
|
| 459 |
|
| 460 |
async def test(self, topic: str, progress_callback):
|
| 461 |
+
self.progress = ResearchProgress(progress_callback, self.master_node)
|
| 462 |
try:
|
| 463 |
for i in range(5):
|
| 464 |
self._check_cancelled()
|
backend/research_node.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
| 1 |
import copy
|
| 2 |
-
from typing import Any, Dict, List, Optional
|
| 3 |
|
| 4 |
|
| 5 |
class ResearchNode:
|
| 6 |
-
def __init__(self, query: str = "_", parent: Optional[
|
| 7 |
self.query = query
|
| 8 |
self.parent = parent
|
| 9 |
self.depth = depth
|
| 10 |
self.children: List[ResearchNode] = []
|
| 11 |
self.data: List[Dict[str, Any]] = []
|
| 12 |
|
| 13 |
-
def add_child(self, query: str, node: Optional[
|
| 14 |
if node:
|
| 15 |
child = node
|
| 16 |
child.parent = self
|
|
@@ -48,10 +48,14 @@ class ResearchNode:
|
|
| 48 |
data.extend(child.get_all_data())
|
| 49 |
return data
|
| 50 |
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
return {
|
| 53 |
"query": self.query,
|
| 54 |
"depth": self.depth,
|
| 55 |
-
"
|
| 56 |
-
"
|
| 57 |
}
|
|
|
|
| 1 |
import copy
|
| 2 |
+
from typing import Any, Dict, List, Optional, Self
|
| 3 |
|
| 4 |
|
| 5 |
class ResearchNode:
|
| 6 |
+
def __init__(self, query: str = "_", parent: Optional[Self] = None, depth: int = 0):
|
| 7 |
self.query = query
|
| 8 |
self.parent = parent
|
| 9 |
self.depth = depth
|
| 10 |
self.children: List[ResearchNode] = []
|
| 11 |
self.data: List[Dict[str, Any]] = []
|
| 12 |
|
| 13 |
+
def add_child(self, query: str, node: Optional[Self] = None) -> Self:
|
| 14 |
if node:
|
| 15 |
child = node
|
| 16 |
child.parent = self
|
|
|
|
| 48 |
data.extend(child.get_all_data())
|
| 49 |
return data
|
| 50 |
|
| 51 |
+
# Build research tree structure
|
| 52 |
+
def build_tree_structure(self) -> Dict:
|
| 53 |
+
if not self:
|
| 54 |
+
return {}
|
| 55 |
+
sources = {d["url"]: d["text"] for d in self.data if d.get("url") and d.get("text")}
|
| 56 |
return {
|
| 57 |
"query": self.query,
|
| 58 |
"depth": self.depth,
|
| 59 |
+
"sources": sources,
|
| 60 |
+
"children": [child.build_tree_structure() for child in self.children],
|
| 61 |
}
|
frontend/bun.lock
CHANGED
|
@@ -18,6 +18,7 @@
|
|
| 18 |
"@types/react-syntax-highlighter": "^15.5.13",
|
| 19 |
"class-variance-authority": "^0.7.1",
|
| 20 |
"clsx": "^2.1.1",
|
|
|
|
| 21 |
"eslint-config-next": "^15.2.4",
|
| 22 |
"lucide-react": "^0.479.0",
|
| 23 |
"next": "^15.2.4",
|
|
@@ -32,9 +33,11 @@
|
|
| 32 |
"socket.io-client": "^4.8.1",
|
| 33 |
"tailwind-merge": "^3.0.2",
|
| 34 |
"tailwindcss-animate": "^1.0.7",
|
|
|
|
| 35 |
"uuid": "^11.1.0",
|
| 36 |
},
|
| 37 |
"devDependencies": {
|
|
|
|
| 38 |
"@types/node": "^20.17.28",
|
| 39 |
"@types/react": "^18.3.20",
|
| 40 |
"@types/react-dom": "^18.3.5",
|
|
@@ -336,12 +339,76 @@
|
|
| 336 |
|
| 337 |
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
| 338 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
| 340 |
|
| 341 |
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
|
| 342 |
|
| 343 |
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
| 344 |
|
|
|
|
|
|
|
| 345 |
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
| 346 |
|
| 347 |
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
|
@@ -562,6 +629,68 @@
|
|
| 562 |
|
| 563 |
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
| 564 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
|
| 566 |
|
| 567 |
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
|
@@ -586,6 +715,8 @@
|
|
| 586 |
|
| 587 |
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
| 588 |
|
|
|
|
|
|
|
| 589 |
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
| 590 |
|
| 591 |
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
|
|
@@ -788,6 +919,8 @@
|
|
| 788 |
|
| 789 |
"human-signals": ["human-signals@4.3.1", "", {}, "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ=="],
|
| 790 |
|
|
|
|
|
|
|
| 791 |
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
| 792 |
|
| 793 |
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
|
@@ -802,6 +935,8 @@
|
|
| 802 |
|
| 803 |
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
| 804 |
|
|
|
|
|
|
|
| 805 |
"is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="],
|
| 806 |
|
| 807 |
"is-alphanumerical": ["is-alphanumerical@1.0.4", "", { "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A=="],
|
|
@@ -1224,8 +1359,12 @@
|
|
| 1224 |
|
| 1225 |
"reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="],
|
| 1226 |
|
|
|
|
|
|
|
| 1227 |
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
| 1228 |
|
|
|
|
|
|
|
| 1229 |
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
| 1230 |
|
| 1231 |
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
|
@@ -1234,6 +1373,8 @@
|
|
| 1234 |
|
| 1235 |
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
| 1236 |
|
|
|
|
|
|
|
| 1237 |
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
| 1238 |
|
| 1239 |
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
|
@@ -1340,6 +1481,8 @@
|
|
| 1340 |
|
| 1341 |
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
|
| 1342 |
|
|
|
|
|
|
|
| 1343 |
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
| 1344 |
|
| 1345 |
"tinyglobby": ["tinyglobby@0.2.12", "", { "dependencies": { "fdir": "^6.4.3", "picomatch": "^4.0.2" } }, "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww=="],
|
|
@@ -1510,6 +1653,8 @@
|
|
| 1510 |
|
| 1511 |
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
| 1512 |
|
|
|
|
|
|
|
| 1513 |
"decode-named-character-reference/character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
|
| 1514 |
|
| 1515 |
"eslint/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
|
|
|
| 18 |
"@types/react-syntax-highlighter": "^15.5.13",
|
| 19 |
"class-variance-authority": "^0.7.1",
|
| 20 |
"clsx": "^2.1.1",
|
| 21 |
+
"d3": "^7.9.0",
|
| 22 |
"eslint-config-next": "^15.2.4",
|
| 23 |
"lucide-react": "^0.479.0",
|
| 24 |
"next": "^15.2.4",
|
|
|
|
| 33 |
"socket.io-client": "^4.8.1",
|
| 34 |
"tailwind-merge": "^3.0.2",
|
| 35 |
"tailwindcss-animate": "^1.0.7",
|
| 36 |
+
"three": "^0.175.0",
|
| 37 |
"uuid": "^11.1.0",
|
| 38 |
},
|
| 39 |
"devDependencies": {
|
| 40 |
+
"@types/d3": "^7.4.3",
|
| 41 |
"@types/node": "^20.17.28",
|
| 42 |
"@types/react": "^18.3.20",
|
| 43 |
"@types/react-dom": "^18.3.5",
|
|
|
|
| 339 |
|
| 340 |
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
| 341 |
|
| 342 |
+
"@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="],
|
| 343 |
+
|
| 344 |
+
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
| 345 |
+
|
| 346 |
+
"@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="],
|
| 347 |
+
|
| 348 |
+
"@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="],
|
| 349 |
+
|
| 350 |
+
"@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="],
|
| 351 |
+
|
| 352 |
+
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
|
| 353 |
+
|
| 354 |
+
"@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="],
|
| 355 |
+
|
| 356 |
+
"@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="],
|
| 357 |
+
|
| 358 |
+
"@types/d3-dispatch": ["@types/d3-dispatch@3.0.6", "", {}, "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ=="],
|
| 359 |
+
|
| 360 |
+
"@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="],
|
| 361 |
+
|
| 362 |
+
"@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="],
|
| 363 |
+
|
| 364 |
+
"@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
|
| 365 |
+
|
| 366 |
+
"@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="],
|
| 367 |
+
|
| 368 |
+
"@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="],
|
| 369 |
+
|
| 370 |
+
"@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="],
|
| 371 |
+
|
| 372 |
+
"@types/d3-geo": ["@types/d3-geo@3.1.0", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="],
|
| 373 |
+
|
| 374 |
+
"@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="],
|
| 375 |
+
|
| 376 |
+
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
|
| 377 |
+
|
| 378 |
+
"@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
|
| 379 |
+
|
| 380 |
+
"@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="],
|
| 381 |
+
|
| 382 |
+
"@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="],
|
| 383 |
+
|
| 384 |
+
"@types/d3-random": ["@types/d3-random@3.0.3", "", {}, "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="],
|
| 385 |
+
|
| 386 |
+
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
|
| 387 |
+
|
| 388 |
+
"@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="],
|
| 389 |
+
|
| 390 |
+
"@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="],
|
| 391 |
+
|
| 392 |
+
"@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="],
|
| 393 |
+
|
| 394 |
+
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
|
| 395 |
+
|
| 396 |
+
"@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="],
|
| 397 |
+
|
| 398 |
+
"@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
|
| 399 |
+
|
| 400 |
+
"@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="],
|
| 401 |
+
|
| 402 |
+
"@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="],
|
| 403 |
+
|
| 404 |
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
| 405 |
|
| 406 |
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
|
| 407 |
|
| 408 |
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
| 409 |
|
| 410 |
+
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
|
| 411 |
+
|
| 412 |
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
| 413 |
|
| 414 |
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
|
|
|
| 629 |
|
| 630 |
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
| 631 |
|
| 632 |
+
"d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="],
|
| 633 |
+
|
| 634 |
+
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
|
| 635 |
+
|
| 636 |
+
"d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="],
|
| 637 |
+
|
| 638 |
+
"d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="],
|
| 639 |
+
|
| 640 |
+
"d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="],
|
| 641 |
+
|
| 642 |
+
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
|
| 643 |
+
|
| 644 |
+
"d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="],
|
| 645 |
+
|
| 646 |
+
"d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="],
|
| 647 |
+
|
| 648 |
+
"d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="],
|
| 649 |
+
|
| 650 |
+
"d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="],
|
| 651 |
+
|
| 652 |
+
"d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="],
|
| 653 |
+
|
| 654 |
+
"d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
|
| 655 |
+
|
| 656 |
+
"d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="],
|
| 657 |
+
|
| 658 |
+
"d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="],
|
| 659 |
+
|
| 660 |
+
"d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="],
|
| 661 |
+
|
| 662 |
+
"d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="],
|
| 663 |
+
|
| 664 |
+
"d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="],
|
| 665 |
+
|
| 666 |
+
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
|
| 667 |
+
|
| 668 |
+
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
|
| 669 |
+
|
| 670 |
+
"d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="],
|
| 671 |
+
|
| 672 |
+
"d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="],
|
| 673 |
+
|
| 674 |
+
"d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="],
|
| 675 |
+
|
| 676 |
+
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
|
| 677 |
+
|
| 678 |
+
"d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="],
|
| 679 |
+
|
| 680 |
+
"d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="],
|
| 681 |
+
|
| 682 |
+
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
|
| 683 |
+
|
| 684 |
+
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
|
| 685 |
+
|
| 686 |
+
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
|
| 687 |
+
|
| 688 |
+
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
|
| 689 |
+
|
| 690 |
+
"d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="],
|
| 691 |
+
|
| 692 |
+
"d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="],
|
| 693 |
+
|
| 694 |
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
|
| 695 |
|
| 696 |
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
|
|
|
| 715 |
|
| 716 |
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
| 717 |
|
| 718 |
+
"delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="],
|
| 719 |
+
|
| 720 |
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
| 721 |
|
| 722 |
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
|
|
|
|
| 919 |
|
| 920 |
"human-signals": ["human-signals@4.3.1", "", {}, "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ=="],
|
| 921 |
|
| 922 |
+
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
| 923 |
+
|
| 924 |
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
| 925 |
|
| 926 |
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
|
|
|
| 935 |
|
| 936 |
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
| 937 |
|
| 938 |
+
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
|
| 939 |
+
|
| 940 |
"is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="],
|
| 941 |
|
| 942 |
"is-alphanumerical": ["is-alphanumerical@1.0.4", "", { "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A=="],
|
|
|
|
| 1359 |
|
| 1360 |
"reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="],
|
| 1361 |
|
| 1362 |
+
"robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
|
| 1363 |
+
|
| 1364 |
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
| 1365 |
|
| 1366 |
+
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
|
| 1367 |
+
|
| 1368 |
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
|
| 1369 |
|
| 1370 |
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
|
|
|
| 1373 |
|
| 1374 |
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
| 1375 |
|
| 1376 |
+
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
| 1377 |
+
|
| 1378 |
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
| 1379 |
|
| 1380 |
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
|
|
|
| 1481 |
|
| 1482 |
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
|
| 1483 |
|
| 1484 |
+
"three": ["three@0.175.0", "", {}, "sha512-nNE3pnTHxXN/Phw768u0Grr7W4+rumGg/H6PgeseNJojkJtmeHJfZWi41Gp2mpXl1pg1pf1zjwR4McM1jTqkpg=="],
|
| 1485 |
+
|
| 1486 |
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
| 1487 |
|
| 1488 |
"tinyglobby": ["tinyglobby@0.2.12", "", { "dependencies": { "fdir": "^6.4.3", "picomatch": "^4.0.2" } }, "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww=="],
|
|
|
|
| 1653 |
|
| 1654 |
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
| 1655 |
|
| 1656 |
+
"d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
| 1657 |
+
|
| 1658 |
"decode-named-character-reference/character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
|
| 1659 |
|
| 1660 |
"eslint/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
frontend/package.json
CHANGED
|
@@ -23,6 +23,7 @@
|
|
| 23 |
"@types/react-syntax-highlighter": "^15.5.13",
|
| 24 |
"class-variance-authority": "^0.7.1",
|
| 25 |
"clsx": "^2.1.1",
|
|
|
|
| 26 |
"eslint-config-next": "^15.2.4",
|
| 27 |
"lucide-react": "^0.479.0",
|
| 28 |
"next": "^15.2.4",
|
|
@@ -37,9 +38,11 @@
|
|
| 37 |
"socket.io-client": "^4.8.1",
|
| 38 |
"tailwind-merge": "^3.0.2",
|
| 39 |
"tailwindcss-animate": "^1.0.7",
|
|
|
|
| 40 |
"uuid": "^11.1.0"
|
| 41 |
},
|
| 42 |
"devDependencies": {
|
|
|
|
| 43 |
"@types/node": "^20.17.28",
|
| 44 |
"@types/react": "^18.3.20",
|
| 45 |
"@types/react-dom": "^18.3.5",
|
|
|
|
| 23 |
"@types/react-syntax-highlighter": "^15.5.13",
|
| 24 |
"class-variance-authority": "^0.7.1",
|
| 25 |
"clsx": "^2.1.1",
|
| 26 |
+
"d3": "^7.9.0",
|
| 27 |
"eslint-config-next": "^15.2.4",
|
| 28 |
"lucide-react": "^0.479.0",
|
| 29 |
"next": "^15.2.4",
|
|
|
|
| 38 |
"socket.io-client": "^4.8.1",
|
| 39 |
"tailwind-merge": "^3.0.2",
|
| 40 |
"tailwindcss-animate": "^1.0.7",
|
| 41 |
+
"three": "^0.175.0",
|
| 42 |
"uuid": "^11.1.0"
|
| 43 |
},
|
| 44 |
"devDependencies": {
|
| 45 |
+
"@types/d3": "^7.4.3",
|
| 46 |
"@types/node": "^20.17.28",
|
| 47 |
"@types/react": "^18.3.20",
|
| 48 |
"@types/react-dom": "^18.3.5",
|
frontend/src/components/ui/ChatLayout.tsx
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
|
|
| 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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
| 6 |
-
import { LayoutGrid, Menu, MessageCircle, Settings } from "lucide-react";
|
| 7 |
-
import React from "react";
|
| 8 |
import { ThemeToggle } from "./ThemeToggle";
|
| 9 |
import Link from "next/link";
|
| 10 |
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
| 11 |
import { Sheet, SheetContent, SheetTrigger, SheetTitle, SheetDescription } from "@/components/ui/sheet";
|
|
|
|
|
|
|
| 12 |
|
| 13 |
interface ChatLayoutProps {
|
| 14 |
sidebar: React.ReactNode;
|
|
@@ -17,6 +20,25 @@ interface ChatLayoutProps {
|
|
| 17 |
}
|
| 18 |
|
| 19 |
const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsPanel }) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
return (
|
| 21 |
<div className="h-screen flex flex-col">
|
| 22 |
<header className="border-b-2 h-14 flex items-center px-6">
|
|
@@ -70,7 +92,7 @@ const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsP
|
|
| 70 |
<ResizableHandle withHandle className="hidden md:flex" />
|
| 71 |
|
| 72 |
<ResizablePanel defaultSize={85} className="w-full md:w-auto">
|
| 73 |
-
<Tabs
|
| 74 |
<div className="p-4">
|
| 75 |
<TabsList className="">
|
| 76 |
<TabsTrigger value="chat" className="flex items-center gap-2">
|
|
@@ -84,18 +106,12 @@ const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsP
|
|
| 84 |
</TabsList>
|
| 85 |
</div>
|
| 86 |
|
| 87 |
-
<TabsContent value="chat" className="overflow-auto flex
|
| 88 |
{mainContent}
|
| 89 |
</TabsContent>
|
| 90 |
|
| 91 |
-
<TabsContent value="visualizations" className="p-4 flex-1">
|
| 92 |
-
<
|
| 93 |
-
<div className="text-center">
|
| 94 |
-
<LayoutGrid className="mx-auto h-12 w-12 mb-4 opacity-30" />
|
| 95 |
-
<h3 className="text-lg font-medium mb-2">Visualizations Coming Soon</h3>
|
| 96 |
-
<p>View graphs, charts, and other visual representations of your research data.</p>
|
| 97 |
-
</div>
|
| 98 |
-
</div>
|
| 99 |
</TabsContent>
|
| 100 |
</Tabs>
|
| 101 |
</ResizablePanel>
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
import { Button } from "@/components/ui/button";
|
| 3 |
import { Card } from "@/components/ui/card";
|
| 4 |
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
| 5 |
import { ScrollArea } from "@/components/ui/scroll-area";
|
| 6 |
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
| 7 |
+
import { LayoutGrid, Menu, MessageCircle, Network, Settings } from "lucide-react";
|
| 8 |
+
import React, { useState } from "react";
|
| 9 |
import { ThemeToggle } from "./ThemeToggle";
|
| 10 |
import Link from "next/link";
|
| 11 |
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
| 12 |
import { Sheet, SheetContent, SheetTrigger, SheetTitle, SheetDescription } from "@/components/ui/sheet";
|
| 13 |
+
import ResearchGraph from "@/components/visualizations/ResearchGraph";
|
| 14 |
+
import { useChatContext } from "@/lib/store/ChatContext";
|
| 15 |
|
| 16 |
interface ChatLayoutProps {
|
| 17 |
sidebar: React.ReactNode;
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsPanel }) => {
|
| 23 |
+
const { chatState } = useChatContext();
|
| 24 |
+
const [activeTab, setActiveTab] = useState<string>("chat");
|
| 25 |
+
const [visualizationType, setVisualizationType] = useState<"d3" | "reactflow">("d3");
|
| 26 |
+
|
| 27 |
+
// Get the latest research tree from messages
|
| 28 |
+
const latestResearchTree = React.useMemo(() => {
|
| 29 |
+
// First look for the most recent completed research message
|
| 30 |
+
const completedResearch = [...chatState.messages].reverse().find((msg) => msg.role === "assistant" && !msg.isProgress && msg.research_tree);
|
| 31 |
+
|
| 32 |
+
if (completedResearch?.research_tree) {
|
| 33 |
+
return completedResearch.research_tree;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// If no completed research, look for progress messages
|
| 37 |
+
const progressMessage = [...chatState.messages].reverse().find((msg) => msg.role === "assistant" && msg.isProgress && msg.research_tree);
|
| 38 |
+
|
| 39 |
+
return progressMessage?.research_tree;
|
| 40 |
+
}, [chatState.messages]);
|
| 41 |
+
|
| 42 |
return (
|
| 43 |
<div className="h-screen flex flex-col">
|
| 44 |
<header className="border-b-2 h-14 flex items-center px-6">
|
|
|
|
| 92 |
<ResizableHandle withHandle className="hidden md:flex" />
|
| 93 |
|
| 94 |
<ResizablePanel defaultSize={85} className="w-full md:w-auto">
|
| 95 |
+
<Tabs value={activeTab} onValueChange={setActiveTab} className="h-full flex flex-col">
|
| 96 |
<div className="p-4">
|
| 97 |
<TabsList className="">
|
| 98 |
<TabsTrigger value="chat" className="flex items-center gap-2">
|
|
|
|
| 106 |
</TabsList>
|
| 107 |
</div>
|
| 108 |
|
| 109 |
+
<TabsContent value="chat" className="overflow-auto flex-1 !mt-0" tabIndex={-1}>
|
| 110 |
{mainContent}
|
| 111 |
</TabsContent>
|
| 112 |
|
| 113 |
+
<TabsContent value="visualizations" className="p-4 pt-0 overflow-auto flex-1 !mt-0" tabIndex={-1}>
|
| 114 |
+
<ResearchGraph researchTree={latestResearchTree} />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
</TabsContent>
|
| 116 |
</Tabs>
|
| 117 |
</ResizablePanel>
|
frontend/src/components/visualizations/ResearchGraph.tsx
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import { Badge } from "@/components/ui/badge";
|
| 3 |
+
import { Card, CardContent, CardTitle } from "@/components/ui/card";
|
| 4 |
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
| 5 |
+
import { ResearchTree } from "@/lib/types";
|
| 6 |
+
import * as d3 from "d3";
|
| 7 |
+
import React, { useEffect, useRef, useState } from "react";
|
| 8 |
+
|
| 9 |
+
interface GraphNode extends d3.SimulationNodeDatum {
|
| 10 |
+
id: string;
|
| 11 |
+
name: string;
|
| 12 |
+
size: number;
|
| 13 |
+
depth: number;
|
| 14 |
+
url?: string;
|
| 15 |
+
width?: number;
|
| 16 |
+
height?: number;
|
| 17 |
+
sources?: Record<string, string>;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
interface GraphLink extends d3.SimulationLinkDatum<GraphNode> {
|
| 21 |
+
source: string | GraphNode;
|
| 22 |
+
target: string | GraphNode;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
interface ResearchGraphProps {
|
| 26 |
+
researchTree: ResearchTree | undefined;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
const ResearchGraph: React.FC<ResearchGraphProps> = ({ researchTree }) => {
|
| 30 |
+
const selectedNodeRef = useRef<GraphNode | null>(null);
|
| 31 |
+
const [dialogOpen, setDialogOpen] = useState(false);
|
| 32 |
+
const [selectedNodeContent, setSelectedNodeContent] = useState<{ title: string; sources: Record<string, string> }>({
|
| 33 |
+
title: "",
|
| 34 |
+
sources: {},
|
| 35 |
+
});
|
| 36 |
+
const [nodePositions, setNodePositions] = useState<Record<string, { x: number; y: number }>>({});
|
| 37 |
+
const [nodesState, setNodesState] = useState<GraphNode[]>([]); // Store nodes for both D3 and rendering
|
| 38 |
+
const containerRef = useRef<HTMLDivElement>(null);
|
| 39 |
+
const [dimensions, setDimensions] = useState({ width: 1200, height: 800 });
|
| 40 |
+
|
| 41 |
+
// Resize observer to update graph size
|
| 42 |
+
useEffect(() => {
|
| 43 |
+
if (!containerRef.current) return;
|
| 44 |
+
const handleResize = () => {
|
| 45 |
+
const rect = containerRef.current?.getBoundingClientRect();
|
| 46 |
+
if (rect) {
|
| 47 |
+
setDimensions({ width: rect.width, height: rect.height });
|
| 48 |
+
}
|
| 49 |
+
};
|
| 50 |
+
handleResize();
|
| 51 |
+
window.addEventListener("resize", handleResize);
|
| 52 |
+
return () => window.removeEventListener("resize", handleResize);
|
| 53 |
+
}, []);
|
| 54 |
+
|
| 55 |
+
useEffect(() => {
|
| 56 |
+
if (!researchTree) return;
|
| 57 |
+
|
| 58 |
+
// Prepare data
|
| 59 |
+
const nodes: GraphNode[] = [];
|
| 60 |
+
const links: GraphLink[] = [];
|
| 61 |
+
|
| 62 |
+
const processTree = (tree: ResearchTree, parentId?: string) => {
|
| 63 |
+
const nodeId = `${tree.query}_${tree.depth}`;
|
| 64 |
+
const textLength = tree.query.length;
|
| 65 |
+
const nodeWidth = Math.max(120, Math.min(300, textLength * 6));
|
| 66 |
+
const nodeHeight = 60;
|
| 67 |
+
|
| 68 |
+
nodes.push({
|
| 69 |
+
id: nodeId,
|
| 70 |
+
name: tree.query,
|
| 71 |
+
size: Object.keys(tree.sources).length + 10,
|
| 72 |
+
depth: tree.depth,
|
| 73 |
+
width: nodeWidth,
|
| 74 |
+
height: nodeHeight,
|
| 75 |
+
sources: tree.sources,
|
| 76 |
+
});
|
| 77 |
+
|
| 78 |
+
if (parentId) {
|
| 79 |
+
links.push({
|
| 80 |
+
source: parentId,
|
| 81 |
+
target: nodeId,
|
| 82 |
+
});
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
tree.children.forEach((child) => {
|
| 86 |
+
processTree(child, nodeId);
|
| 87 |
+
});
|
| 88 |
+
};
|
| 89 |
+
|
| 90 |
+
processTree(researchTree);
|
| 91 |
+
setNodesState([...nodes]); // Save nodes for rendering
|
| 92 |
+
|
| 93 |
+
const { width, height } = dimensions;
|
| 94 |
+
const simulation = d3
|
| 95 |
+
.forceSimulation<GraphNode>(nodes)
|
| 96 |
+
.force(
|
| 97 |
+
"link",
|
| 98 |
+
d3
|
| 99 |
+
.forceLink<GraphNode, GraphLink>()
|
| 100 |
+
.id((d: GraphNode) => d.id)
|
| 101 |
+
.links(links)
|
| 102 |
+
.distance(80)
|
| 103 |
+
)
|
| 104 |
+
.force("charge", d3.forceManyBody().strength(-800))
|
| 105 |
+
.force("center", d3.forceCenter(width / 2, height / 2))
|
| 106 |
+
.force(
|
| 107 |
+
"collision",
|
| 108 |
+
d3.forceCollide().radius((d: d3.SimulationNodeDatum) => {
|
| 109 |
+
const node = d as GraphNode;
|
| 110 |
+
return Math.max(node.width || 0, node.height || 0) / 2 + 40;
|
| 111 |
+
})
|
| 112 |
+
)
|
| 113 |
+
.on("tick", () => {
|
| 114 |
+
setNodePositions((prev) => {
|
| 115 |
+
const updated: Record<string, { x: number; y: number }> = {};
|
| 116 |
+
nodes.forEach((n) => {
|
| 117 |
+
// Clamp positions to container
|
| 118 |
+
updated[n.id] = {
|
| 119 |
+
x: Math.max((n.width || 120) / 2, Math.min(n.x || 0, width - (n.width || 120) / 2)),
|
| 120 |
+
y: Math.max((n.height || 60) / 2, Math.min(n.y || 0, height - (n.height || 60) / 2)),
|
| 121 |
+
};
|
| 122 |
+
});
|
| 123 |
+
return updated;
|
| 124 |
+
});
|
| 125 |
+
});
|
| 126 |
+
|
| 127 |
+
return () => {
|
| 128 |
+
simulation.stop();
|
| 129 |
+
};
|
| 130 |
+
}, [researchTree, dimensions]);
|
| 131 |
+
|
| 132 |
+
const formatSourceContent = () => {
|
| 133 |
+
return Object.entries(selectedNodeContent.sources).map(([url, content]) => (
|
| 134 |
+
<div key={url} className="mb-6 border-b pb-4">
|
| 135 |
+
<h3 className="text-base font-semibold mb-2">
|
| 136 |
+
<a href={url} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline break-words">
|
| 137 |
+
{url}
|
| 138 |
+
</a>
|
| 139 |
+
</h3>
|
| 140 |
+
<div className="prose prose-sm dark:prose-invert max-w-none break-words">{content}</div>
|
| 141 |
+
</div>
|
| 142 |
+
));
|
| 143 |
+
};
|
| 144 |
+
|
| 145 |
+
const renderNodeCard = (node: GraphNode) => {
|
| 146 |
+
const pos = nodePositions[node.id] || { x: 0, y: 0 };
|
| 147 |
+
return (
|
| 148 |
+
<div
|
| 149 |
+
key={node.id}
|
| 150 |
+
style={{
|
| 151 |
+
position: "absolute",
|
| 152 |
+
left: pos.x - (node.width || 120) / 2,
|
| 153 |
+
top: pos.y - (node.height || 60) / 2,
|
| 154 |
+
width: node.width || 120,
|
| 155 |
+
zIndex: 2,
|
| 156 |
+
cursor: node.url ? "pointer" : "default",
|
| 157 |
+
userSelect: "none",
|
| 158 |
+
pointerEvents: "auto",
|
| 159 |
+
}}
|
| 160 |
+
tabIndex={0}
|
| 161 |
+
className="select-none focus:outline-none"
|
| 162 |
+
onClick={(e) => {
|
| 163 |
+
e.stopPropagation();
|
| 164 |
+
selectedNodeRef.current = node;
|
| 165 |
+
if (node.url) {
|
| 166 |
+
window.open(node.url, "_blank");
|
| 167 |
+
} else if (node.sources && Object.keys(node.sources).length > 0) {
|
| 168 |
+
setSelectedNodeContent({ title: node.name, sources: node.sources });
|
| 169 |
+
setDialogOpen(true);
|
| 170 |
+
}
|
| 171 |
+
}}
|
| 172 |
+
onMouseDown={(e) => e.stopPropagation()}>
|
| 173 |
+
<Card className={"bg-muted text-white cursor-pointer" + (node.name === "_" ? " w-32 h-[5rem]" : "")}>
|
| 174 |
+
<CardTitle className={`p-2 pb-0 text-sm ${node.name === "_" ? "text-lg grid place-items-center h-4/5 w-full" : ""}`} title={node.name}>
|
| 175 |
+
{node.name === "_" ? "Master Node" : node.name}
|
| 176 |
+
</CardTitle>
|
| 177 |
+
{node.name !== "_" && (
|
| 178 |
+
<CardContent className="p-2 flex flex-wrap gap-1 w-full">
|
| 179 |
+
<Badge variant="secondary" className="w-full bg-white/20 text-white border-white/20 grid place-items-center" title={node.name}>
|
| 180 |
+
Source
|
| 181 |
+
</Badge>
|
| 182 |
+
</CardContent>
|
| 183 |
+
)}
|
| 184 |
+
</Card>
|
| 185 |
+
</div>
|
| 186 |
+
);
|
| 187 |
+
};
|
| 188 |
+
|
| 189 |
+
return (
|
| 190 |
+
<div ref={containerRef} className="flex flex-col h-full flex-1 relative" style={{ minHeight: 600 }} onClick={() => setDialogOpen(false)}>
|
| 191 |
+
<Card className="p-4 mb-4" id="graph-info-panel">
|
| 192 |
+
<h3 className="text-lg font-medium">Research Visualization</h3>
|
| 193 |
+
<p className="text-sm text-muted-foreground">Click on a node to see details. Source nodes are shown in purple. Click a node to open its source or view sources.</p>
|
| 194 |
+
</Card>
|
| 195 |
+
<div className="flex-1 border rounded-lg bg-card flex relative overflow-hidden" style={{ minHeight: 600 }}>
|
| 196 |
+
{!researchTree ? (
|
| 197 |
+
<div className="h-full w-full flex items-center justify-center text-muted-foreground">
|
| 198 |
+
<p>No research data available yet. Start a conversation to begin research.</p>
|
| 199 |
+
</div>
|
| 200 |
+
) : (
|
| 201 |
+
<div style={{ width: "100%", height: "100%", position: "relative" }}>
|
| 202 |
+
{/* Background for interaction */}
|
| 203 |
+
<div style={{ position: "absolute", inset: 0, zIndex: 0, pointerEvents: "auto" }} onClick={() => setDialogOpen(false)} />
|
| 204 |
+
{/* SVG for links only */}
|
| 205 |
+
<svg width={dimensions.width} height={dimensions.height} style={{ position: "absolute", top: 0, left: 0, zIndex: 1, pointerEvents: "none" }}>
|
| 206 |
+
{(() => {
|
| 207 |
+
if (!researchTree) return null;
|
| 208 |
+
const nodes: GraphNode[] = [];
|
| 209 |
+
const links: GraphLink[] = [];
|
| 210 |
+
const processTree = (tree: ResearchTree, parentId?: string) => {
|
| 211 |
+
const nodeId = `${tree.query}_${tree.depth}`;
|
| 212 |
+
nodes.push({ id: nodeId, name: tree.query, size: 10, depth: tree.depth });
|
| 213 |
+
if (parentId) {
|
| 214 |
+
links.push({ source: parentId, target: nodeId });
|
| 215 |
+
}
|
| 216 |
+
tree.children.forEach((child) => processTree(child, nodeId));
|
| 217 |
+
};
|
| 218 |
+
processTree(researchTree);
|
| 219 |
+
return links.map((l, i) => {
|
| 220 |
+
const src = nodePositions[(typeof l.source === "string" ? l.source : l.source.id) as string];
|
| 221 |
+
const tgt = nodePositions[(typeof l.target === "string" ? l.target : l.target.id) as string];
|
| 222 |
+
if (!src || !tgt) return null;
|
| 223 |
+
return <line key={i} x1={src.x} y1={src.y} x2={tgt.x} y2={tgt.y} stroke="#bbb" strokeWidth={2} strokeOpacity={0.7} />;
|
| 224 |
+
});
|
| 225 |
+
})()}
|
| 226 |
+
</svg>
|
| 227 |
+
{/* Render node cards using nodesState */}
|
| 228 |
+
{nodesState.map(renderNodeCard)}
|
| 229 |
+
</div>
|
| 230 |
+
)}
|
| 231 |
+
</div>
|
| 232 |
+
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
| 233 |
+
<DialogContent className="max-w-3xl max-h-[80vh] overflow-hidden flex flex-col">
|
| 234 |
+
<DialogHeader>
|
| 235 |
+
<DialogTitle className="text-xl font-semibold">Sources for: {selectedNodeContent.title}</DialogTitle>
|
| 236 |
+
</DialogHeader>
|
| 237 |
+
<div className="flex-1 overflow-y-auto py-4">{formatSourceContent()}</div>
|
| 238 |
+
</DialogContent>
|
| 239 |
+
</Dialog>
|
| 240 |
+
</div>
|
| 241 |
+
);
|
| 242 |
+
};
|
| 243 |
+
|
| 244 |
+
export default ResearchGraph;
|
frontend/src/lib/store/ChatContext.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
import { ChatData, ChatState, Conversation, Message, ResearchOptions, ResearchResults, StatusUpdate } from "@/lib/types";
|
| 4 |
import { ReactNode, createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
| 5 |
import { v4 as uuidv4 } from "uuid";
|
| 6 |
import { disconnectSocket, getSocket, initializeSocket } from "@/lib/socket";
|
|
@@ -101,13 +101,9 @@ export const ChatProvider = ({ children }: { children: ReactNode }) => {
|
|
| 101 |
if (conversation) {
|
| 102 |
// Check if any loaded message has isProgress true
|
| 103 |
let messages = conversation.messages;
|
| 104 |
-
if (messages.some(msg => msg.isProgress === true)) {
|
| 105 |
// Convert any progress messages to error messages
|
| 106 |
-
messages = messages.map(msg =>
|
| 107 |
-
msg.isProgress === true
|
| 108 |
-
? { ...msg, content: "Connection error", isProgress: false }
|
| 109 |
-
: msg
|
| 110 |
-
);
|
| 111 |
}
|
| 112 |
|
| 113 |
setChatState((prev) => ({ ...prev, messages, isLoading: false }));
|
|
@@ -128,17 +124,13 @@ export const ChatProvider = ({ children }: { children: ReactNode }) => {
|
|
| 128 |
|
| 129 |
// When socket disconnects, update any progress messages to show connection error
|
| 130 |
setChatState((prevState) => {
|
| 131 |
-
const updatedMessages = prevState.messages.map(msg =>
|
| 132 |
-
msg.isProgress === true
|
| 133 |
-
? { ...msg, content: "Connection error", isProgress: false }
|
| 134 |
-
: msg
|
| 135 |
-
);
|
| 136 |
|
| 137 |
return {
|
| 138 |
...prevState,
|
| 139 |
messages: updatedMessages,
|
| 140 |
isLoading: false,
|
| 141 |
-
error: "Lost connection to research server"
|
| 142 |
};
|
| 143 |
});
|
| 144 |
});
|
|
@@ -153,15 +145,16 @@ export const ChatProvider = ({ children }: { children: ReactNode }) => {
|
|
| 153 |
const lastProgressIndex = messages.findLastIndex((msg) => msg.role === "assistant" && msg.isProgress === true);
|
| 154 |
|
| 155 |
if (lastProgressIndex !== -1) {
|
| 156 |
-
// Update existing progress message
|
| 157 |
messages[lastProgressIndex] = {
|
| 158 |
...messages[lastProgressIndex],
|
| 159 |
content: progressText,
|
| 160 |
progress: progress,
|
| 161 |
timestamp: new Date(),
|
|
|
|
| 162 |
};
|
| 163 |
} else {
|
| 164 |
-
// Add new progress message
|
| 165 |
messages.push({
|
| 166 |
id: uuidv4(),
|
| 167 |
content: progressText,
|
|
@@ -169,6 +162,8 @@ export const ChatProvider = ({ children }: { children: ReactNode }) => {
|
|
| 169 |
timestamp: new Date(),
|
| 170 |
progress: progress,
|
| 171 |
isProgress: true,
|
|
|
|
|
|
|
| 172 |
});
|
| 173 |
}
|
| 174 |
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import { ChatData, ChatState, Conversation, Message, ResearchOptions, ResearchResults, ResearchTree, StatusUpdate } from "@/lib/types";
|
| 4 |
import { ReactNode, createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
| 5 |
import { v4 as uuidv4 } from "uuid";
|
| 6 |
import { disconnectSocket, getSocket, initializeSocket } from "@/lib/socket";
|
|
|
|
| 101 |
if (conversation) {
|
| 102 |
// Check if any loaded message has isProgress true
|
| 103 |
let messages = conversation.messages;
|
| 104 |
+
if (messages.some((msg) => msg.isProgress === true)) {
|
| 105 |
// Convert any progress messages to error messages
|
| 106 |
+
messages = messages.map((msg) => (msg.isProgress === true ? { ...msg, content: "Connection error", isProgress: false } : msg));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
}
|
| 108 |
|
| 109 |
setChatState((prev) => ({ ...prev, messages, isLoading: false }));
|
|
|
|
| 124 |
|
| 125 |
// When socket disconnects, update any progress messages to show connection error
|
| 126 |
setChatState((prevState) => {
|
| 127 |
+
const updatedMessages = prevState.messages.map((msg) => (msg.isProgress === true ? { ...msg, content: "Connection error", isProgress: false } : msg));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
return {
|
| 130 |
...prevState,
|
| 131 |
messages: updatedMessages,
|
| 132 |
isLoading: false,
|
| 133 |
+
error: "Lost connection to research server",
|
| 134 |
};
|
| 135 |
});
|
| 136 |
});
|
|
|
|
| 145 |
const lastProgressIndex = messages.findLastIndex((msg) => msg.role === "assistant" && msg.isProgress === true);
|
| 146 |
|
| 147 |
if (lastProgressIndex !== -1) {
|
| 148 |
+
// Update existing progress message with research_tree data
|
| 149 |
messages[lastProgressIndex] = {
|
| 150 |
...messages[lastProgressIndex],
|
| 151 |
content: progressText,
|
| 152 |
progress: progress,
|
| 153 |
timestamp: new Date(),
|
| 154 |
+
research_tree: data.research_tree, // Update the research_tree in real-time
|
| 155 |
};
|
| 156 |
} else {
|
| 157 |
+
// Add new progress message with research_tree
|
| 158 |
messages.push({
|
| 159 |
id: uuidv4(),
|
| 160 |
content: progressText,
|
|
|
|
| 162 |
timestamp: new Date(),
|
| 163 |
progress: progress,
|
| 164 |
isProgress: true,
|
| 165 |
+
research_tree: data.research_tree, // Include the research_tree
|
| 166 |
+
media: {}, // Initialize empty media object
|
| 167 |
});
|
| 168 |
}
|
| 169 |
|
frontend/src/lib/types.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface Conversation {
|
|
| 42 |
export interface StatusUpdate {
|
| 43 |
message: string;
|
| 44 |
progress: number;
|
|
|
|
| 45 |
}
|
| 46 |
|
| 47 |
// Simplified research results based on actual server output
|
|
@@ -49,22 +50,22 @@ export interface ResearchResults {
|
|
| 49 |
topic: string;
|
| 50 |
timestamp: string;
|
| 51 |
// Optional fields not present in basic server response
|
| 52 |
-
content
|
| 53 |
-
media
|
| 54 |
-
images
|
| 55 |
-
videos
|
| 56 |
-
links
|
| 57 |
text: string;
|
| 58 |
url: string;
|
| 59 |
}>;
|
| 60 |
};
|
| 61 |
-
research_tree
|
| 62 |
}
|
| 63 |
|
| 64 |
export interface ResearchTree {
|
| 65 |
query: string;
|
| 66 |
depth: number;
|
| 67 |
-
sources: string
|
| 68 |
children: ResearchTree[];
|
| 69 |
}
|
| 70 |
|
|
|
|
| 42 |
export interface StatusUpdate {
|
| 43 |
message: string;
|
| 44 |
progress: number;
|
| 45 |
+
research_tree: ResearchTree;
|
| 46 |
}
|
| 47 |
|
| 48 |
// Simplified research results based on actual server output
|
|
|
|
| 50 |
topic: string;
|
| 51 |
timestamp: string;
|
| 52 |
// Optional fields not present in basic server response
|
| 53 |
+
content: string;
|
| 54 |
+
media: {
|
| 55 |
+
images: string[];
|
| 56 |
+
videos: string[];
|
| 57 |
+
links: Array<{
|
| 58 |
text: string;
|
| 59 |
url: string;
|
| 60 |
}>;
|
| 61 |
};
|
| 62 |
+
research_tree: ResearchTree;
|
| 63 |
}
|
| 64 |
|
| 65 |
export interface ResearchTree {
|
| 66 |
query: string;
|
| 67 |
depth: number;
|
| 68 |
+
sources: Record<string, string>; // it's like { "https://...": "Webpage text..." }
|
| 69 |
children: ResearchTree[];
|
| 70 |
}
|
| 71 |
|