Spaces:
Paused
Paused
Soham Waghmare
commited on
Commit
·
62283c0
1
Parent(s):
27a07a9
feat: better error and data handling
Browse files- backend/crawl_ai.py +2 -3
- backend/knet.py +43 -22
- backend/research_node.py +7 -2
- backend/scraper.py +76 -32
- frontend/bun.lock +70 -10
- frontend/package.json +2 -0
- frontend/src/components/ChatInterface.tsx +23 -7
- frontend/src/components/Message.tsx +128 -10
- frontend/src/components/ui/ChatLayout.tsx +25 -6
- frontend/src/components/ui/dialog.tsx +1 -0
- frontend/src/components/ui/sheet.tsx +5 -4
- frontend/src/lib/types.ts +9 -0
backend/crawl_ai.py
CHANGED
|
@@ -24,15 +24,14 @@ async def main(urls):
|
|
| 24 |
semaphore_count=3,
|
| 25 |
wait_for_images=True,
|
| 26 |
)
|
| 27 |
-
|
| 28 |
-
f.write("")
|
| 29 |
for result in results:
|
| 30 |
if result.success:
|
| 31 |
dump_result = {
|
| 32 |
"url": result.url,
|
| 33 |
"markdown": result.markdown,
|
| 34 |
}
|
| 35 |
-
with open("output.json", "a") as f:
|
| 36 |
json.dump(dump_result, f)
|
| 37 |
# Print the extracted content
|
| 38 |
hr = lambda n=1: print(("-" * 80) * 2 * n)
|
|
|
|
| 24 |
semaphore_count=3,
|
| 25 |
wait_for_images=True,
|
| 26 |
)
|
| 27 |
+
open("output.log.json", "w").close()
|
|
|
|
| 28 |
for result in results:
|
| 29 |
if result.success:
|
| 30 |
dump_result = {
|
| 31 |
"url": result.url,
|
| 32 |
"markdown": result.markdown,
|
| 33 |
}
|
| 34 |
+
with open("output.log.json", "a") as f:
|
| 35 |
json.dump(dump_result, f)
|
| 36 |
# Print the extracted content
|
| 37 |
hr = lambda n=1: print(("-" * 80) * 2 * n)
|
backend/knet.py
CHANGED
|
@@ -70,7 +70,7 @@ class Prompt:
|
|
| 70 |
Return as JSON array of objects with properties:
|
| 71 |
- query (string)""")
|
| 72 |
|
| 73 |
-
self.report_outline = dedent("""Generate a comprehensive report
|
| 74 |
User query:
|
| 75 |
{topic}
|
| 76 |
|
|
@@ -79,7 +79,8 @@ class Prompt:
|
|
| 79 |
|
| 80 |
The outline should include:
|
| 81 |
- Title
|
| 82 |
-
- List of h2 headings
|
|
|
|
| 83 |
|
| 84 |
self.report_fillin = dedent("""Fill in the content for the following report outline on the user query based on the following research findings:
|
| 85 |
User query:
|
|
@@ -88,11 +89,17 @@ class Prompt:
|
|
| 88 |
Findings:
|
| 89 |
{ctx_manager}
|
| 90 |
|
| 91 |
-
Report
|
|
|
|
|
|
|
|
|
|
| 92 |
{report_outline}
|
| 93 |
|
| 94 |
-
Current
|
| 95 |
-
{slot}
|
|
|
|
|
|
|
|
|
|
| 96 |
""")
|
| 97 |
|
| 98 |
|
|
@@ -186,7 +193,9 @@ class KNet:
|
|
| 186 |
try:
|
| 187 |
# Generate research plan
|
| 188 |
await self.progress.update(0, "Generating research plan...")
|
| 189 |
-
self.research_plan = self.generate_content(self.prompt.research_plan.format(topic=topic), schema=self.schema.research_plan)[
|
|
|
|
|
|
|
| 190 |
self.logger.info(f"Research plan:\n{json.dumps(self.research_plan, indent=2)}")
|
| 191 |
|
| 192 |
master_node = ResearchNode()
|
|
@@ -201,11 +210,12 @@ class KNet:
|
|
| 201 |
vertical=self.research_plan[self.idx_research_plan], research_plan="None", past_queries="None", ctx_manager="None", n=1
|
| 202 |
),
|
| 203 |
schema=self.schema.search_query,
|
|
|
|
| 204 |
)["branches"][0]
|
| 205 |
|
| 206 |
root_node = ResearchNode(query)
|
| 207 |
-
master_node.add_child(root_node)
|
| 208 |
-
to_explore = deque([(root_node,
|
| 209 |
explored_queries = set() # {string, string, ...}
|
| 210 |
|
| 211 |
await self.progress.update(100 / (len(self.research_plan) + 1), f"{self.research_plan[self.idx_research_plan]}")
|
|
@@ -234,7 +244,7 @@ class KNet:
|
|
| 234 |
|
| 235 |
# Generate final report
|
| 236 |
await self.progress.update(100 / (len(self.research_plan) + 1), "Generating final report...")
|
| 237 |
-
final_report = self._generate_final_report(master_node, topic)
|
| 238 |
|
| 239 |
self.logger.info(f"Research completed. Explored {len(explored_queries)} queries across {master_node.max_depth()} levels")
|
| 240 |
await self.progress.update(100, "Research complete!")
|
|
@@ -247,9 +257,9 @@ class KNet:
|
|
| 247 |
self.logger.error("Research failed", exc_info=True)
|
| 248 |
raise
|
| 249 |
|
| 250 |
-
def _generate_final_report(self, root_node: ResearchNode, topic: str, retry_count: int = 1) -> Dict[str, Any]:
|
| 251 |
try:
|
| 252 |
-
self.progress.setter(0, "Generating report...")
|
| 253 |
findings = "\n\n------\n\n".join(self.ctx_manager)
|
| 254 |
with open("ctx_manager.log.txt", "w", encoding="utf-8") as f:
|
| 255 |
f.write(findings)
|
|
@@ -258,21 +268,26 @@ class KNet:
|
|
| 258 |
outline = self.generate_content(self.prompt.report_outline.format(topic=topic, ctx_manager=findings), schema=self.schema.report_outline)
|
| 259 |
self.logger.info(f"Report outline:\n{json.dumps(outline, indent=2)}")
|
| 260 |
report = []
|
|
|
|
| 261 |
# Fill in report outline
|
| 262 |
for i, heading in enumerate(outline["headings"]):
|
| 263 |
-
self.progress.update(100 / (len(outline["headings"]) + 1), "Generating report...")
|
| 264 |
content = self.generate_content(
|
| 265 |
self.prompt.report_fillin.format(
|
| 266 |
topic=topic,
|
| 267 |
ctx_manager=findings,
|
|
|
|
| 268 |
report_outline=["[done] " + outline["title"]] + [f"[done] {h}" for _, h in enumerate(outline["headings"]) if i < _],
|
| 269 |
slot=heading,
|
| 270 |
),
|
| 271 |
schema=self.schema.report_fillin,
|
| 272 |
)["content"]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
report.append({"heading": heading, "content": content})
|
| 274 |
-
|
| 275 |
-
raster_report = f"# {outline['title']}\n\n" + "\n\n".join([f"## {r['heading']}\n\n{r['content']}" for r in report])
|
| 276 |
|
| 277 |
# Collate multimedia content
|
| 278 |
media_content = {"images": [], "videos": [], "links": [], "references": []}
|
|
@@ -317,9 +332,11 @@ class KNet:
|
|
| 317 |
}
|
| 318 |
|
| 319 |
except Exception as e:
|
| 320 |
-
if e in ["GEMINI_RECITATION", "NO_RESPONSE"]
|
| 321 |
-
self.logger.error(
|
| 322 |
-
|
|
|
|
|
|
|
| 323 |
self.logger.error("Error generating final report", exc_info=True)
|
| 324 |
raise
|
| 325 |
|
|
@@ -351,8 +368,10 @@ class KNet:
|
|
| 351 |
return new_nodes
|
| 352 |
|
| 353 |
except Exception as e:
|
| 354 |
-
if e in ["GEMINI_RECITATION", "NO_RESPONSE"]
|
| 355 |
-
self.logger.error(
|
|
|
|
|
|
|
| 356 |
return self._gen_queries(node, topic, retry_count + 1)
|
| 357 |
self.logger.error("_gen_queries failed", exc_info=True)
|
| 358 |
raise
|
|
@@ -381,13 +400,15 @@ class KNet:
|
|
| 381 |
return response["decision"]
|
| 382 |
|
| 383 |
except Exception as e:
|
| 384 |
-
if e in ["GEMINI_RECITATION", "NO_RESPONSE"]
|
| 385 |
-
self.logger.error(
|
|
|
|
|
|
|
| 386 |
return self._should_continue_branch(node, topic, retry_count + 1)
|
| 387 |
self.logger.error("Branch decision failed:", exc_info=True)
|
| 388 |
raise
|
| 389 |
|
| 390 |
-
def generate_content(self, prompt: str, schema: Dict[str, Any] = {}, temp: float =
|
| 391 |
safe = [
|
| 392 |
types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold=types.HarmBlockThreshold.BLOCK_NONE),
|
| 393 |
types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_HARASSMENT, threshold=types.HarmBlockThreshold.BLOCK_NONE),
|
|
|
|
| 70 |
Return as JSON array of objects with properties:
|
| 71 |
- query (string)""")
|
| 72 |
|
| 73 |
+
self.report_outline = dedent("""Generate a comprehensive outline for a report on the user query based on the findings:
|
| 74 |
User query:
|
| 75 |
{topic}
|
| 76 |
|
|
|
|
| 79 |
|
| 80 |
The outline should include:
|
| 81 |
- Title
|
| 82 |
+
- List of h2 headings
|
| 83 |
+
Do not include hashtags""")
|
| 84 |
|
| 85 |
self.report_fillin = dedent("""Fill in the content for the following report outline on the user query based on the following research findings:
|
| 86 |
User query:
|
|
|
|
| 89 |
Findings:
|
| 90 |
{ctx_manager}
|
| 91 |
|
| 92 |
+
Report generated os far:
|
| 93 |
+
{report_progress}
|
| 94 |
+
|
| 95 |
+
The outline:
|
| 96 |
{report_outline}
|
| 97 |
|
| 98 |
+
Current heading to fill in:
|
| 99 |
+
## {slot}
|
| 100 |
+
|
| 101 |
+
The content should be comprehensive, detailed and well-structured, providing detailed information on the topic. Use tables, lists, and other formatting as needed to enhance readability.
|
| 102 |
+
Do not include the heading in the content.
|
| 103 |
""")
|
| 104 |
|
| 105 |
|
|
|
|
| 193 |
try:
|
| 194 |
# Generate research plan
|
| 195 |
await self.progress.update(0, "Generating research plan...")
|
| 196 |
+
self.research_plan = self.generate_content(self.prompt.research_plan.format(topic=topic), schema=self.schema.research_plan, temp=1.5)[
|
| 197 |
+
"steps"
|
| 198 |
+
]
|
| 199 |
self.logger.info(f"Research plan:\n{json.dumps(self.research_plan, indent=2)}")
|
| 200 |
|
| 201 |
master_node = ResearchNode()
|
|
|
|
| 210 |
vertical=self.research_plan[self.idx_research_plan], research_plan="None", past_queries="None", ctx_manager="None", n=1
|
| 211 |
),
|
| 212 |
schema=self.schema.search_query,
|
| 213 |
+
temp=1.5,
|
| 214 |
)["branches"][0]
|
| 215 |
|
| 216 |
root_node = ResearchNode(query)
|
| 217 |
+
master_node.add_child(root_node.query, node=root_node)
|
| 218 |
+
to_explore = deque([(root_node, 1)]) # (node, depth) pairs
|
| 219 |
explored_queries = set() # {string, string, ...}
|
| 220 |
|
| 221 |
await self.progress.update(100 / (len(self.research_plan) + 1), f"{self.research_plan[self.idx_research_plan]}")
|
|
|
|
| 244 |
|
| 245 |
# Generate final report
|
| 246 |
await self.progress.update(100 / (len(self.research_plan) + 1), "Generating final report...")
|
| 247 |
+
final_report = await self._generate_final_report(master_node, topic)
|
| 248 |
|
| 249 |
self.logger.info(f"Research completed. Explored {len(explored_queries)} queries across {master_node.max_depth()} levels")
|
| 250 |
await self.progress.update(100, "Research complete!")
|
|
|
|
| 257 |
self.logger.error("Research failed", exc_info=True)
|
| 258 |
raise
|
| 259 |
|
| 260 |
+
async def _generate_final_report(self, root_node: ResearchNode, topic: str, retry_count: int = 1) -> Dict[str, Any]:
|
| 261 |
try:
|
| 262 |
+
await self.progress.setter(0, "Generating report...")
|
| 263 |
findings = "\n\n------\n\n".join(self.ctx_manager)
|
| 264 |
with open("ctx_manager.log.txt", "w", encoding="utf-8") as f:
|
| 265 |
f.write(findings)
|
|
|
|
| 268 |
outline = self.generate_content(self.prompt.report_outline.format(topic=topic, ctx_manager=findings), schema=self.schema.report_outline)
|
| 269 |
self.logger.info(f"Report outline:\n{json.dumps(outline, indent=2)}")
|
| 270 |
report = []
|
| 271 |
+
raster_report = f"# {outline['title']}\n\n"
|
| 272 |
# Fill in report outline
|
| 273 |
for i, heading in enumerate(outline["headings"]):
|
| 274 |
+
await self.progress.update(100 / (len(outline["headings"]) + 1), "Generating report...")
|
| 275 |
content = self.generate_content(
|
| 276 |
self.prompt.report_fillin.format(
|
| 277 |
topic=topic,
|
| 278 |
ctx_manager=findings,
|
| 279 |
+
report_progress=raster_report,
|
| 280 |
report_outline=["[done] " + outline["title"]] + [f"[done] {h}" for _, h in enumerate(outline["headings"]) if i < _],
|
| 281 |
slot=heading,
|
| 282 |
),
|
| 283 |
schema=self.schema.report_fillin,
|
| 284 |
)["content"]
|
| 285 |
+
# Remove heading if LLM put it there regardless
|
| 286 |
+
idx_heading = content.find(heading)
|
| 287 |
+
if idx_heading != -1:
|
| 288 |
+
content = content[idx_heading + len(heading) :].strip()
|
| 289 |
report.append({"heading": heading, "content": content})
|
| 290 |
+
raster_report += f"\n\n## {heading}\n\n{content}"
|
|
|
|
| 291 |
|
| 292 |
# Collate multimedia content
|
| 293 |
media_content = {"images": [], "videos": [], "links": [], "references": []}
|
|
|
|
| 332 |
}
|
| 333 |
|
| 334 |
except Exception as e:
|
| 335 |
+
if e in ["GEMINI_RECITATION", "NO_RESPONSE"]:
|
| 336 |
+
self.logger.error("GEMINI_RECITATION or NO_RESPONSE")
|
| 337 |
+
if retry_count < 3:
|
| 338 |
+
self.logger.error(f"Retrying final report:C:{retry_count} / 3", exc_info=True)
|
| 339 |
+
return await self._generate_final_report(root_node, retry_count + 1)
|
| 340 |
self.logger.error("Error generating final report", exc_info=True)
|
| 341 |
raise
|
| 342 |
|
|
|
|
| 368 |
return new_nodes
|
| 369 |
|
| 370 |
except Exception as e:
|
| 371 |
+
if e in ["GEMINI_RECITATION", "NO_RESPONSE"]:
|
| 372 |
+
self.logger.error("GEMINI_RECITATION or NO_RESPONSE")
|
| 373 |
+
if retry_count < 3:
|
| 374 |
+
self.logger.error(f"Retrying _gen_queries | C:{retry_count} / 3", exc_info=True)
|
| 375 |
return self._gen_queries(node, topic, retry_count + 1)
|
| 376 |
self.logger.error("_gen_queries failed", exc_info=True)
|
| 377 |
raise
|
|
|
|
| 400 |
return response["decision"]
|
| 401 |
|
| 402 |
except Exception as e:
|
| 403 |
+
if e in ["GEMINI_RECITATION", "NO_RESPONSE"]:
|
| 404 |
+
self.logger.error("GEMINI_RECITATION or NO_RESPONSE")
|
| 405 |
+
if retry_count < 3:
|
| 406 |
+
self.logger.error(f"Retrying branch decision:C:{retry_count} / 3", exc_info=True)
|
| 407 |
return self._should_continue_branch(node, topic, retry_count + 1)
|
| 408 |
self.logger.error("Branch decision failed:", exc_info=True)
|
| 409 |
raise
|
| 410 |
|
| 411 |
+
def generate_content(self, prompt: str, schema: Dict[str, Any] = {}, temp: float = 1, _retry_count: int = 1) -> Dict[str, Any] | str:
|
| 412 |
safe = [
|
| 413 |
types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold=types.HarmBlockThreshold.BLOCK_NONE),
|
| 414 |
types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_HARASSMENT, threshold=types.HarmBlockThreshold.BLOCK_NONE),
|
backend/research_node.py
CHANGED
|
@@ -10,8 +10,13 @@ class ResearchNode:
|
|
| 10 |
self.children: List[ResearchNode] = []
|
| 11 |
self.data: List[Dict[str, Any]] = []
|
| 12 |
|
| 13 |
-
def add_child(self, query: str) -> "ResearchNode":
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
self.children.append(child)
|
| 16 |
return child
|
| 17 |
|
|
|
|
| 10 |
self.children: List[ResearchNode] = []
|
| 11 |
self.data: List[Dict[str, Any]] = []
|
| 12 |
|
| 13 |
+
def add_child(self, query: str, node: Optional["ResearchNode"] = None) -> "ResearchNode":
|
| 14 |
+
if node:
|
| 15 |
+
child = node
|
| 16 |
+
child.parent = self
|
| 17 |
+
child.depth = self.depth + 1
|
| 18 |
+
else:
|
| 19 |
+
child = ResearchNode(query, parent=self, depth=self.depth + 1)
|
| 20 |
self.children.append(child)
|
| 21 |
return child
|
| 22 |
|
backend/scraper.py
CHANGED
|
@@ -163,7 +163,7 @@ class CrawlForAIScraper:
|
|
| 163 |
headless=True,
|
| 164 |
viewport_width=1920,
|
| 165 |
viewport_height=1080,
|
| 166 |
-
accept_downloads=
|
| 167 |
verbose=False,
|
| 168 |
)
|
| 169 |
self.crawler = AsyncWebCrawler(config=self.base_browser)
|
|
@@ -185,19 +185,25 @@ class CrawlForAIScraper:
|
|
| 185 |
self.logger.info(f"Querying: {query}")
|
| 186 |
|
| 187 |
# Perform a search to get a list of webpages
|
| 188 |
-
search_results = await self._search(query
|
| 189 |
|
| 190 |
# Scrape each webpage
|
| 191 |
scraped_data = []
|
| 192 |
-
self.logger.info(f"Scraping {
|
| 193 |
-
data = await self._scrape_pages(search_results)
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
self.logger.info(f"Completed scraping {len(scraped_data)} sites")
|
| 198 |
return scraped_data
|
| 199 |
|
| 200 |
-
async def _search(self, query: str
|
| 201 |
try:
|
| 202 |
encoded_query = quote_plus(query)
|
| 203 |
search_uri = f"https://www.google.com/search?q={encoded_query}"
|
|
@@ -210,26 +216,28 @@ class CrawlForAIScraper:
|
|
| 210 |
scan_full_page=True,
|
| 211 |
)
|
| 212 |
|
| 213 |
-
soup = BeautifulSoup(result.
|
| 214 |
search_results = []
|
| 215 |
|
| 216 |
for link in list(soup.select("div > span > a"))[2:]:
|
| 217 |
url = link.get("href").replace(" ", "").replace("\n", "").strip()
|
| 218 |
if not url.startswith(("http://", "https://")):
|
| 219 |
url = "https://" + url
|
|
|
|
|
|
|
| 220 |
search_results.append(url)
|
| 221 |
|
| 222 |
self.logger.info(f"Found {len(search_results)} results")
|
| 223 |
-
return search_results
|
| 224 |
|
| 225 |
except requests.exceptions.RequestException as e:
|
| 226 |
-
self.logger.error(f"Google search error: {str(e)}")
|
| 227 |
-
|
| 228 |
except Exception as e:
|
| 229 |
-
self.logger.error(f"Google search error: {str(e)}")
|
| 230 |
-
|
| 231 |
|
| 232 |
-
async def _scrape_pages(self, urls: str) -> Dict[str, Any]:
|
| 233 |
await self.start()
|
| 234 |
|
| 235 |
try:
|
|
@@ -241,22 +249,47 @@ class CrawlForAIScraper:
|
|
| 241 |
scan_full_page=True,
|
| 242 |
semaphore_count=4,
|
| 243 |
wait_for_images=True,
|
|
|
|
|
|
|
|
|
|
| 244 |
page_timeout=25000,
|
| 245 |
)
|
| 246 |
scraped_sites = []
|
| 247 |
for result in results:
|
| 248 |
if result.success:
|
| 249 |
soup = BeautifulSoup(result.html, "html.parser")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
data = {
|
| 251 |
"url": result.url,
|
| 252 |
"text": result.markdown,
|
| 253 |
-
"images":
|
| 254 |
-
"videos":
|
| 255 |
-
"links": result.links["external"],
|
| 256 |
}
|
| 257 |
scraped_sites.append(data)
|
| 258 |
self.logger.info(f" - {result.url[:80]}...")
|
| 259 |
-
return scraped_sites
|
| 260 |
|
| 261 |
except Exception as e:
|
| 262 |
self.logger.error(f"Scraping error while {urls}: {str(e)}")
|
|
@@ -293,20 +326,31 @@ class CrawlForAIScraper:
|
|
| 293 |
videos = []
|
| 294 |
nodes = list(soup.find_all("iframe")) + list(soup.find_all("video")) + list(soup.find_all("a"))
|
| 295 |
for node in nodes:
|
| 296 |
-
if
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
videos.append(href)
|
| 308 |
return videos
|
| 309 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
|
| 311 |
if __name__ == "__main__":
|
| 312 |
import sys
|
|
@@ -325,9 +369,9 @@ if __name__ == "__main__":
|
|
| 325 |
async def main():
|
| 326 |
scraper = CrawlForAIScraper()
|
| 327 |
await scraper.start()
|
| 328 |
-
data = await scraper.search_and_scrape("
|
| 329 |
await scraper.close()
|
| 330 |
-
with open("output.json", "w") as f:
|
| 331 |
f.write(json.dumps(data, indent=2))
|
| 332 |
print(json.dumps(data, indent=2))
|
| 333 |
|
|
|
|
| 163 |
headless=True,
|
| 164 |
viewport_width=1920,
|
| 165 |
viewport_height=1080,
|
| 166 |
+
accept_downloads=False,
|
| 167 |
verbose=False,
|
| 168 |
)
|
| 169 |
self.crawler = AsyncWebCrawler(config=self.base_browser)
|
|
|
|
| 185 |
self.logger.info(f"Querying: {query}")
|
| 186 |
|
| 187 |
# Perform a search to get a list of webpages
|
| 188 |
+
search_results = await self._search(query)
|
| 189 |
|
| 190 |
# Scrape each webpage
|
| 191 |
scraped_data = []
|
| 192 |
+
self.logger.info(f"Scraping {num_sites} sites...")
|
| 193 |
+
data = await self._scrape_pages(search_results[: num_sites + 2], num_sites)
|
| 194 |
+
scraped_data.extend(data)
|
| 195 |
+
|
| 196 |
+
# Scrape next pages when some failed
|
| 197 |
+
for _ in range(3):
|
| 198 |
+
if len(scraped_data) < num_sites:
|
| 199 |
+
idx_last_page = search_results.index(search_results[-1])
|
| 200 |
+
data = await self._scrape_pages(search_results[idx_last_page + 1 : num_sites + 2], num_sites)
|
| 201 |
+
scraped_data.extend(data)
|
| 202 |
|
| 203 |
self.logger.info(f"Completed scraping {len(scraped_data)} sites")
|
| 204 |
return scraped_data
|
| 205 |
|
| 206 |
+
async def _search(self, query: str) -> List[str]:
|
| 207 |
try:
|
| 208 |
encoded_query = quote_plus(query)
|
| 209 |
search_uri = f"https://www.google.com/search?q={encoded_query}"
|
|
|
|
| 216 |
scan_full_page=True,
|
| 217 |
)
|
| 218 |
|
| 219 |
+
soup = BeautifulSoup(result.cleaned_html, "html.parser")
|
| 220 |
search_results = []
|
| 221 |
|
| 222 |
for link in list(soup.select("div > span > a"))[2:]:
|
| 223 |
url = link.get("href").replace(" ", "").replace("\n", "").strip()
|
| 224 |
if not url.startswith(("http://", "https://")):
|
| 225 |
url = "https://" + url
|
| 226 |
+
if "support.google.com" in url or url.startswith("/search?q="):
|
| 227 |
+
continue
|
| 228 |
search_results.append(url)
|
| 229 |
|
| 230 |
self.logger.info(f"Found {len(search_results)} results")
|
| 231 |
+
return search_results
|
| 232 |
|
| 233 |
except requests.exceptions.RequestException as e:
|
| 234 |
+
self.logger.error(f"Google search error: {str(e)}", exc_info=True)
|
| 235 |
+
raise
|
| 236 |
except Exception as e:
|
| 237 |
+
self.logger.error(f"Google search error: {str(e)}", exc_info=True)
|
| 238 |
+
raise
|
| 239 |
|
| 240 |
+
async def _scrape_pages(self, urls: str, max_sites: int) -> Dict[str, Any]:
|
| 241 |
await self.start()
|
| 242 |
|
| 243 |
try:
|
|
|
|
| 249 |
scan_full_page=True,
|
| 250 |
semaphore_count=4,
|
| 251 |
wait_for_images=True,
|
| 252 |
+
scroll_delay=0.1,
|
| 253 |
+
delay_before_return_html=2,
|
| 254 |
+
exclude_external_images=True,
|
| 255 |
page_timeout=25000,
|
| 256 |
)
|
| 257 |
scraped_sites = []
|
| 258 |
for result in results:
|
| 259 |
if result.success:
|
| 260 |
soup = BeautifulSoup(result.html, "html.parser")
|
| 261 |
+
|
| 262 |
+
# Combine images
|
| 263 |
+
extracted_images = self._extract_images(soup, result.url)
|
| 264 |
+
media_images = []
|
| 265 |
+
for img in result.media["images"]:
|
| 266 |
+
if img["width"] is None or (isinstance(img["width"], (int, float)) and img["width"] > 300):
|
| 267 |
+
# Resolve multiple URLs in the src attribute
|
| 268 |
+
src = img["src"]
|
| 269 |
+
if " " in src and "w," in src:
|
| 270 |
+
urls = [url.strip() for url in src.split(" ") if url.strip()]
|
| 271 |
+
if urls:
|
| 272 |
+
last_url = urls[-1].split(" ")[0]
|
| 273 |
+
media_images.append(last_url)
|
| 274 |
+
else:
|
| 275 |
+
media_images.append(src)
|
| 276 |
+
all_images = list(set(extracted_images + media_images))
|
| 277 |
+
|
| 278 |
+
# Combine videos
|
| 279 |
+
all_videos = self._extract_videos(soup)
|
| 280 |
+
media_videos = [v["src"] for v in result.media["videos"] if v["src"]]
|
| 281 |
+
all_videos = list(set(all_videos + media_videos))
|
| 282 |
+
|
| 283 |
data = {
|
| 284 |
"url": result.url,
|
| 285 |
"text": result.markdown,
|
| 286 |
+
"images": all_images,
|
| 287 |
+
"videos": all_videos,
|
| 288 |
+
"links": self._extract_links(result.links["external"]),
|
| 289 |
}
|
| 290 |
scraped_sites.append(data)
|
| 291 |
self.logger.info(f" - {result.url[:80]}...")
|
| 292 |
+
return scraped_sites[: max_sites]
|
| 293 |
|
| 294 |
except Exception as e:
|
| 295 |
self.logger.error(f"Scraping error while {urls}: {str(e)}")
|
|
|
|
| 326 |
videos = []
|
| 327 |
nodes = list(soup.find_all("iframe")) + list(soup.find_all("video")) + list(soup.find_all("a"))
|
| 328 |
for node in nodes:
|
| 329 |
+
if not any(
|
| 330 |
+
keyword in node.get("src", "") or keyword in node.get("href", "")
|
| 331 |
+
for keyword in ["accounts.google.com", "blob:", "youtube.com/redirect"]
|
| 332 |
+
):
|
| 333 |
+
continue
|
| 334 |
+
elif (
|
| 335 |
+
any(node.name in tag for tag in ["video", "iframe", "a"])
|
| 336 |
+
and "www.youtube.com/watch?v" in node.get("src", "")
|
| 337 |
+
or "www.youtube.com/watch?v" in node.get("href", "")
|
| 338 |
+
):
|
| 339 |
+
videos.append(node.get("src", ""))
|
|
|
|
| 340 |
return videos
|
| 341 |
|
| 342 |
+
def _extract_links(self, links: list) -> List[str]:
|
| 343 |
+
# Filter out unwanted links
|
| 344 |
+
filtered_links = []
|
| 345 |
+
for link in links:
|
| 346 |
+
url = link.get("href")
|
| 347 |
+
if url.startswith(("http://", "https://")) and not any(
|
| 348 |
+
keyword in url
|
| 349 |
+
for keyword in ["support.google.com", "google.com", "accounts.google.com", "youtube.com", "blob:", "mailto:", "javascript:"]
|
| 350 |
+
):
|
| 351 |
+
filtered_links.append(link)
|
| 352 |
+
return filtered_links
|
| 353 |
+
|
| 354 |
|
| 355 |
if __name__ == "__main__":
|
| 356 |
import sys
|
|
|
|
| 369 |
async def main():
|
| 370 |
scraper = CrawlForAIScraper()
|
| 371 |
await scraper.start()
|
| 372 |
+
data = await scraper.search_and_scrape("blender.org")
|
| 373 |
await scraper.close()
|
| 374 |
+
with open("output.log.json", "w") as f:
|
| 375 |
f.write(json.dumps(data, indent=2))
|
| 376 |
print(json.dumps(data, indent=2))
|
| 377 |
|
frontend/bun.lock
CHANGED
|
@@ -15,6 +15,7 @@
|
|
| 15 |
"@radix-ui/react-slot": "^1.1.2",
|
| 16 |
"@radix-ui/react-tabs": "^1.1.3",
|
| 17 |
"@radix-ui/react-tooltip": "^1.1.8",
|
|
|
|
| 18 |
"class-variance-authority": "^0.7.1",
|
| 19 |
"clsx": "^2.1.1",
|
| 20 |
"eslint-config-next": "^15.2.4",
|
|
@@ -25,6 +26,7 @@
|
|
| 25 |
"react-dom": "^19.1.0",
|
| 26 |
"react-markdown": "^10.1.0",
|
| 27 |
"react-resizable-panels": "^2.1.7",
|
|
|
|
| 28 |
"remark-gfm": "^4.0.1",
|
| 29 |
"shadcn": "^2.4.0-canary.19",
|
| 30 |
"socket.io-client": "^4.8.1",
|
|
@@ -92,6 +94,8 @@
|
|
| 92 |
|
| 93 |
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.26.8", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw=="],
|
| 94 |
|
|
|
|
|
|
|
| 95 |
"@babel/template": ["@babel/template@7.26.9", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.26.9", "@babel/types": "^7.26.9" } }, "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA=="],
|
| 96 |
|
| 97 |
"@babel/traverse": ["@babel/traverse@7.26.10", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.10", "@babel/parser": "^7.26.10", "@babel/template": "^7.26.9", "@babel/types": "^7.26.10", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A=="],
|
|
@@ -356,6 +360,8 @@
|
|
| 356 |
|
| 357 |
"@types/react-dom": ["@types/react-dom@18.3.5", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q=="],
|
| 358 |
|
|
|
|
|
|
|
| 359 |
"@types/statuses": ["@types/statuses@2.0.5", "", {}, "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A=="],
|
| 360 |
|
| 361 |
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
|
|
@@ -502,13 +508,13 @@
|
|
| 502 |
|
| 503 |
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
| 504 |
|
| 505 |
-
"character-entities": ["character-entities@2.
|
| 506 |
|
| 507 |
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
| 508 |
|
| 509 |
-
"character-entities-legacy": ["character-entities-legacy@
|
| 510 |
|
| 511 |
-
"character-reference-invalid": ["character-reference-invalid@
|
| 512 |
|
| 513 |
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
| 514 |
|
|
@@ -680,6 +686,8 @@
|
|
| 680 |
|
| 681 |
"fastq": ["fastq@1.19.0", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA=="],
|
| 682 |
|
|
|
|
|
|
|
| 683 |
"fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="],
|
| 684 |
|
| 685 |
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
|
@@ -698,6 +706,8 @@
|
|
| 698 |
|
| 699 |
"foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="],
|
| 700 |
|
|
|
|
|
|
|
| 701 |
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
| 702 |
|
| 703 |
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
|
|
@@ -758,12 +768,20 @@
|
|
| 758 |
|
| 759 |
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
| 760 |
|
|
|
|
|
|
|
| 761 |
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
|
| 762 |
|
| 763 |
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
| 764 |
|
|
|
|
|
|
|
| 765 |
"headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="],
|
| 766 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 767 |
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
| 768 |
|
| 769 |
"https-proxy-agent": ["https-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^7.0.2", "debug": "4" } }, "sha512-ONsE3+yfZF2caH5+bJlcddtWqNI3Gvs5A38+ngvljxaBiRXRswym2c7yf8UAeFpRFKjFNHIFEHqR/OLAWJzyiA=="],
|
|
@@ -784,9 +802,9 @@
|
|
| 784 |
|
| 785 |
"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=="],
|
| 786 |
|
| 787 |
-
"is-alphabetical": ["is-alphabetical@
|
| 788 |
|
| 789 |
-
"is-alphanumerical": ["is-alphanumerical@
|
| 790 |
|
| 791 |
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
| 792 |
|
|
@@ -810,7 +828,7 @@
|
|
| 810 |
|
| 811 |
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
| 812 |
|
| 813 |
-
"is-decimal": ["is-decimal@
|
| 814 |
|
| 815 |
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
| 816 |
|
|
@@ -822,7 +840,7 @@
|
|
| 822 |
|
| 823 |
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
| 824 |
|
| 825 |
-
"is-hexadecimal": ["is-hexadecimal@
|
| 826 |
|
| 827 |
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
|
| 828 |
|
|
@@ -916,6 +934,8 @@
|
|
| 916 |
|
| 917 |
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
| 918 |
|
|
|
|
|
|
|
| 919 |
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
| 920 |
|
| 921 |
"lucide-react": ["lucide-react@0.479.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ=="],
|
|
@@ -1088,7 +1108,7 @@
|
|
| 1088 |
|
| 1089 |
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
| 1090 |
|
| 1091 |
-
"parse-entities": ["parse-entities@
|
| 1092 |
|
| 1093 |
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
| 1094 |
|
|
@@ -1132,6 +1152,8 @@
|
|
| 1132 |
|
| 1133 |
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
| 1134 |
|
|
|
|
|
|
|
| 1135 |
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
|
| 1136 |
|
| 1137 |
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
|
@@ -1162,6 +1184,8 @@
|
|
| 1162 |
|
| 1163 |
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
| 1164 |
|
|
|
|
|
|
|
| 1165 |
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
| 1166 |
|
| 1167 |
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
|
@@ -1172,6 +1196,10 @@
|
|
| 1172 |
|
| 1173 |
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
| 1174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1175 |
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
| 1176 |
|
| 1177 |
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
|
|
@@ -1410,6 +1438,8 @@
|
|
| 1410 |
|
| 1411 |
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
|
| 1412 |
|
|
|
|
|
|
|
| 1413 |
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
| 1414 |
|
| 1415 |
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
|
@@ -1466,6 +1496,8 @@
|
|
| 1466 |
|
| 1467 |
"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=="],
|
| 1468 |
|
|
|
|
|
|
|
| 1469 |
"eslint/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
| 1470 |
|
| 1471 |
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
|
@@ -1486,24 +1518,34 @@
|
|
| 1486 |
|
| 1487 |
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
| 1488 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1489 |
"is-bun-module/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
| 1490 |
|
| 1491 |
"log-symbols/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
| 1492 |
|
| 1493 |
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
| 1494 |
|
|
|
|
|
|
|
| 1495 |
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
| 1496 |
|
| 1497 |
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
| 1498 |
|
| 1499 |
"ora/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
| 1500 |
|
| 1501 |
-
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
| 1502 |
-
|
| 1503 |
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
| 1504 |
|
| 1505 |
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
| 1506 |
|
|
|
|
|
|
|
| 1507 |
"restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
| 1508 |
|
| 1509 |
"sharp/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
|
@@ -1518,6 +1560,8 @@
|
|
| 1518 |
|
| 1519 |
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
| 1520 |
|
|
|
|
|
|
|
| 1521 |
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
| 1522 |
|
| 1523 |
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
|
@@ -1546,6 +1590,20 @@
|
|
| 1546 |
|
| 1547 |
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
| 1548 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1549 |
"restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
| 1550 |
|
| 1551 |
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
|
@@ -1555,5 +1613,7 @@
|
|
| 1555 |
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
| 1556 |
|
| 1557 |
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
|
|
|
|
|
|
| 1558 |
}
|
| 1559 |
}
|
|
|
|
| 15 |
"@radix-ui/react-slot": "^1.1.2",
|
| 16 |
"@radix-ui/react-tabs": "^1.1.3",
|
| 17 |
"@radix-ui/react-tooltip": "^1.1.8",
|
| 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",
|
|
|
|
| 26 |
"react-dom": "^19.1.0",
|
| 27 |
"react-markdown": "^10.1.0",
|
| 28 |
"react-resizable-panels": "^2.1.7",
|
| 29 |
+
"react-syntax-highlighter": "^15.6.1",
|
| 30 |
"remark-gfm": "^4.0.1",
|
| 31 |
"shadcn": "^2.4.0-canary.19",
|
| 32 |
"socket.io-client": "^4.8.1",
|
|
|
|
| 94 |
|
| 95 |
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.26.8", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw=="],
|
| 96 |
|
| 97 |
+
"@babel/runtime": ["@babel/runtime@7.27.0", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw=="],
|
| 98 |
+
|
| 99 |
"@babel/template": ["@babel/template@7.26.9", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.26.9", "@babel/types": "^7.26.9" } }, "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA=="],
|
| 100 |
|
| 101 |
"@babel/traverse": ["@babel/traverse@7.26.10", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.10", "@babel/parser": "^7.26.10", "@babel/template": "^7.26.9", "@babel/types": "^7.26.10", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A=="],
|
|
|
|
| 360 |
|
| 361 |
"@types/react-dom": ["@types/react-dom@18.3.5", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q=="],
|
| 362 |
|
| 363 |
+
"@types/react-syntax-highlighter": ["@types/react-syntax-highlighter@15.5.13", "", { "dependencies": { "@types/react": "*" } }, "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA=="],
|
| 364 |
+
|
| 365 |
"@types/statuses": ["@types/statuses@2.0.5", "", {}, "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A=="],
|
| 366 |
|
| 367 |
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
|
|
|
|
| 508 |
|
| 509 |
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
| 510 |
|
| 511 |
+
"character-entities": ["character-entities@1.2.4", "", {}, "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="],
|
| 512 |
|
| 513 |
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
| 514 |
|
| 515 |
+
"character-entities-legacy": ["character-entities-legacy@1.1.4", "", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="],
|
| 516 |
|
| 517 |
+
"character-reference-invalid": ["character-reference-invalid@1.1.4", "", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="],
|
| 518 |
|
| 519 |
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
| 520 |
|
|
|
|
| 686 |
|
| 687 |
"fastq": ["fastq@1.19.0", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA=="],
|
| 688 |
|
| 689 |
+
"fault": ["fault@1.0.4", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA=="],
|
| 690 |
+
|
| 691 |
"fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="],
|
| 692 |
|
| 693 |
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
|
|
|
| 706 |
|
| 707 |
"foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="],
|
| 708 |
|
| 709 |
+
"format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="],
|
| 710 |
+
|
| 711 |
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
| 712 |
|
| 713 |
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
|
|
|
|
| 768 |
|
| 769 |
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
| 770 |
|
| 771 |
+
"hast-util-parse-selector": ["hast-util-parse-selector@2.2.5", "", {}, "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="],
|
| 772 |
+
|
| 773 |
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
|
| 774 |
|
| 775 |
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
| 776 |
|
| 777 |
+
"hastscript": ["hastscript@6.0.0", "", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="],
|
| 778 |
+
|
| 779 |
"headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="],
|
| 780 |
|
| 781 |
+
"highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
|
| 782 |
+
|
| 783 |
+
"highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="],
|
| 784 |
+
|
| 785 |
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
| 786 |
|
| 787 |
"https-proxy-agent": ["https-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^7.0.2", "debug": "4" } }, "sha512-ONsE3+yfZF2caH5+bJlcddtWqNI3Gvs5A38+ngvljxaBiRXRswym2c7yf8UAeFpRFKjFNHIFEHqR/OLAWJzyiA=="],
|
|
|
|
| 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=="],
|
| 808 |
|
| 809 |
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
| 810 |
|
|
|
|
| 828 |
|
| 829 |
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
| 830 |
|
| 831 |
+
"is-decimal": ["is-decimal@1.0.4", "", {}, "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="],
|
| 832 |
|
| 833 |
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
| 834 |
|
|
|
|
| 840 |
|
| 841 |
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
| 842 |
|
| 843 |
+
"is-hexadecimal": ["is-hexadecimal@1.0.4", "", {}, "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="],
|
| 844 |
|
| 845 |
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
|
| 846 |
|
|
|
|
| 934 |
|
| 935 |
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
| 936 |
|
| 937 |
+
"lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="],
|
| 938 |
+
|
| 939 |
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
| 940 |
|
| 941 |
"lucide-react": ["lucide-react@0.479.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ=="],
|
|
|
|
| 1108 |
|
| 1109 |
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
| 1110 |
|
| 1111 |
+
"parse-entities": ["parse-entities@2.0.0", "", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="],
|
| 1112 |
|
| 1113 |
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
| 1114 |
|
|
|
|
| 1152 |
|
| 1153 |
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
| 1154 |
|
| 1155 |
+
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
| 1156 |
+
|
| 1157 |
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
|
| 1158 |
|
| 1159 |
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
|
|
|
| 1184 |
|
| 1185 |
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
| 1186 |
|
| 1187 |
+
"react-syntax-highlighter": ["react-syntax-highlighter@15.6.1", "", { "dependencies": { "@babel/runtime": "^7.3.1", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.27.0", "refractor": "^3.6.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg=="],
|
| 1188 |
+
|
| 1189 |
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
| 1190 |
|
| 1191 |
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
|
|
|
| 1196 |
|
| 1197 |
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
| 1198 |
|
| 1199 |
+
"refractor": ["refractor@3.6.0", "", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="],
|
| 1200 |
+
|
| 1201 |
+
"regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="],
|
| 1202 |
+
|
| 1203 |
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
| 1204 |
|
| 1205 |
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
|
|
|
|
| 1438 |
|
| 1439 |
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
|
| 1440 |
|
| 1441 |
+
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
| 1442 |
+
|
| 1443 |
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
| 1444 |
|
| 1445 |
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
|
|
|
| 1496 |
|
| 1497 |
"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=="],
|
| 1498 |
|
| 1499 |
+
"decode-named-character-reference/character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
|
| 1500 |
+
|
| 1501 |
"eslint/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
| 1502 |
|
| 1503 |
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
|
|
|
| 1518 |
|
| 1519 |
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
| 1520 |
|
| 1521 |
+
"hastscript/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="],
|
| 1522 |
+
|
| 1523 |
+
"hastscript/comma-separated-tokens": ["comma-separated-tokens@1.0.8", "", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="],
|
| 1524 |
+
|
| 1525 |
+
"hastscript/property-information": ["property-information@5.6.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA=="],
|
| 1526 |
+
|
| 1527 |
+
"hastscript/space-separated-tokens": ["space-separated-tokens@1.1.5", "", {}, "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="],
|
| 1528 |
+
|
| 1529 |
"is-bun-module/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
| 1530 |
|
| 1531 |
"log-symbols/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
| 1532 |
|
| 1533 |
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
| 1534 |
|
| 1535 |
+
"mdast-util-mdx-jsx/parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
|
| 1536 |
+
|
| 1537 |
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
| 1538 |
|
| 1539 |
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
| 1540 |
|
| 1541 |
"ora/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
| 1542 |
|
|
|
|
|
|
|
| 1543 |
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
| 1544 |
|
| 1545 |
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
| 1546 |
|
| 1547 |
+
"refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="],
|
| 1548 |
+
|
| 1549 |
"restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
| 1550 |
|
| 1551 |
"sharp/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
|
|
|
| 1560 |
|
| 1561 |
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
| 1562 |
|
| 1563 |
+
"stringify-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
| 1564 |
+
|
| 1565 |
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
| 1566 |
|
| 1567 |
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
|
|
|
| 1590 |
|
| 1591 |
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
| 1592 |
|
| 1593 |
+
"hastscript/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
| 1594 |
+
|
| 1595 |
+
"mdast-util-mdx-jsx/parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
| 1596 |
+
|
| 1597 |
+
"mdast-util-mdx-jsx/parse-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
| 1598 |
+
|
| 1599 |
+
"mdast-util-mdx-jsx/parse-entities/character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
|
| 1600 |
+
|
| 1601 |
+
"mdast-util-mdx-jsx/parse-entities/is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
|
| 1602 |
+
|
| 1603 |
+
"mdast-util-mdx-jsx/parse-entities/is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
|
| 1604 |
+
|
| 1605 |
+
"mdast-util-mdx-jsx/parse-entities/is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
|
| 1606 |
+
|
| 1607 |
"restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
| 1608 |
|
| 1609 |
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
|
|
|
| 1613 |
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
| 1614 |
|
| 1615 |
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
| 1616 |
+
|
| 1617 |
+
"mdast-util-mdx-jsx/parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
|
| 1618 |
}
|
| 1619 |
}
|
frontend/package.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
| 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 |
"eslint-config-next": "^15.2.4",
|
|
@@ -30,6 +31,7 @@
|
|
| 30 |
"react-dom": "^19.1.0",
|
| 31 |
"react-markdown": "^10.1.0",
|
| 32 |
"react-resizable-panels": "^2.1.7",
|
|
|
|
| 33 |
"remark-gfm": "^4.0.1",
|
| 34 |
"shadcn": "^2.4.0-canary.19",
|
| 35 |
"socket.io-client": "^4.8.1",
|
|
|
|
| 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 |
+
"@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",
|
|
|
|
| 31 |
"react-dom": "^19.1.0",
|
| 32 |
"react-markdown": "^10.1.0",
|
| 33 |
"react-resizable-panels": "^2.1.7",
|
| 34 |
+
"react-syntax-highlighter": "^15.6.1",
|
| 35 |
"remark-gfm": "^4.0.1",
|
| 36 |
"shadcn": "^2.4.0-canary.19",
|
| 37 |
"socket.io-client": "^4.8.1",
|
frontend/src/components/ChatInterface.tsx
CHANGED
|
@@ -27,7 +27,18 @@ const loadFromStorage = (): ChatData => {
|
|
| 27 |
try {
|
| 28 |
const parsed = JSON.parse(data);
|
| 29 |
return {
|
| 30 |
-
conversations: Array.isArray(parsed.conversations)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
currentConversationId: parsed.currentConversationId,
|
| 32 |
};
|
| 33 |
} catch (e) {
|
|
@@ -45,7 +56,7 @@ const ChatInterface = () => {
|
|
| 45 |
sources: true,
|
| 46 |
citations: false,
|
| 47 |
max_depth: 1,
|
| 48 |
-
num_sites_per_query:
|
| 49 |
});
|
| 50 |
|
| 51 |
const userInputRef = useRef<HTMLTextAreaElement>(null);
|
|
@@ -118,7 +129,10 @@ const ChatInterface = () => {
|
|
| 118 |
// Format research stats and response
|
| 119 |
const stats = [`Total Queries: ${results.metadata.total_queries}`, `Sources Used: ${results.metadata.total_sources}`, `Search Depth: ${results.metadata.max_depth_reached}`].join(" | ");
|
| 120 |
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
const newMessages = [
|
| 124 |
...messages,
|
|
@@ -127,6 +141,8 @@ const ChatInterface = () => {
|
|
| 127 |
content: formattedResponse,
|
| 128 |
role: "assistant" as const,
|
| 129 |
timestamp: new Date(results.timestamp),
|
|
|
|
|
|
|
| 130 |
},
|
| 131 |
];
|
| 132 |
|
|
@@ -172,9 +188,9 @@ const ChatInterface = () => {
|
|
| 172 |
setCurrentConversationId(data.currentConversationId);
|
| 173 |
|
| 174 |
if (data.currentConversationId) {
|
| 175 |
-
const
|
| 176 |
-
if (
|
| 177 |
-
setChatState((prev) => ({ ...prev, messages:
|
| 178 |
}
|
| 179 |
}
|
| 180 |
}, []);
|
|
@@ -257,7 +273,7 @@ const ChatInterface = () => {
|
|
| 257 |
const handleNewConversation = () => {
|
| 258 |
userInputRef.current?.focus();
|
| 259 |
setCurrentConversationId(null);
|
| 260 |
-
setChatState((
|
| 261 |
messages: [],
|
| 262 |
isLoading: false,
|
| 263 |
error: null,
|
|
|
|
| 27 |
try {
|
| 28 |
const parsed = JSON.parse(data);
|
| 29 |
return {
|
| 30 |
+
conversations: Array.isArray(parsed.conversations)
|
| 31 |
+
? parsed.conversations.map((conv: Conversation) => ({
|
| 32 |
+
...conv,
|
| 33 |
+
messages: Array.isArray(conv.messages)
|
| 34 |
+
? conv.messages.map((msg: Message) => ({
|
| 35 |
+
...msg,
|
| 36 |
+
// Ensure media property is preserved if it exists
|
| 37 |
+
media: msg.media || undefined,
|
| 38 |
+
}))
|
| 39 |
+
: [],
|
| 40 |
+
}))
|
| 41 |
+
: [],
|
| 42 |
currentConversationId: parsed.currentConversationId,
|
| 43 |
};
|
| 44 |
} catch (e) {
|
|
|
|
| 56 |
sources: true,
|
| 57 |
citations: false,
|
| 58 |
max_depth: 1,
|
| 59 |
+
num_sites_per_query: 3,
|
| 60 |
});
|
| 61 |
|
| 62 |
const userInputRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
| 129 |
// Format research stats and response
|
| 130 |
const stats = [`Total Queries: ${results.metadata.total_queries}`, `Sources Used: ${results.metadata.total_sources}`, `Search Depth: ${results.metadata.max_depth_reached}`].join(" | ");
|
| 131 |
|
| 132 |
+
// Format images in a way the Message component can extract
|
| 133 |
+
const imageMarkdown = results.media?.images?.length ? `\n\n**Relevant Images:**\n${results.media.images.map((img) => ``).join("\n")}` : "";
|
| 134 |
+
|
| 135 |
+
const formattedResponse = [results.content, `\n\n---\n**Research Stats:**\n${stats}`, imageMarkdown].join("");
|
| 136 |
|
| 137 |
const newMessages = [
|
| 138 |
...messages,
|
|
|
|
| 141 |
content: formattedResponse,
|
| 142 |
role: "assistant" as const,
|
| 143 |
timestamp: new Date(results.timestamp),
|
| 144 |
+
// Store the media object directly with the message
|
| 145 |
+
media: results.media,
|
| 146 |
},
|
| 147 |
];
|
| 148 |
|
|
|
|
| 188 |
setCurrentConversationId(data.currentConversationId);
|
| 189 |
|
| 190 |
if (data.currentConversationId) {
|
| 191 |
+
const conversation = data.conversations.find((c) => c.id === data.currentConversationId);
|
| 192 |
+
if (conversation) {
|
| 193 |
+
setChatState((prev) => ({ ...prev, messages: conversation.messages }));
|
| 194 |
}
|
| 195 |
}
|
| 196 |
}, []);
|
|
|
|
| 273 |
const handleNewConversation = () => {
|
| 274 |
userInputRef.current?.focus();
|
| 275 |
setCurrentConversationId(null);
|
| 276 |
+
setChatState(() => ({
|
| 277 |
messages: [],
|
| 278 |
isLoading: false,
|
| 279 |
error: null,
|
frontend/src/components/Message.tsx
CHANGED
|
@@ -5,9 +5,69 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
|
| 5 |
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
| 6 |
import { Message as MessageType } from "@/lib/types";
|
| 7 |
import { Bot, Copy, MoreHorizontal, User2 } from "lucide-react";
|
| 8 |
-
import React from "react";
|
| 9 |
import ReactMarkdown from "react-markdown";
|
| 10 |
import remarkGfm from "remark-gfm";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
const MarkdownComponents: Record<string, React.ComponentType<any>> = {
|
| 13 |
h1: ({ children }) => <h1 className="text-2xl font-bold mb-4">{children}</h1>,
|
|
@@ -17,27 +77,83 @@ const MarkdownComponents: Record<string, React.ComponentType<any>> = {
|
|
| 17 |
ul: ({ children }) => <ul className="list-disc ml-6 mb-4">{children}</ul>,
|
| 18 |
ol: ({ children }) => <ol className="list-decimal ml-6 mb-4">{children}</ol>,
|
| 19 |
li: ({ children }) => <li className="mb-1">{children}</li>,
|
| 20 |
-
code: ({ node, inline, className, children, ...props }) =>
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
| 23 |
{children}
|
| 24 |
</code>
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
a: ({ children, href }) => (
|
| 29 |
<a href={href} className="text-primary hover:underline" target="_blank" rel="noopener noreferrer">
|
| 30 |
{children}
|
| 31 |
</a>
|
| 32 |
),
|
| 33 |
blockquote: ({ children }) => <blockquote className="border-l-4 border-border pl-4 italic my-4">{children}</blockquote>,
|
| 34 |
-
table: ({ children }) =>
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
};
|
| 37 |
|
| 38 |
const Message = ({ message }: { message: MessageType }) => {
|
| 39 |
const isUser = message.role === "user";
|
| 40 |
const isProgress = message.content.includes("%)") && message.role === "assistant";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
const copyToClipboard = () => {
|
| 43 |
navigator.clipboard.writeText(message.content);
|
|
@@ -85,8 +201,10 @@ const Message = ({ message }: { message: MessageType }) => {
|
|
| 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 |
-
{
|
| 89 |
</ReactMarkdown>
|
|
|
|
|
|
|
| 90 |
</div>
|
| 91 |
</div>
|
| 92 |
</div>
|
|
|
|
| 5 |
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
| 6 |
import { Message as MessageType } from "@/lib/types";
|
| 7 |
import { Bot, Copy, MoreHorizontal, User2 } from "lucide-react";
|
| 8 |
+
import React, { useState, useEffect } from "react";
|
| 9 |
import ReactMarkdown from "react-markdown";
|
| 10 |
import remarkGfm from "remark-gfm";
|
| 11 |
+
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
| 12 |
+
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
| 13 |
+
|
| 14 |
+
// ImageGallery component for handling images in a scrollable container
|
| 15 |
+
const ImageGallery = ({ imageUrls }: { imageUrls: string[] }) => {
|
| 16 |
+
const [loadedImages, setLoadedImages] = useState<string[]>([]);
|
| 17 |
+
|
| 18 |
+
// Lazy load images using Intersection Observer
|
| 19 |
+
useEffect(() => {
|
| 20 |
+
const observer = new IntersectionObserver(
|
| 21 |
+
(entries) => {
|
| 22 |
+
entries.forEach((entry) => {
|
| 23 |
+
if (entry.isIntersecting) {
|
| 24 |
+
const img = entry.target as HTMLImageElement;
|
| 25 |
+
const src = img.getAttribute("data-src");
|
| 26 |
+
if (src) {
|
| 27 |
+
img.src = src;
|
| 28 |
+
observer.unobserve(img);
|
| 29 |
+
setLoadedImages((prev) => [...prev, src]);
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
});
|
| 33 |
+
},
|
| 34 |
+
{ rootMargin: "100px" }
|
| 35 |
+
);
|
| 36 |
+
|
| 37 |
+
const imgPlaceholders = document.querySelectorAll(".lazy-image");
|
| 38 |
+
imgPlaceholders.forEach((img) => observer.observe(img));
|
| 39 |
+
|
| 40 |
+
return () => observer.disconnect();
|
| 41 |
+
}, [imageUrls]);
|
| 42 |
+
|
| 43 |
+
if (!imageUrls || imageUrls.length === 0) return null;
|
| 44 |
+
|
| 45 |
+
return (
|
| 46 |
+
<div className="mt-4 mb-4">
|
| 47 |
+
<h3 className="text-md font-semibold mb-2">Relevant Images:</h3>
|
| 48 |
+
<ScrollArea className="w-full max-h-72 rounded-md border">
|
| 49 |
+
<div className="p-2 space-y-3">
|
| 50 |
+
{imageUrls.map((url, index) => (
|
| 51 |
+
<div key={index} className="image-container">
|
| 52 |
+
<img
|
| 53 |
+
className="lazy-image rounded-md max-w-full"
|
| 54 |
+
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%23f1f1f1'/%3E%3C/svg%3E"
|
| 55 |
+
data-src={url}
|
| 56 |
+
alt={`Research image ${index + 1}`}
|
| 57 |
+
loading="lazy"
|
| 58 |
+
onError={(e) => {
|
| 59 |
+
const target = e.target as HTMLImageElement;
|
| 60 |
+
target.onerror = null;
|
| 61 |
+
target.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%23f1f1f1'/%3E%3Ctext x='50%' y='50%' font-size='12' text-anchor='middle' alignment-baseline='middle' font-family='Arial, sans-serif'%3EImage failed to load%3C/text%3E%3C/svg%3E";
|
| 62 |
+
}}
|
| 63 |
+
/>
|
| 64 |
+
</div>
|
| 65 |
+
))}
|
| 66 |
+
</div>
|
| 67 |
+
</ScrollArea>
|
| 68 |
+
</div>
|
| 69 |
+
);
|
| 70 |
+
};
|
| 71 |
|
| 72 |
const MarkdownComponents: Record<string, React.ComponentType<any>> = {
|
| 73 |
h1: ({ children }) => <h1 className="text-2xl font-bold mb-4">{children}</h1>,
|
|
|
|
| 77 |
ul: ({ children }) => <ul className="list-disc ml-6 mb-4">{children}</ul>,
|
| 78 |
ol: ({ children }) => <ol className="list-decimal ml-6 mb-4">{children}</ol>,
|
| 79 |
li: ({ children }) => <li className="mb-1">{children}</li>,
|
| 80 |
+
code: ({ node, inline, className, children, ...props }) => {
|
| 81 |
+
const match = /language-(\w+)/.exec(className || "");
|
| 82 |
+
const language = match ? match[1] : "";
|
| 83 |
+
|
| 84 |
+
return Object.keys(node.properties).length === 0 ? (
|
| 85 |
+
<code className="bg-muted px-1 py-0.5 rounded-md text-sm" {...props}>
|
| 86 |
{children}
|
| 87 |
</code>
|
| 88 |
+
) : (
|
| 89 |
+
<ScrollArea className="w-full max-w-full">
|
| 90 |
+
<SyntaxHighlighter
|
| 91 |
+
style={oneDark}
|
| 92 |
+
language={language || "text"}
|
| 93 |
+
PreTag="div"
|
| 94 |
+
className="rounded-md my-2 text-sm"
|
| 95 |
+
showLineNumbers
|
| 96 |
+
customStyle={{
|
| 97 |
+
margin: 0,
|
| 98 |
+
borderRadius: "0.5rem",
|
| 99 |
+
padding: "1rem",
|
| 100 |
+
}}
|
| 101 |
+
{...props}>
|
| 102 |
+
{String(children).replace(/\n$/, "")}
|
| 103 |
+
</SyntaxHighlighter>
|
| 104 |
+
</ScrollArea>
|
| 105 |
+
);
|
| 106 |
+
},
|
| 107 |
+
pre: ({ children }) => <div className="bg-transparent p-0 max-w-full">{children}</div>,
|
| 108 |
a: ({ children, href }) => (
|
| 109 |
<a href={href} className="text-primary hover:underline" target="_blank" rel="noopener noreferrer">
|
| 110 |
{children}
|
| 111 |
</a>
|
| 112 |
),
|
| 113 |
blockquote: ({ children }) => <blockquote className="border-l-4 border-border pl-4 italic my-4">{children}</blockquote>,
|
| 114 |
+
table: ({ children }) => (
|
| 115 |
+
<div className="overflow-x-auto border rounded-2xl my-4">
|
| 116 |
+
<table className="w-full border-collapse rounded-2xl overflow-hidden shadow-sm">{children}</table>
|
| 117 |
+
</div>
|
| 118 |
+
),
|
| 119 |
+
thead: ({ children }) => <thead className="bg-muted">{children}</thead>,
|
| 120 |
+
tbody: ({ children }) => <tbody>{children}</tbody>,
|
| 121 |
+
th: ({ children }) => <th className="border-r last:border-r-0 border-slate-900 px-4 py-2 text-left font-semibold">{children}</th>,
|
| 122 |
+
tr: ({ children }) => <tr className="border-b last:border-b-0 border-border">{children}</tr>,
|
| 123 |
+
td: ({ children }) => <td className="border-r last:border-r-0 border-border px-4 py-2">{children}</td>,
|
| 124 |
};
|
| 125 |
|
| 126 |
const Message = ({ message }: { message: MessageType }) => {
|
| 127 |
const isUser = message.role === "user";
|
| 128 |
const isProgress = message.content.includes("%)") && message.role === "assistant";
|
| 129 |
+
const [imageUrls, setImageUrls] = useState<string[]>([]);
|
| 130 |
+
|
| 131 |
+
// Extract image URLs from the message content or use the media object
|
| 132 |
+
useEffect(() => {
|
| 133 |
+
if (!isUser && !isProgress) {
|
| 134 |
+
let urls: string[] = [];
|
| 135 |
+
|
| 136 |
+
// First, check if there's a media object with images
|
| 137 |
+
if (message.media?.images && message.media.images.length > 0) {
|
| 138 |
+
urls = message.media.images;
|
| 139 |
+
} else {
|
| 140 |
+
// Fallback to extracting from markdown content
|
| 141 |
+
const imgRegex = /!\[.*?\]\((.*?)\)/g;
|
| 142 |
+
let match;
|
| 143 |
+
|
| 144 |
+
while ((match = imgRegex.exec(message.content)) !== null) {
|
| 145 |
+
if (match[1]) {
|
| 146 |
+
urls.push(match[1]);
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
setImageUrls(urls);
|
| 152 |
+
}
|
| 153 |
+
}, [message.content, message.media, isUser, isProgress]);
|
| 154 |
+
|
| 155 |
+
// Prepare message content without image markdown
|
| 156 |
+
const cleanContent = message.content.replace(/!\[.*?\]\(.*?\)\n?/g, "").replace(/\*\*Relevant Images:\*\*\n/g, "");
|
| 157 |
|
| 158 |
const copyToClipboard = () => {
|
| 159 |
navigator.clipboard.writeText(message.content);
|
|
|
|
| 201 |
|
| 202 |
<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" }}>
|
| 203 |
<ReactMarkdown remarkPlugins={[remarkGfm]} components={MarkdownComponents}>
|
| 204 |
+
{cleanContent}
|
| 205 |
</ReactMarkdown>
|
| 206 |
+
|
| 207 |
+
{imageUrls.length > 0 && <ImageGallery imageUrls={imageUrls} />}
|
| 208 |
</div>
|
| 209 |
</div>
|
| 210 |
</div>
|
frontend/src/components/ui/ChatLayout.tsx
CHANGED
|
@@ -3,11 +3,12 @@ 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, 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 |
|
| 12 |
interface ChatLayoutProps {
|
| 13 |
sidebar: React.ReactNode;
|
|
@@ -19,8 +20,28 @@ const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsP
|
|
| 19 |
return (
|
| 20 |
<div className="h-screen flex flex-col">
|
| 21 |
<header className="border-b-2 h-14 flex items-center px-6">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
<Link href={"/"}>
|
| 23 |
-
<h1 className="text-xl font-semibold">KnowledgeNet: Deep Research</h1>
|
|
|
|
| 24 |
</Link>
|
| 25 |
<div className="flex-1" />
|
| 26 |
<ThemeToggle />
|
|
@@ -33,9 +54,7 @@ const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsP
|
|
| 33 |
<DialogContent>
|
| 34 |
<DialogHeader>
|
| 35 |
<DialogTitle>Research Settings</DialogTitle>
|
| 36 |
-
<DialogDescription>
|
| 37 |
-
Configure your research parameters and preferences.
|
| 38 |
-
</DialogDescription>
|
| 39 |
</DialogHeader>
|
| 40 |
{settingsPanel}
|
| 41 |
</DialogContent>
|
|
@@ -52,7 +71,7 @@ const ChatLayout: React.FC<ChatLayoutProps> = ({ sidebar, mainContent, settingsP
|
|
| 52 |
|
| 53 |
<ResizableHandle withHandle className="hidden md:flex" />
|
| 54 |
|
| 55 |
-
<ResizablePanel defaultSize={75}>
|
| 56 |
<Tabs defaultValue="chat" className="h-full flex flex-col">
|
| 57 |
<div className="p-4">
|
| 58 |
<TabsList className="">
|
|
|
|
| 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;
|
|
|
|
| 20 |
return (
|
| 21 |
<div className="h-screen flex flex-col">
|
| 22 |
<header className="border-b-2 h-14 flex items-center px-6">
|
| 23 |
+
<Sheet>
|
| 24 |
+
<SheetTrigger asChild>
|
| 25 |
+
<Button variant="ghost" size="icon" className="md:hidden mr-2">
|
| 26 |
+
<Menu size={20} />
|
| 27 |
+
<span className="sr-only">Toggle sidebar</span>
|
| 28 |
+
</Button>
|
| 29 |
+
</SheetTrigger>
|
| 30 |
+
<SheetContent side="left" className="w-[80%] sm:w-[350px] p-0">
|
| 31 |
+
<SheetTitle className="sr-only">Mobile Navigation</SheetTitle>
|
| 32 |
+
<SheetDescription className="sr-only">
|
| 33 |
+
Sidebar navigation for mobile devices
|
| 34 |
+
</SheetDescription>
|
| 35 |
+
<div className="border-b p-4">
|
| 36 |
+
<h2 className="text-lg font-semibold">Conversations</h2>
|
| 37 |
+
</div>
|
| 38 |
+
<ScrollArea className="h-[calc(100%-60px)] py-2">{sidebar}</ScrollArea>
|
| 39 |
+
</SheetContent>
|
| 40 |
+
</Sheet>
|
| 41 |
+
|
| 42 |
<Link href={"/"}>
|
| 43 |
+
<h1 className="text-xl font-semibold hidden sm:inline">KnowledgeNet: Deep Research</h1>
|
| 44 |
+
<h1 className="text-lg font-semibold sm:hidden">KNet: Deep Research</h1>
|
| 45 |
</Link>
|
| 46 |
<div className="flex-1" />
|
| 47 |
<ThemeToggle />
|
|
|
|
| 54 |
<DialogContent>
|
| 55 |
<DialogHeader>
|
| 56 |
<DialogTitle>Research Settings</DialogTitle>
|
| 57 |
+
<DialogDescription>Configure your research parameters and preferences.</DialogDescription>
|
|
|
|
|
|
|
| 58 |
</DialogHeader>
|
| 59 |
{settingsPanel}
|
| 60 |
</DialogContent>
|
|
|
|
| 71 |
|
| 72 |
<ResizableHandle withHandle className="hidden md:flex" />
|
| 73 |
|
| 74 |
+
<ResizablePanel defaultSize={75} className="w-full md:w-auto">
|
| 75 |
<Tabs defaultValue="chat" className="h-full flex flex-col">
|
| 76 |
<div className="p-4">
|
| 77 |
<TabsList className="">
|
frontend/src/components/ui/dialog.tsx
CHANGED
|
@@ -41,6 +41,7 @@ const DialogContent = React.forwardRef<
|
|
| 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}
|
|
|
|
| 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 |
+
aria-describedby={!props['aria-describedby'] ? undefined : props['aria-describedby']}
|
| 45 |
{...props}
|
| 46 |
>
|
| 47 |
{children}
|
frontend/src/components/ui/sheet.tsx
CHANGED
|
@@ -21,7 +21,7 @@ const SheetOverlay = React.forwardRef<
|
|
| 21 |
>(({ className, ...props }, ref) => (
|
| 22 |
<SheetPrimitive.Overlay
|
| 23 |
className={cn(
|
| 24 |
-
"fixed inset-0 z-50 bg-black/80
|
| 25 |
className
|
| 26 |
)}
|
| 27 |
{...props}
|
|
@@ -31,7 +31,7 @@ const SheetOverlay = React.forwardRef<
|
|
| 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=
|
| 35 |
{
|
| 36 |
variants: {
|
| 37 |
side: {
|
|
@@ -40,7 +40,7 @@ const sheetVariants = cva(
|
|
| 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
|
| 44 |
},
|
| 45 |
},
|
| 46 |
defaultVariants: {
|
|
@@ -62,13 +62,14 @@ const SheetContent = React.forwardRef<
|
|
| 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 |
))
|
|
|
|
| 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}
|
|
|
|
| 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=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
| 35 |
{
|
| 36 |
variants: {
|
| 37 |
side: {
|
|
|
|
| 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: {
|
|
|
|
| 62 |
<SheetPrimitive.Content
|
| 63 |
ref={ref}
|
| 64 |
className={cn(sheetVariants({ side }), className)}
|
| 65 |
+
aria-describedby={!props['aria-describedby'] ? undefined : props['aria-describedby']}
|
| 66 |
{...props}
|
| 67 |
>
|
| 68 |
+
{children}
|
| 69 |
<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">
|
| 70 |
<X className="h-4 w-4" />
|
| 71 |
<span className="sr-only">Close</span>
|
| 72 |
</SheetPrimitive.Close>
|
|
|
|
| 73 |
</SheetPrimitive.Content>
|
| 74 |
</SheetPortal>
|
| 75 |
))
|
frontend/src/lib/types.ts
CHANGED
|
@@ -3,6 +3,15 @@ export interface Message {
|
|
| 3 |
content: string;
|
| 4 |
role: "user" | "assistant" | "system";
|
| 5 |
timestamp: Date;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
}
|
| 7 |
|
| 8 |
export interface ChatState {
|
|
|
|
| 3 |
content: string;
|
| 4 |
role: "user" | "assistant" | "system";
|
| 5 |
timestamp: Date;
|
| 6 |
+
media?: {
|
| 7 |
+
images?: string[];
|
| 8 |
+
videos?: string[];
|
| 9 |
+
links?: Array<{
|
| 10 |
+
text: string;
|
| 11 |
+
url: string;
|
| 12 |
+
}>;
|
| 13 |
+
references?: any[];
|
| 14 |
+
};
|
| 15 |
}
|
| 16 |
|
| 17 |
export interface ChatState {
|