Qar-Raz commited on
Commit
344f4d8
·
1 Parent(s): b4cdea6

improved frontend, added markdown based retrieval, added time metrics

Browse files
.gitignore CHANGED
@@ -25,6 +25,7 @@ dist/
25
  .mypy_cache/
26
  .ruff_cache/
27
  .ipynb_checkpoints/
 
28
 
29
  # IDE/editor
30
  .vscode/
 
25
  .mypy_cache/
26
  .ruff_cache/
27
  .ipynb_checkpoints/
28
+ .cache/
29
 
30
  # IDE/editor
31
  .vscode/
api.py CHANGED
@@ -1,14 +1,18 @@
1
  # Fastapi endpoints defined here
 
2
  import os
 
3
  import time
4
  from typing import Any
5
 
6
  from dotenv import load_dotenv
7
  from fastapi import FastAPI, HTTPException
8
  from fastapi.middleware.cors import CORSMiddleware
 
 
9
  from pydantic import BaseModel, Field
10
 
11
- from vector_db import get_index_by_name, load_chunks_from_pinecone
12
  from retriever.retriever import HybridRetriever
13
  from retriever.generator import RAGGenerator
14
  from retriever.processor import ChunkProcessor
@@ -20,6 +24,9 @@ from models.deepseek_v3 import DeepSeek_V3
20
  from models.tiny_aya import TinyAya
21
 
22
 
 
 
 
23
  class PredictRequest(BaseModel):
24
  query: str = Field(..., min_length=1, description="User query text")
25
  model: str = Field(default="Llama-3-8B", description="Model name key")
@@ -36,6 +43,102 @@ class PredictResponse(BaseModel):
36
  metrics: dict[str, float]
37
 
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  # Fastapi setup
41
  # Fastapi allows us to define python based endpoint
@@ -89,10 +192,16 @@ def _resolve_model(name: str, models: dict[str, Any]) -> tuple[str, Any]:
89
 
90
  @app.on_event("startup")
91
  def startup_event() -> None:
 
 
 
92
  load_dotenv()
 
93
 
 
94
  hf_token = os.getenv("HF_TOKEN")
95
  pinecone_api_key = os.getenv("PINECONE_API_KEY")
 
96
 
97
  if not pinecone_api_key:
98
  raise RuntimeError("PINECONE_API_KEY not found in environment variables")
@@ -101,35 +210,71 @@ def startup_event() -> None:
101
 
102
  index_name = "arxiv-tournament-recursive"
103
  embed_model_name = "all-MiniLM-L6-v2"
 
 
 
104
 
105
- startup_start = time.perf_counter()
106
-
107
  index = get_index_by_name(
108
  api_key=pinecone_api_key,
109
  index_name=index_name
110
  )
 
111
 
112
  chunks_start = time.perf_counter()
113
- final_chunks = load_chunks_from_pinecone(index)
 
 
 
 
 
 
114
  chunk_load_time = time.perf_counter() - chunks_start
115
 
116
  if not final_chunks:
117
  raise RuntimeError("No chunks found in Pinecone metadata. Run indexing once before API mode.")
118
 
119
- proc = ChunkProcessor(model_name=embed_model_name, verbose=False)
 
 
 
 
120
  retriever = HybridRetriever(final_chunks, proc.encoder, verbose=False)
 
 
 
121
  rag_engine = RAGGenerator()
 
 
 
122
  models = _build_models(hf_token)
 
123
 
 
124
  state["index"] = index
125
  state["retriever"] = retriever
126
  state["rag_engine"] = rag_engine
127
  state["models"] = models
 
 
 
128
 
129
  startup_time = time.perf_counter() - startup_start
130
  print(
131
  f"API startup complete | chunks={len(final_chunks)} | "
132
- f"chunk_load={chunk_load_time:.3f}s | total={startup_time:.3f}s"
 
 
 
 
 
 
 
 
 
 
 
 
133
  )
134
 
135
 
@@ -139,27 +284,65 @@ def health() -> dict[str, str]:
139
  return {"status": "ok" if ready else "starting"}
140
 
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  # Predict endpoint that takes a query and returns an answer along with contexts and metrics
144
  # is called from the frontend when user clicks submits
145
  # Also resolves model based on user selection
146
  @app.post("/predict", response_model=PredictResponse)
147
  def predict(payload: PredictRequest) -> PredictResponse:
 
 
 
148
  if not state:
149
  raise HTTPException(status_code=503, detail="Service not initialized yet")
150
 
151
  query = payload.query.strip()
152
  if not query:
153
  raise HTTPException(status_code=400, detail="Query cannot be empty")
 
154
 
155
- total_start = time.perf_counter()
156
-
157
  retriever: HybridRetriever = state["retriever"]
158
  index = state["index"]
159
  rag_engine: RAGGenerator = state["rag_engine"]
160
  models: dict[str, Any] = state["models"]
 
161
 
 
162
  model_name, model_instance = _resolve_model(payload.model, models)
 
163
 
164
  retrieval_start = time.perf_counter()
165
  contexts = retriever.search(
@@ -177,19 +360,116 @@ def predict(payload: PredictRequest) -> PredictResponse:
177
  if not contexts:
178
  raise HTTPException(status_code=404, detail="No context chunks retrieved for this query")
179
 
180
- generation_start = time.perf_counter()
181
  answer = rag_engine.get_answer(model_instance, query, contexts, temperature=0.1)
182
- generation_time = time.perf_counter() - generation_start
 
 
 
 
 
 
 
 
 
 
183
 
184
- total_time = time.perf_counter() - total_start
 
 
 
 
 
 
 
 
 
 
185
 
186
  return PredictResponse(
187
  model=model_name,
188
  answer=answer,
189
  contexts=contexts,
190
- metrics={
191
- "retrieval_s": round(retrieval_time, 3),
192
- "generation_s": round(generation_time, 3),
193
- "total_s": round(total_time, 3),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  },
195
  )
 
1
  # Fastapi endpoints defined here
2
+ import json
3
  import os
4
+ import re
5
  import time
6
  from typing import Any
7
 
8
  from dotenv import load_dotenv
9
  from fastapi import FastAPI, HTTPException
10
  from fastapi.middleware.cors import CORSMiddleware
11
+ from fastapi.responses import StreamingResponse
12
+ from huggingface_hub import InferenceClient
13
  from pydantic import BaseModel, Field
14
 
15
+ from vector_db import get_index_by_name, load_chunks_with_local_cache
16
  from retriever.retriever import HybridRetriever
17
  from retriever.generator import RAGGenerator
18
  from retriever.processor import ChunkProcessor
 
24
  from models.tiny_aya import TinyAya
25
 
26
 
27
+ #Added cacheing and time logging to track every stages time
28
+
29
+
30
  class PredictRequest(BaseModel):
31
  query: str = Field(..., min_length=1, description="User query text")
32
  model: str = Field(default="Llama-3-8B", description="Model name key")
 
43
  metrics: dict[str, float]
44
 
45
 
46
+ class TitleRequest(BaseModel):
47
+ query: str = Field(..., min_length=1, description="First user message")
48
+
49
+
50
+ class TitleResponse(BaseModel):
51
+ title: str
52
+ source: str
53
+
54
+
55
+ def _to_ndjson(payload: dict[str, Any]) -> str:
56
+ return json.dumps(payload, ensure_ascii=False) + "\n"
57
+
58
+
59
+
60
+ # simpliest possible implementation to determine chat title
61
+ # is fallback incase hf generation fails.
62
+
63
+ def _title_from_query(query: str) -> str:
64
+ stop_words = {
65
+ "a", "an", "and", "are", "as", "at", "be", "by", "can", "do", "for", "from", "how",
66
+ "i", "in", "is", "it", "me", "my", "of", "on", "or", "please", "show", "tell", "that",
67
+ "the", "this", "to", "we", "what", "when", "where", "which", "why", "with", "you", "your",
68
+ }
69
+
70
+ words = re.findall(r"[A-Za-z0-9][A-Za-z0-9\-_/+]*", query)
71
+ if not words:
72
+ return "New Chat"
73
+
74
+ filtered: list[str] = []
75
+ for word in words:
76
+ cleaned = word.strip("-_/+")
77
+ if not cleaned:
78
+ continue
79
+ if cleaned.lower() in stop_words:
80
+ continue
81
+ filtered.append(cleaned)
82
+ if len(filtered) >= 6:
83
+ break
84
+
85
+ chosen = filtered if filtered else words[:6]
86
+ normalized = [w.capitalize() if w.islower() else w for w in chosen]
87
+ title = " ".join(normalized).strip()
88
+ return title[:80] if title else "New Chat"
89
+
90
+
91
+ #actual code for title generation using hf model, uses a simple prompt to generate a concise title based on user query, with some formatting rules to ensure clean output. If generation fails or returns an empty title, falls back to rule-based method.
92
+ # is called in the /predict/title endpoint
93
+
94
+ def _clean_title_text(raw: str) -> str:
95
+ text = (raw or "").strip()
96
+ text = text.replace("\n", " ").replace("\r", " ")
97
+ text = re.sub(r"^[\"'`\s]+|[\"'`\s]+$", "", text)
98
+ text = re.sub(r"\s+", " ", text).strip()
99
+ words = text.split()
100
+ if len(words) > 8:
101
+ text = " ".join(words[:8])
102
+ return text[:80]
103
+
104
+
105
+ def _title_from_hf(query: str, client: InferenceClient, model_id: str) -> str | None:
106
+ system_prompt = (
107
+ "You generate short chat titles. Return only a title, no punctuation at the end, no quotes."
108
+ )
109
+ user_prompt = (
110
+ "Create a concise 3-7 word title for this user request:\n"
111
+ f"{query}"
112
+ )
113
+
114
+ response = client.chat_completion(
115
+ model=model_id,
116
+ messages=[
117
+ {"role": "system", "content": system_prompt},
118
+ {"role": "user", "content": user_prompt},
119
+ ],
120
+ max_tokens=24,
121
+ temperature=0.2,
122
+ )
123
+ if not response or not response.choices:
124
+ return None
125
+
126
+ raw_title = response.choices[0].message.content or ""
127
+ title = _clean_title_text(raw_title)
128
+ if not title or title.lower() == "new chat":
129
+ return None
130
+ return title
131
+
132
+
133
+ def _parse_title_model_candidates() -> list[str]:
134
+ raw = os.getenv(
135
+ "TITLE_MODEL_IDS",
136
+ "Qwen/Qwen2.5-1.5B-Instruct,CohereLabs/tiny-aya-global,meta-llama/Meta-Llama-3-8B-Instruct",
137
+ )
138
+ models = [m.strip() for m in raw.split(",") if m.strip()]
139
+ return models or ["meta-llama/Meta-Llama-3-8B-Instruct"]
140
+
141
+
142
 
143
  # Fastapi setup
144
  # Fastapi allows us to define python based endpoint
 
192
 
193
  @app.on_event("startup")
194
  def startup_event() -> None:
195
+ startup_start = time.perf_counter()
196
+
197
+ dotenv_start = time.perf_counter()
198
  load_dotenv()
199
+ dotenv_time = time.perf_counter() - dotenv_start
200
 
201
+ env_start = time.perf_counter()
202
  hf_token = os.getenv("HF_TOKEN")
203
  pinecone_api_key = os.getenv("PINECONE_API_KEY")
204
+ env_time = time.perf_counter() - env_start
205
 
206
  if not pinecone_api_key:
207
  raise RuntimeError("PINECONE_API_KEY not found in environment variables")
 
210
 
211
  index_name = "arxiv-tournament-recursive"
212
  embed_model_name = "all-MiniLM-L6-v2"
213
+ project_root = os.path.dirname(os.path.abspath(__file__))
214
+ cache_dir = os.getenv("BM25_CACHE_DIR", os.path.join(project_root, ".cache"))
215
+ force_cache_refresh = os.getenv("BM25_CACHE_REFRESH", "0").lower() in {"1", "true", "yes"}
216
 
217
+ index_start = time.perf_counter()
 
218
  index = get_index_by_name(
219
  api_key=pinecone_api_key,
220
  index_name=index_name
221
  )
222
+ index_time = time.perf_counter() - index_start
223
 
224
  chunks_start = time.perf_counter()
225
+ final_chunks, chunk_source = load_chunks_with_local_cache(
226
+ index=index,
227
+ index_name=index_name,
228
+ cache_dir=cache_dir,
229
+ batch_size=100,
230
+ force_refresh=force_cache_refresh,
231
+ )
232
  chunk_load_time = time.perf_counter() - chunks_start
233
 
234
  if not final_chunks:
235
  raise RuntimeError("No chunks found in Pinecone metadata. Run indexing once before API mode.")
236
 
237
+ processor_start = time.perf_counter()
238
+ proc = ChunkProcessor(model_name=embed_model_name, verbose=False, load_hf_embeddings=False)
239
+ processor_time = time.perf_counter() - processor_start
240
+
241
+ retriever_start = time.perf_counter()
242
  retriever = HybridRetriever(final_chunks, proc.encoder, verbose=False)
243
+ retriever_time = time.perf_counter() - retriever_start
244
+
245
+ rag_start = time.perf_counter()
246
  rag_engine = RAGGenerator()
247
+ rag_time = time.perf_counter() - rag_start
248
+
249
+ models_start = time.perf_counter()
250
  models = _build_models(hf_token)
251
+ models_time = time.perf_counter() - models_start
252
 
253
+ state_start = time.perf_counter()
254
  state["index"] = index
255
  state["retriever"] = retriever
256
  state["rag_engine"] = rag_engine
257
  state["models"] = models
258
+ state["title_model_ids"] = _parse_title_model_candidates()
259
+ state["title_client"] = InferenceClient(token=hf_token)
260
+ state_time = time.perf_counter() - state_start
261
 
262
  startup_time = time.perf_counter() - startup_start
263
  print(
264
  f"API startup complete | chunks={len(final_chunks)} | "
265
+ f"dotenv={dotenv_time:.3f}s | "
266
+ f"env={env_time:.3f}s | "
267
+ f"index={index_time:.3f}s | "
268
+ f"cache_dir={cache_dir} | "
269
+ f"force_cache_refresh={force_cache_refresh} | "
270
+ f"chunk_source={chunk_source} | "
271
+ f"chunk_load={chunk_load_time:.3f}s | "
272
+ f"processor={processor_time:.3f}s | "
273
+ f"retriever={retriever_time:.3f}s | "
274
+ f"rag={rag_time:.3f}s | "
275
+ f"models={models_time:.3f}s | "
276
+ f"state={state_time:.3f}s | "
277
+ f"total={startup_time:.3f}s"
278
  )
279
 
280
 
 
284
  return {"status": "ok" if ready else "starting"}
285
 
286
 
287
+ #title generation endpoint
288
+ # is called only once when we create a new chat, after first prompt
289
+ @app.post("/predict/title", response_model=TitleResponse)
290
+ def suggest_title(payload: TitleRequest) -> TitleResponse:
291
+ query = payload.query.strip()
292
+ if not query:
293
+ raise HTTPException(status_code=400, detail="Query cannot be empty")
294
+
295
+ fallback_title = _title_from_query(query)
296
+
297
+ title_client: InferenceClient | None = state.get("title_client")
298
+ title_model_ids: list[str] = state.get("title_model_ids", _parse_title_model_candidates())
299
+
300
+ if title_client is not None:
301
+ for title_model_id in title_model_ids:
302
+ try:
303
+ hf_title = _title_from_hf(query, title_client, title_model_id)
304
+ if hf_title:
305
+ return TitleResponse(title=hf_title, source=f"hf:{title_model_id}")
306
+ except Exception as exc:
307
+ err_text = str(exc)
308
+ # Provider/model availability differs across HF accounts; skip unsupported models.
309
+ if "model_not_supported" in err_text or "not supported by any provider" in err_text:
310
+ continue
311
+ print(f"Title generation model failed ({title_model_id}): {exc}")
312
+ continue
313
+
314
+ print("Title generation fallback triggered: no title model available/successful")
315
+
316
+ return TitleResponse(title=fallback_title, source="rule-based")
317
+
318
+
319
 
320
  # Predict endpoint that takes a query and returns an answer along with contexts and metrics
321
  # is called from the frontend when user clicks submits
322
  # Also resolves model based on user selection
323
  @app.post("/predict", response_model=PredictResponse)
324
  def predict(payload: PredictRequest) -> PredictResponse:
325
+ req_start = time.perf_counter()
326
+
327
+ precheck_start = time.perf_counter()
328
  if not state:
329
  raise HTTPException(status_code=503, detail="Service not initialized yet")
330
 
331
  query = payload.query.strip()
332
  if not query:
333
  raise HTTPException(status_code=400, detail="Query cannot be empty")
334
+ precheck_time = time.perf_counter() - precheck_start
335
 
336
+ state_access_start = time.perf_counter()
 
337
  retriever: HybridRetriever = state["retriever"]
338
  index = state["index"]
339
  rag_engine: RAGGenerator = state["rag_engine"]
340
  models: dict[str, Any] = state["models"]
341
+ state_access_time = time.perf_counter() - state_access_start
342
 
343
+ model_resolve_start = time.perf_counter()
344
  model_name, model_instance = _resolve_model(payload.model, models)
345
+ model_resolve_time = time.perf_counter() - model_resolve_start
346
 
347
  retrieval_start = time.perf_counter()
348
  contexts = retriever.search(
 
360
  if not contexts:
361
  raise HTTPException(status_code=404, detail="No context chunks retrieved for this query")
362
 
363
+ inference_start = time.perf_counter()
364
  answer = rag_engine.get_answer(model_instance, query, contexts, temperature=0.1)
365
+ inference_time = time.perf_counter() - inference_start
366
+
367
+ response_start = time.perf_counter()
368
+ metrics = {
369
+ "precheck_s": round(precheck_time, 3),
370
+ "state_access_s": round(state_access_time, 3),
371
+ "model_resolve_s": round(model_resolve_time, 3),
372
+ "retrieval_s": round(retrieval_time, 3),
373
+ "inference_s": round(inference_time, 3),
374
+ }
375
+ response_build_time = time.perf_counter() - response_start
376
 
377
+ total_time = time.perf_counter() - req_start
378
+ metrics["response_build_s"] = round(response_build_time, 3)
379
+ metrics["total_s"] = round(total_time, 3)
380
+
381
+ print(
382
+ f"Predict timing | model={model_name} | mode={payload.mode} | "
383
+ f"rerank={payload.rerank_strategy} | precheck={precheck_time:.3f}s | "
384
+ f"state_access={state_access_time:.3f}s | model_resolve={model_resolve_time:.3f}s | "
385
+ f"retrieval={retrieval_time:.3f}s | inference={inference_time:.3f}s | "
386
+ f"response_build={response_build_time:.3f}s | total={total_time:.3f}s"
387
+ )
388
 
389
  return PredictResponse(
390
  model=model_name,
391
  answer=answer,
392
  contexts=contexts,
393
+ metrics=metrics,
394
+ )
395
+
396
+ # new endpoint for streaming response, allows frontend to render tokens as they come in instead of waiting for full answer
397
+ @app.post("/predict/stream")
398
+ def predict_stream(payload: PredictRequest) -> StreamingResponse:
399
+ req_start = time.perf_counter()
400
+
401
+ precheck_start = time.perf_counter()
402
+ if not state:
403
+ raise HTTPException(status_code=503, detail="Service not initialized yet")
404
+
405
+ query = payload.query.strip()
406
+ if not query:
407
+ raise HTTPException(status_code=400, detail="Query cannot be empty")
408
+ precheck_time = time.perf_counter() - precheck_start
409
+
410
+ state_access_start = time.perf_counter()
411
+ retriever: HybridRetriever = state["retriever"]
412
+ index = state["index"]
413
+ rag_engine: RAGGenerator = state["rag_engine"]
414
+ models: dict[str, Any] = state["models"]
415
+ state_access_time = time.perf_counter() - state_access_start
416
+
417
+ model_resolve_start = time.perf_counter()
418
+ model_name, model_instance = _resolve_model(payload.model, models)
419
+ model_resolve_time = time.perf_counter() - model_resolve_start
420
+
421
+ retrieval_start = time.perf_counter()
422
+ contexts = retriever.search(
423
+ query,
424
+ index,
425
+ mode=payload.mode,
426
+ rerank_strategy=payload.rerank_strategy,
427
+ use_mmr=True,
428
+ top_k=payload.top_k,
429
+ final_k=payload.final_k,
430
+ verbose=False,
431
+ )
432
+ retrieval_time = time.perf_counter() - retrieval_start
433
+
434
+ if not contexts:
435
+ raise HTTPException(status_code=404, detail="No context chunks retrieved for this query")
436
+
437
+ def stream_events():
438
+ inference_start = time.perf_counter()
439
+ answer_parts: list[str] = []
440
+ try:
441
+ for token in rag_engine.get_answer_stream(model_instance, query, contexts, temperature=0.1):
442
+ answer_parts.append(token)
443
+ yield _to_ndjson({"type": "token", "token": token})
444
+
445
+ inference_time = time.perf_counter() - inference_start
446
+ total_time = time.perf_counter() - req_start
447
+ answer = "".join(answer_parts)
448
+ metrics = {
449
+ "precheck_s": round(precheck_time, 3),
450
+ "state_access_s": round(state_access_time, 3),
451
+ "model_resolve_s": round(model_resolve_time, 3),
452
+ "retrieval_s": round(retrieval_time, 3),
453
+ "inference_s": round(inference_time, 3),
454
+ "total_s": round(total_time, 3),
455
+ }
456
+
457
+ yield _to_ndjson(
458
+ {
459
+ "type": "done",
460
+ "model": model_name,
461
+ "answer": answer,
462
+ "metrics": metrics,
463
+ }
464
+ )
465
+ except Exception as exc:
466
+ yield _to_ndjson({"type": "error", "message": f"Streaming failed: {exc}"})
467
+
468
+ return StreamingResponse(
469
+ stream_events(),
470
+ media_type="application/x-ndjson",
471
+ headers={
472
+ "Cache-Control": "no-cache",
473
+ "X-Accel-Buffering": "no",
474
  },
475
  )
frontend/app/globals.css CHANGED
@@ -139,3 +139,142 @@
139
  .animate-gradient {
140
  animation: gradient 8s linear infinite;
141
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  .animate-gradient {
140
  animation: gradient 8s linear infinite;
141
  }
142
+
143
+ @keyframes streamCursorBlink {
144
+ 0%,
145
+ 45% {
146
+ opacity: 1;
147
+ }
148
+ 55%,
149
+ 100% {
150
+ opacity: 0;
151
+ }
152
+ }
153
+
154
+ @keyframes streamTextReveal {
155
+ from {
156
+ opacity: 0.65;
157
+ }
158
+ to {
159
+ opacity: 1;
160
+ }
161
+ }
162
+
163
+ .streaming-text-reveal {
164
+ animation: streamTextReveal 180ms ease-out;
165
+ }
166
+
167
+ .stream-cursor {
168
+ display: inline-block;
169
+ width: 0.55ch;
170
+ margin-left: 0.1ch;
171
+ border-bottom: 0.12em solid currentColor;
172
+ animation: streamCursorBlink 1s steps(1, end) infinite;
173
+ vertical-align: baseline;
174
+ }
175
+
176
+ .assistant-streaming-bubble {
177
+ background-image: linear-gradient(180deg, rgba(16, 185, 129, 0.06), rgba(16, 185, 129, 0));
178
+ }
179
+
180
+ .markdown-body {
181
+ line-height: 1.6;
182
+ }
183
+
184
+ .markdown-body > * + * {
185
+ margin-top: 0.65rem;
186
+ }
187
+
188
+ .markdown-body h1,
189
+ .markdown-body h2,
190
+ .markdown-body h3,
191
+ .markdown-body h4 {
192
+ font-weight: 700;
193
+ line-height: 1.35;
194
+ }
195
+
196
+ .markdown-body h1 {
197
+ font-size: 1.1rem;
198
+ }
199
+
200
+ .markdown-body h2 {
201
+ font-size: 1.02rem;
202
+ }
203
+
204
+ .markdown-body h3,
205
+ .markdown-body h4 {
206
+ font-size: 0.95rem;
207
+ }
208
+
209
+ .markdown-body p {
210
+ margin: 0;
211
+ }
212
+
213
+ .markdown-body ul,
214
+ .markdown-body ol {
215
+ margin-left: 1.1rem;
216
+ }
217
+
218
+ .markdown-body li + li {
219
+ margin-top: 0.2rem;
220
+ }
221
+
222
+ .markdown-body a {
223
+ text-decoration: underline;
224
+ text-underline-offset: 2px;
225
+ }
226
+
227
+ .markdown-body code {
228
+ border-radius: 0.35rem;
229
+ background: rgba(148, 163, 184, 0.2);
230
+ padding: 0.05rem 0.35rem;
231
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
232
+ font-size: 0.82em;
233
+ }
234
+
235
+ .markdown-body pre {
236
+ overflow-x: auto;
237
+ border-radius: 0.75rem;
238
+ border: 1px solid rgba(148, 163, 184, 0.3);
239
+ background: rgba(2, 6, 23, 0.92);
240
+ color: #e2e8f0;
241
+ padding: 0.75rem;
242
+ }
243
+
244
+ .markdown-body pre code {
245
+ background: transparent;
246
+ padding: 0;
247
+ color: inherit;
248
+ font-size: 0.78rem;
249
+ }
250
+
251
+ .markdown-body table {
252
+ width: 100%;
253
+ border-collapse: collapse;
254
+ font-size: 0.86rem;
255
+ }
256
+
257
+ .markdown-body th,
258
+ .markdown-body td {
259
+ border: 1px solid rgba(148, 163, 184, 0.35);
260
+ padding: 0.4rem 0.5rem;
261
+ text-align: left;
262
+ }
263
+
264
+ .markdown-body th {
265
+ background: rgba(148, 163, 184, 0.15);
266
+ font-weight: 600;
267
+ }
268
+
269
+ .markdown-body blockquote {
270
+ border-left: 3px solid rgba(148, 163, 184, 0.45);
271
+ margin: 0;
272
+ padding-left: 0.7rem;
273
+ color: inherit;
274
+ opacity: 0.9;
275
+ }
276
+
277
+ .markdown-body hr {
278
+ border: 0;
279
+ border-top: 1px solid rgba(148, 163, 184, 0.35);
280
+ }
frontend/components/AIAssistantUI.jsx CHANGED
@@ -84,6 +84,14 @@ export default function AIAssistantUI() {
84
 
85
  const [isThinking, setIsThinking] = useState(false)
86
  const [thinkingConvId, setThinkingConvId] = useState(null)
 
 
 
 
 
 
 
 
87
 
88
  useEffect(() => {
89
  const onKey = (e) => {
@@ -193,8 +201,13 @@ export default function AIAssistantUI() {
193
 
194
  async function sendMessage(convId, content) {
195
  if (!content.trim()) return
 
 
 
 
196
  const now = new Date().toISOString()
197
  const userMsg = { id: Math.random().toString(36).slice(2), role: "user", content, createdAt: now }
 
198
 
199
  setConversations((prev) =>
200
  prev.map((c) => {
@@ -213,7 +226,124 @@ export default function AIAssistantUI() {
213
  setIsThinking(true)
214
  setThinkingConvId(convId)
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  const currentConvId = convId
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
  // Prefer same-origin proxy to avoid browser CORS/network issues in development.
219
  const primaryUrl = "/api/proxy"
@@ -226,18 +356,19 @@ export default function AIAssistantUI() {
226
  query: content,
227
  model: selectedModel,
228
  }),
 
229
  }
230
 
231
  try {
232
  let res
233
 
234
  try {
235
- res = await fetch(`${primaryUrl}/predict`, fetchOptions)
236
  } catch {}
237
 
238
  if (!res || !res.ok) {
239
  // Retry direct backend URL if proxy is not reachable.
240
- res = await fetch(`${fallbackUrl}/predict`, fetchOptions)
241
  }
242
 
243
  if (!res.ok) {
@@ -245,49 +376,83 @@ export default function AIAssistantUI() {
245
  throw new Error(`Prediction failed (${res.status}) ${details}`.trim())
246
  }
247
 
248
- const data = await res.json()
 
 
249
 
250
- setConversations((prev) =>
251
- prev.map((c) => {
252
- if (c.id !== currentConvId) return c
253
- const asstMsg = {
254
- id: Math.random().toString(36).slice(2),
255
- role: "assistant",
256
- content: data.answer || "Sorry, I encountered an error.",
257
- createdAt: new Date().toISOString(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  }
259
- const msgs = [...(c.messages || []), asstMsg]
260
- return {
261
- ...c,
262
- messages: msgs,
263
- updatedAt: new Date().toISOString(),
264
- messageCount: msgs.length,
265
- preview: (asstMsg.content || "").slice(0, 80),
266
  }
267
- }),
268
- )
269
- } catch (err) {
270
- console.error("predict request failed:", err)
271
- setConversations((prev) =>
272
- prev.map((c) => {
273
- if (c.id !== currentConvId) return c
274
- const errorMsg = {
275
- id: Math.random().toString(36).slice(2),
276
- role: "assistant",
277
- content: "Sorry, I could not reach the backend. Start FastAPI and verify frontend .env.local URLs, then restart Next.js dev server.",
278
- createdAt: new Date().toISOString(),
279
  }
280
- const msgs = [...(c.messages || []), errorMsg]
281
- return {
282
- ...c,
283
- messages: msgs,
284
- updatedAt: new Date().toISOString(),
285
- messageCount: msgs.length,
286
- preview: errorMsg.content.slice(0, 80),
287
  }
288
- }),
289
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  } finally {
 
 
 
291
  setIsThinking(false)
292
  setThinkingConvId(null)
293
  }
@@ -318,6 +483,8 @@ export default function AIAssistantUI() {
318
  }
319
 
320
  function pauseThinking() {
 
 
321
  setIsThinking(false)
322
  setThinkingConvId(null)
323
  }
 
84
 
85
  const [isThinking, setIsThinking] = useState(false)
86
  const [thinkingConvId, setThinkingConvId] = useState(null)
87
+ const activeRequestRef = useRef(null)
88
+
89
+ useEffect(() => {
90
+ return () => {
91
+ activeRequestRef.current?.abort()
92
+ activeRequestRef.current = null
93
+ }
94
+ }, [])
95
 
96
  useEffect(() => {
97
  const onKey = (e) => {
 
201
 
202
  async function sendMessage(convId, content) {
203
  if (!content.trim()) return
204
+ const existingConversation = conversations.find((c) => c.id === convId)
205
+ const hasUserMessages = (existingConversation?.messages || []).some((m) => m.role === "user")
206
+ const shouldAutoTitle = existingConversation?.title === "New Chat" && !hasUserMessages
207
+
208
  const now = new Date().toISOString()
209
  const userMsg = { id: Math.random().toString(36).slice(2), role: "user", content, createdAt: now }
210
+ const assistantMsgId = Math.random().toString(36).slice(2)
211
 
212
  setConversations((prev) =>
213
  prev.map((c) => {
 
226
  setIsThinking(true)
227
  setThinkingConvId(convId)
228
 
229
+ if (shouldAutoTitle) {
230
+ const primaryUrl = "/api/proxy"
231
+ const fallbackUrl = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:8000"
232
+ const titlePayload = {
233
+ method: "POST",
234
+ headers: { "Content-Type": "application/json" },
235
+ body: JSON.stringify({ query: content }),
236
+ }
237
+
238
+ ;(async () => {
239
+ try {
240
+ let res
241
+ try {
242
+ res = await fetch(`${primaryUrl}/predict/title`, titlePayload)
243
+ } catch {}
244
+
245
+ if (!res || !res.ok) {
246
+ res = await fetch(`${fallbackUrl}/predict/title`, titlePayload)
247
+ }
248
+
249
+ if (!res.ok) return
250
+ const data = await res.json().catch(() => null)
251
+ const nextTitle = (data?.title || "").trim()
252
+ if (!nextTitle) return
253
+
254
+ setConversations((prev) =>
255
+ prev.map((c) => {
256
+ if (c.id !== convId) return c
257
+ if (c.title !== "New Chat") return c
258
+ return {
259
+ ...c,
260
+ title: nextTitle.slice(0, 80),
261
+ updatedAt: new Date().toISOString(),
262
+ }
263
+ }),
264
+ )
265
+ } catch {
266
+ // Keep default title if title endpoint is unavailable.
267
+ }
268
+ })()
269
+ }
270
+
271
  const currentConvId = convId
272
+ const controller = new AbortController()
273
+ activeRequestRef.current = controller
274
+
275
+ const upsertAssistantMessage = (updater, fallbackContent = "") => {
276
+ setConversations((prev) =>
277
+ prev.map((c) => {
278
+ if (c.id !== currentConvId) return c
279
+ const existingMessages = c.messages || []
280
+ const idx = existingMessages.findIndex((m) => m.id === assistantMsgId)
281
+ const baseMessage =
282
+ idx >= 0
283
+ ? existingMessages[idx]
284
+ : {
285
+ id: assistantMsgId,
286
+ role: "assistant",
287
+ content: fallbackContent,
288
+ createdAt: new Date().toISOString(),
289
+ isStreaming: true,
290
+ }
291
+
292
+ const nextMessage = updater(baseMessage)
293
+ const nextMessages = [...existingMessages]
294
+ if (idx >= 0) {
295
+ nextMessages[idx] = nextMessage
296
+ } else {
297
+ nextMessages.push(nextMessage)
298
+ }
299
+
300
+ return {
301
+ ...c,
302
+ messages: nextMessages,
303
+ updatedAt: new Date().toISOString(),
304
+ messageCount: nextMessages.length,
305
+ preview: nextMessages[nextMessages.length - 1]?.content?.slice(0, 80) || c.preview,
306
+ }
307
+ }),
308
+ )
309
+ }
310
+
311
+ const appendChunk = (chunk) => {
312
+ if (!chunk) return
313
+ upsertAssistantMessage(
314
+ (m) => ({
315
+ ...m,
316
+ content: (m.content || "") + chunk,
317
+ isStreaming: true,
318
+ }),
319
+ "",
320
+ )
321
+ }
322
+
323
+ const finalizeAssistant = (finalText) => {
324
+ upsertAssistantMessage(
325
+ (m) => {
326
+ const fallbackContent = m.content || "Sorry, I encountered an error."
327
+ return {
328
+ ...m,
329
+ content: finalText != null ? finalText : fallbackContent,
330
+ isStreaming: false,
331
+ }
332
+ },
333
+ finalText || "Sorry, I encountered an error.",
334
+ )
335
+ }
336
+
337
+ const setAssistantError = (message) => {
338
+ upsertAssistantMessage(
339
+ (m) => ({
340
+ ...m,
341
+ content: message,
342
+ isStreaming: false,
343
+ }),
344
+ message,
345
+ )
346
+ }
347
 
348
  // Prefer same-origin proxy to avoid browser CORS/network issues in development.
349
  const primaryUrl = "/api/proxy"
 
356
  query: content,
357
  model: selectedModel,
358
  }),
359
+ signal: controller.signal,
360
  }
361
 
362
  try {
363
  let res
364
 
365
  try {
366
+ res = await fetch(`${primaryUrl}/predict/stream`, fetchOptions)
367
  } catch {}
368
 
369
  if (!res || !res.ok) {
370
  // Retry direct backend URL if proxy is not reachable.
371
+ res = await fetch(`${fallbackUrl}/predict/stream`, fetchOptions)
372
  }
373
 
374
  if (!res.ok) {
 
376
  throw new Error(`Prediction failed (${res.status}) ${details}`.trim())
377
  }
378
 
379
+ if (!res.body) {
380
+ throw new Error("Streaming is not available in this browser response")
381
+ }
382
 
383
+ const reader = res.body.getReader()
384
+ const decoder = new TextDecoder()
385
+ let buffer = ""
386
+ let firstTokenReceived = false
387
+ let finalAnswer = null
388
+
389
+ while (true) {
390
+ const { value, done } = await reader.read()
391
+ if (done) break
392
+
393
+ buffer += decoder.decode(value, { stream: true })
394
+ const lines = buffer.split("\n")
395
+ buffer = lines.pop() || ""
396
+
397
+ for (const line of lines) {
398
+ const trimmed = line.trim()
399
+ if (!trimmed) continue
400
+
401
+ let evt
402
+ try {
403
+ evt = JSON.parse(trimmed)
404
+ } catch {
405
+ continue
406
  }
407
+
408
+ if (evt.type === "token") {
409
+ if (!firstTokenReceived) {
410
+ firstTokenReceived = true
411
+ setIsThinking(false)
412
+ }
413
+ appendChunk(evt.token || "")
414
  }
415
+
416
+ if (evt.type === "done") {
417
+ finalAnswer = typeof evt.answer === "string" ? evt.answer : null
 
 
 
 
 
 
 
 
 
418
  }
419
+
420
+ if (evt.type === "error") {
421
+ throw new Error(evt.message || "Streaming failed")
 
 
 
 
422
  }
423
+ }
424
+ }
425
+
426
+ const remainder = buffer.trim()
427
+ if (remainder) {
428
+ try {
429
+ const evt = JSON.parse(remainder)
430
+ if (evt.type === "done") {
431
+ finalAnswer = typeof evt.answer === "string" ? evt.answer : null
432
+ }
433
+ if (evt.type === "token") {
434
+ appendChunk(evt.token || "")
435
+ }
436
+ if (evt.type === "error") {
437
+ throw new Error(evt.message || "Streaming failed")
438
+ }
439
+ } catch {
440
+ // Ignore malformed tail chunk.
441
+ }
442
+ }
443
+
444
+ finalizeAssistant(finalAnswer)
445
+ } catch (err) {
446
+ console.error("predict request failed:", err)
447
+ if (err?.name === "AbortError") {
448
+ setAssistantError("Generation paused.")
449
+ } else {
450
+ setAssistantError("Sorry, I could not reach the backend. Start FastAPI and verify frontend .env.local URLs, then restart Next.js dev server.")
451
+ }
452
  } finally {
453
+ if (activeRequestRef.current === controller) {
454
+ activeRequestRef.current = null
455
+ }
456
  setIsThinking(false)
457
  setThinkingConvId(null)
458
  }
 
483
  }
484
 
485
  function pauseThinking() {
486
+ activeRequestRef.current?.abort()
487
+ activeRequestRef.current = null
488
  setIsThinking(false)
489
  setThinkingConvId(null)
490
  }
frontend/components/ChatPane.jsx CHANGED
@@ -3,6 +3,7 @@
3
  import { useState, forwardRef, useImperativeHandle, useRef } from "react"
4
  import { RefreshCw, Check, X, Square } from "lucide-react"
5
  import Message from "./Message"
 
6
  import Composer from "./Composer"
7
  import { cls, timeAgo } from "./utils"
8
 
@@ -171,8 +172,14 @@ const ChatPane = forwardRef(function ChatPane(
171
  </div>
172
  </div>
173
  ) : (
174
- <Message role={m.role}>
175
- <div className="whitespace-pre-wrap">{m.content}</div>
 
 
 
 
 
 
176
  </Message>
177
  )}
178
  </div>
 
3
  import { useState, forwardRef, useImperativeHandle, useRef } from "react"
4
  import { RefreshCw, Check, X, Square } from "lucide-react"
5
  import Message from "./Message"
6
+ import MarkdownMessage from "./MarkdownMessage"
7
  import Composer from "./Composer"
8
  import { cls, timeAgo } from "./utils"
9
 
 
172
  </div>
173
  </div>
174
  ) : (
175
+ <Message role={m.role} streaming={Boolean(m.isStreaming)}>
176
+ {m.role === "assistant" ? (
177
+ <div className={cls(m.isStreaming && "streaming-text-reveal")}>
178
+ <MarkdownMessage content={m.content} isStreaming={Boolean(m.isStreaming)} />
179
+ </div>
180
+ ) : (
181
+ <div className="whitespace-pre-wrap">{m.content}</div>
182
+ )}
183
  </Message>
184
  )}
185
  </div>
frontend/components/MarkdownMessage.jsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import ReactMarkdown from "react-markdown"
4
+ import remarkGfm from "remark-gfm"
5
+ import rehypeSanitize from "rehype-sanitize"
6
+
7
+ function withTemporaryFenceClosure(markdown) {
8
+ const text = markdown || ""
9
+ const fenceMatches = text.match(/```/g)
10
+ const fenceCount = fenceMatches ? fenceMatches.length : 0
11
+ if (fenceCount % 2 === 1) {
12
+ return `${text}\n\n\`\`\`\n`
13
+ }
14
+ return text
15
+ }
16
+
17
+ export default function MarkdownMessage({ content, isStreaming = false }) {
18
+ const source = isStreaming ? withTemporaryFenceClosure(content) : content || ""
19
+
20
+ return (
21
+ <div className="markdown-body">
22
+ <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSanitize]}>
23
+ {source}
24
+ </ReactMarkdown>
25
+ </div>
26
+ )
27
+ }
frontend/components/Message.jsx CHANGED
@@ -1,6 +1,6 @@
1
  import { cls } from "./utils"
2
 
3
- export default function Message({ role, children }) {
4
  const isUser = role === "user"
5
  return (
6
  <div className={cls("flex gap-3", isUser ? "justify-end" : "justify-start")}>
@@ -17,6 +17,7 @@ export default function Message({ role, children }) {
17
  isUser
18
  ? "bg-zinc-900 text-white dark:bg-white dark:text-zinc-900"
19
  : "bg-white text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100 border border-zinc-200 dark:border-zinc-800",
 
20
  )}
21
  >
22
  {children}
 
1
  import { cls } from "./utils"
2
 
3
+ export default function Message({ role, children, streaming = false }) {
4
  const isUser = role === "user"
5
  return (
6
  <div className={cls("flex gap-3", isUser ? "justify-end" : "justify-start")}>
 
17
  isUser
18
  ? "bg-zinc-900 text-white dark:bg-white dark:text-zinc-900"
19
  : "bg-white text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100 border border-zinc-200 dark:border-zinc-800",
20
+ !isUser && streaming && "assistant-streaming-bubble",
21
  )}
22
  >
23
  {children}
frontend/package-lock.json CHANGED
@@ -21,7 +21,10 @@
21
  "react-dom": "^19.2.1",
22
  "react-dropzone": "^14.3.8",
23
  "react-icons": "^5.5.0",
 
24
  "react-use-measure": "^2.1.7",
 
 
25
  "tailwind-merge": "^3.3.1"
26
  },
27
  "devDependencies": {
@@ -3643,13 +3646,39 @@
3643
  "tslib": "^2.4.0"
3644
  }
3645
  },
 
 
 
 
 
 
 
 
 
3646
  "node_modules/@types/estree": {
3647
  "version": "1.0.8",
3648
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
3649
  "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
3650
- "dev": true,
3651
  "license": "MIT"
3652
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3653
  "node_modules/@types/json-schema": {
3654
  "version": "7.0.15",
3655
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -3664,6 +3693,21 @@
3664
  "dev": true,
3665
  "license": "MIT"
3666
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3667
  "node_modules/@types/node": {
3668
  "version": "20.19.24",
3669
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz",
@@ -3678,7 +3722,6 @@
3678
  "version": "19.2.2",
3679
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
3680
  "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
3681
- "devOptional": true,
3682
  "license": "MIT",
3683
  "dependencies": {
3684
  "csstype": "^3.0.2"
@@ -3694,6 +3737,12 @@
3694
  "@types/react": "^19.2.0"
3695
  }
3696
  },
 
 
 
 
 
 
3697
  "node_modules/@typescript-eslint/eslint-plugin": {
3698
  "version": "8.46.3",
3699
  "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz",
@@ -3995,6 +4044,12 @@
3995
  "url": "https://opencollective.com/typescript-eslint"
3996
  }
3997
  },
 
 
 
 
 
 
3998
  "node_modules/@unrs/resolver-binding-android-arm-eabi": {
3999
  "version": "1.11.1",
4000
  "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
@@ -4609,6 +4664,16 @@
4609
  "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
4610
  }
4611
  },
 
 
 
 
 
 
 
 
 
 
4612
  "node_modules/balanced-match": {
4613
  "version": "1.0.2",
4614
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -4779,6 +4844,16 @@
4779
  ],
4780
  "license": "CC-BY-4.0"
4781
  },
 
 
 
 
 
 
 
 
 
 
4782
  "node_modules/chalk": {
4783
  "version": "4.1.2",
4784
  "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -4796,6 +4871,46 @@
4796
  "url": "https://github.com/chalk/chalk?sponsor=1"
4797
  }
4798
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4799
  "node_modules/class-variance-authority": {
4800
  "version": "0.7.1",
4801
  "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
@@ -4843,6 +4958,16 @@
4843
  "dev": true,
4844
  "license": "MIT"
4845
  },
 
 
 
 
 
 
 
 
 
 
4846
  "node_modules/commander": {
4847
  "version": "7.2.0",
4848
  "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
@@ -4997,7 +5122,6 @@
4997
  "version": "3.1.3",
4998
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
4999
  "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
5000
- "devOptional": true,
5001
  "license": "MIT"
5002
  },
5003
  "node_modules/damerau-levenshtein": {
@@ -5078,6 +5202,19 @@
5078
  }
5079
  }
5080
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
5081
  "node_modules/deep-is": {
5082
  "version": "0.1.4",
5083
  "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -5130,6 +5267,15 @@
5130
  "url": "https://github.com/sponsors/ljharb"
5131
  }
5132
  },
 
 
 
 
 
 
 
 
 
5133
  "node_modules/detect-libc": {
5134
  "version": "2.1.2",
5135
  "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -5146,6 +5292,19 @@
5146
  "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
5147
  "license": "MIT"
5148
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
5149
  "node_modules/doctrine": {
5150
  "version": "2.1.0",
5151
  "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -5900,6 +6059,16 @@
5900
  "node": ">=4.0"
5901
  }
5902
  },
 
 
 
 
 
 
 
 
 
 
5903
  "node_modules/esutils": {
5904
  "version": "2.0.3",
5905
  "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -5909,6 +6078,12 @@
5909
  "node": ">=0.10.0"
5910
  }
5911
  },
 
 
 
 
 
 
5912
  "node_modules/fast-deep-equal": {
5913
  "version": "3.1.3",
5914
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -6390,6 +6565,61 @@
6390
  "node": ">= 0.4"
6391
  }
6392
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6393
  "node_modules/hermes-estree": {
6394
  "version": "0.25.1",
6395
  "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
@@ -6407,6 +6637,16 @@
6407
  "hermes-estree": "0.25.1"
6408
  }
6409
  },
 
 
 
 
 
 
 
 
 
 
6410
  "node_modules/ignore": {
6411
  "version": "5.3.2",
6412
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -6443,6 +6683,12 @@
6443
  "node": ">=0.8.19"
6444
  }
6445
  },
 
 
 
 
 
 
6446
  "node_modules/internal-slot": {
6447
  "version": "1.1.0",
6448
  "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -6458,6 +6704,30 @@
6458
  "node": ">= 0.4"
6459
  }
6460
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6461
  "node_modules/is-array-buffer": {
6462
  "version": "3.0.5",
6463
  "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -6621,6 +6891,16 @@
6621
  "url": "https://github.com/sponsors/ljharb"
6622
  }
6623
  },
 
 
 
 
 
 
 
 
 
 
6624
  "node_modules/is-extglob": {
6625
  "version": "2.1.1",
6626
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -6680,6 +6960,16 @@
6680
  "node": ">=0.10.0"
6681
  }
6682
  },
 
 
 
 
 
 
 
 
 
 
6683
  "node_modules/is-map": {
6684
  "version": "2.0.3",
6685
  "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
@@ -6733,6 +7023,18 @@
6733
  "url": "https://github.com/sponsors/ljharb"
6734
  }
6735
  },
 
 
 
 
 
 
 
 
 
 
 
 
6736
  "node_modules/is-regex": {
6737
  "version": "1.2.1",
6738
  "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -7345,6 +7647,16 @@
7345
  "dev": true,
7346
  "license": "MIT"
7347
  },
 
 
 
 
 
 
 
 
 
 
7348
  "node_modules/loose-envify": {
7349
  "version": "1.4.0",
7350
  "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -7394,6 +7706,16 @@
7394
  "@jridgewell/sourcemap-codec": "^1.5.5"
7395
  }
7396
  },
 
 
 
 
 
 
 
 
 
 
7397
  "node_modules/math-intrinsics": {
7398
  "version": "1.1.0",
7399
  "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -7404,76 +7726,921 @@
7404
  "node": ">= 0.4"
7405
  }
7406
  },
7407
- "node_modules/mdn-data": {
7408
- "version": "2.0.30",
7409
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
7410
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
7411
- "license": "CC0-1.0"
 
 
 
 
 
 
 
 
 
 
7412
  },
7413
- "node_modules/merge2": {
7414
- "version": "1.4.1",
7415
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
7416
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
7417
- "dev": true,
7418
  "license": "MIT",
7419
  "engines": {
7420
- "node": ">= 8"
 
 
 
7421
  }
7422
  },
7423
- "node_modules/micromatch": {
7424
- "version": "4.0.8",
7425
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
7426
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
7427
- "dev": true,
7428
  "license": "MIT",
7429
  "dependencies": {
7430
- "braces": "^3.0.3",
7431
- "picomatch": "^2.3.1"
 
 
 
 
 
 
 
 
 
 
7432
  },
7433
- "engines": {
7434
- "node": ">=8.6"
 
7435
  }
7436
  },
7437
- "node_modules/minimatch": {
7438
- "version": "3.1.2",
7439
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
7440
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
7441
- "dev": true,
7442
- "license": "ISC",
7443
  "dependencies": {
7444
- "brace-expansion": "^1.1.7"
 
 
 
 
 
 
7445
  },
7446
- "engines": {
7447
- "node": "*"
 
7448
  }
7449
  },
7450
- "node_modules/minimist": {
7451
- "version": "1.2.8",
7452
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
7453
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
7454
- "dev": true,
7455
  "license": "MIT",
 
 
 
 
 
 
 
7456
  "funding": {
7457
- "url": "https://github.com/sponsors/ljharb"
 
7458
  }
7459
  },
7460
- "node_modules/motion": {
7461
- "version": "12.23.24",
7462
- "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.24.tgz",
7463
- "integrity": "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==",
7464
  "license": "MIT",
7465
  "dependencies": {
7466
- "framer-motion": "^12.23.24",
7467
- "tslib": "^2.4.0"
 
 
 
7468
  },
7469
- "peerDependencies": {
7470
- "@emotion/is-prop-valid": "*",
7471
- "react": "^18.0.0 || ^19.0.0",
7472
- "react-dom": "^18.0.0 || ^19.0.0"
 
 
 
 
 
 
 
 
 
 
7473
  },
7474
- "peerDependenciesMeta": {
7475
- "@emotion/is-prop-valid": {
7476
- "optional": true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7477
  },
7478
  "react": {
7479
  "optional": true
@@ -7861,6 +9028,31 @@
7861
  "node": ">=6"
7862
  }
7863
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7864
  "node_modules/parse-json": {
7865
  "version": "5.2.0",
7866
  "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -7993,6 +9185,16 @@
7993
  "react-is": "^16.13.1"
7994
  }
7995
  },
 
 
 
 
 
 
 
 
 
 
7996
  "node_modules/punycode": {
7997
  "version": "2.3.1",
7998
  "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -8077,6 +9279,33 @@
8077
  "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
8078
  "license": "MIT"
8079
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8080
  "node_modules/react-remove-scroll": {
8081
  "version": "2.7.1",
8082
  "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
@@ -8258,6 +9487,86 @@
8258
  "regjsparser": "bin/parser"
8259
  }
8260
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8261
  "node_modules/resolve": {
8262
  "version": "1.22.11",
8263
  "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -8627,6 +9936,16 @@
8627
  "node": ">=0.10.0"
8628
  }
8629
  },
 
 
 
 
 
 
 
 
 
 
8630
  "node_modules/stable-hash": {
8631
  "version": "0.0.5",
8632
  "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
@@ -8761,6 +10080,20 @@
8761
  "url": "https://github.com/sponsors/ljharb"
8762
  }
8763
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8764
  "node_modules/strip-bom": {
8765
  "version": "3.0.0",
8766
  "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -8784,6 +10117,24 @@
8784
  "url": "https://github.com/sponsors/sindresorhus"
8785
  }
8786
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8787
  "node_modules/styled-jsx": {
8788
  "version": "5.1.6",
8789
  "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
@@ -8955,6 +10306,26 @@
8955
  "node": ">=8.0"
8956
  }
8957
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8958
  "node_modules/ts-api-utils": {
8959
  "version": "2.1.0",
8960
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -9195,6 +10566,93 @@
9195
  "node": ">=4"
9196
  }
9197
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9198
  "node_modules/unrs-resolver": {
9199
  "version": "1.11.1",
9200
  "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
@@ -9313,6 +10771,34 @@
9313
  }
9314
  }
9315
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9316
  "node_modules/which": {
9317
  "version": "2.0.2",
9318
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -9469,6 +10955,16 @@
9469
  "peerDependencies": {
9470
  "zod": "^3.25.0 || ^4.0.0"
9471
  }
 
 
 
 
 
 
 
 
 
 
9472
  }
9473
  }
9474
  }
 
21
  "react-dom": "^19.2.1",
22
  "react-dropzone": "^14.3.8",
23
  "react-icons": "^5.5.0",
24
+ "react-markdown": "^10.1.0",
25
  "react-use-measure": "^2.1.7",
26
+ "rehype-sanitize": "^6.0.0",
27
+ "remark-gfm": "^4.0.1",
28
  "tailwind-merge": "^3.3.1"
29
  },
30
  "devDependencies": {
 
3646
  "tslib": "^2.4.0"
3647
  }
3648
  },
3649
+ "node_modules/@types/debug": {
3650
+ "version": "4.1.13",
3651
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
3652
+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
3653
+ "license": "MIT",
3654
+ "dependencies": {
3655
+ "@types/ms": "*"
3656
+ }
3657
+ },
3658
  "node_modules/@types/estree": {
3659
  "version": "1.0.8",
3660
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
3661
  "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
 
3662
  "license": "MIT"
3663
  },
3664
+ "node_modules/@types/estree-jsx": {
3665
+ "version": "1.0.5",
3666
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
3667
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
3668
+ "license": "MIT",
3669
+ "dependencies": {
3670
+ "@types/estree": "*"
3671
+ }
3672
+ },
3673
+ "node_modules/@types/hast": {
3674
+ "version": "3.0.4",
3675
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
3676
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
3677
+ "license": "MIT",
3678
+ "dependencies": {
3679
+ "@types/unist": "*"
3680
+ }
3681
+ },
3682
  "node_modules/@types/json-schema": {
3683
  "version": "7.0.15",
3684
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
 
3693
  "dev": true,
3694
  "license": "MIT"
3695
  },
3696
+ "node_modules/@types/mdast": {
3697
+ "version": "4.0.4",
3698
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
3699
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
3700
+ "license": "MIT",
3701
+ "dependencies": {
3702
+ "@types/unist": "*"
3703
+ }
3704
+ },
3705
+ "node_modules/@types/ms": {
3706
+ "version": "2.1.0",
3707
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
3708
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
3709
+ "license": "MIT"
3710
+ },
3711
  "node_modules/@types/node": {
3712
  "version": "20.19.24",
3713
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz",
 
3722
  "version": "19.2.2",
3723
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
3724
  "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
 
3725
  "license": "MIT",
3726
  "dependencies": {
3727
  "csstype": "^3.0.2"
 
3737
  "@types/react": "^19.2.0"
3738
  }
3739
  },
3740
+ "node_modules/@types/unist": {
3741
+ "version": "3.0.3",
3742
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
3743
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
3744
+ "license": "MIT"
3745
+ },
3746
  "node_modules/@typescript-eslint/eslint-plugin": {
3747
  "version": "8.46.3",
3748
  "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz",
 
4044
  "url": "https://opencollective.com/typescript-eslint"
4045
  }
4046
  },
4047
+ "node_modules/@ungap/structured-clone": {
4048
+ "version": "1.3.0",
4049
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
4050
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
4051
+ "license": "ISC"
4052
+ },
4053
  "node_modules/@unrs/resolver-binding-android-arm-eabi": {
4054
  "version": "1.11.1",
4055
  "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
 
4664
  "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
4665
  }
4666
  },
4667
+ "node_modules/bail": {
4668
+ "version": "2.0.2",
4669
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
4670
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
4671
+ "license": "MIT",
4672
+ "funding": {
4673
+ "type": "github",
4674
+ "url": "https://github.com/sponsors/wooorm"
4675
+ }
4676
+ },
4677
  "node_modules/balanced-match": {
4678
  "version": "1.0.2",
4679
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
 
4844
  ],
4845
  "license": "CC-BY-4.0"
4846
  },
4847
+ "node_modules/ccount": {
4848
+ "version": "2.0.1",
4849
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
4850
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
4851
+ "license": "MIT",
4852
+ "funding": {
4853
+ "type": "github",
4854
+ "url": "https://github.com/sponsors/wooorm"
4855
+ }
4856
+ },
4857
  "node_modules/chalk": {
4858
  "version": "4.1.2",
4859
  "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 
4871
  "url": "https://github.com/chalk/chalk?sponsor=1"
4872
  }
4873
  },
4874
+ "node_modules/character-entities": {
4875
+ "version": "2.0.2",
4876
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
4877
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
4878
+ "license": "MIT",
4879
+ "funding": {
4880
+ "type": "github",
4881
+ "url": "https://github.com/sponsors/wooorm"
4882
+ }
4883
+ },
4884
+ "node_modules/character-entities-html4": {
4885
+ "version": "2.1.0",
4886
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
4887
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
4888
+ "license": "MIT",
4889
+ "funding": {
4890
+ "type": "github",
4891
+ "url": "https://github.com/sponsors/wooorm"
4892
+ }
4893
+ },
4894
+ "node_modules/character-entities-legacy": {
4895
+ "version": "3.0.0",
4896
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
4897
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
4898
+ "license": "MIT",
4899
+ "funding": {
4900
+ "type": "github",
4901
+ "url": "https://github.com/sponsors/wooorm"
4902
+ }
4903
+ },
4904
+ "node_modules/character-reference-invalid": {
4905
+ "version": "2.0.1",
4906
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
4907
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
4908
+ "license": "MIT",
4909
+ "funding": {
4910
+ "type": "github",
4911
+ "url": "https://github.com/sponsors/wooorm"
4912
+ }
4913
+ },
4914
  "node_modules/class-variance-authority": {
4915
  "version": "0.7.1",
4916
  "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
 
4958
  "dev": true,
4959
  "license": "MIT"
4960
  },
4961
+ "node_modules/comma-separated-tokens": {
4962
+ "version": "2.0.3",
4963
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
4964
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
4965
+ "license": "MIT",
4966
+ "funding": {
4967
+ "type": "github",
4968
+ "url": "https://github.com/sponsors/wooorm"
4969
+ }
4970
+ },
4971
  "node_modules/commander": {
4972
  "version": "7.2.0",
4973
  "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
 
5122
  "version": "3.1.3",
5123
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
5124
  "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
 
5125
  "license": "MIT"
5126
  },
5127
  "node_modules/damerau-levenshtein": {
 
5202
  }
5203
  }
5204
  },
5205
+ "node_modules/decode-named-character-reference": {
5206
+ "version": "1.3.0",
5207
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
5208
+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
5209
+ "license": "MIT",
5210
+ "dependencies": {
5211
+ "character-entities": "^2.0.0"
5212
+ },
5213
+ "funding": {
5214
+ "type": "github",
5215
+ "url": "https://github.com/sponsors/wooorm"
5216
+ }
5217
+ },
5218
  "node_modules/deep-is": {
5219
  "version": "0.1.4",
5220
  "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
 
5267
  "url": "https://github.com/sponsors/ljharb"
5268
  }
5269
  },
5270
+ "node_modules/dequal": {
5271
+ "version": "2.0.3",
5272
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
5273
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
5274
+ "license": "MIT",
5275
+ "engines": {
5276
+ "node": ">=6"
5277
+ }
5278
+ },
5279
  "node_modules/detect-libc": {
5280
  "version": "2.1.2",
5281
  "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
 
5292
  "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
5293
  "license": "MIT"
5294
  },
5295
+ "node_modules/devlop": {
5296
+ "version": "1.1.0",
5297
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
5298
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
5299
+ "license": "MIT",
5300
+ "dependencies": {
5301
+ "dequal": "^2.0.0"
5302
+ },
5303
+ "funding": {
5304
+ "type": "github",
5305
+ "url": "https://github.com/sponsors/wooorm"
5306
+ }
5307
+ },
5308
  "node_modules/doctrine": {
5309
  "version": "2.1.0",
5310
  "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
 
6059
  "node": ">=4.0"
6060
  }
6061
  },
6062
+ "node_modules/estree-util-is-identifier-name": {
6063
+ "version": "3.0.0",
6064
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
6065
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
6066
+ "license": "MIT",
6067
+ "funding": {
6068
+ "type": "opencollective",
6069
+ "url": "https://opencollective.com/unified"
6070
+ }
6071
+ },
6072
  "node_modules/esutils": {
6073
  "version": "2.0.3",
6074
  "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
 
6078
  "node": ">=0.10.0"
6079
  }
6080
  },
6081
+ "node_modules/extend": {
6082
+ "version": "3.0.2",
6083
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
6084
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
6085
+ "license": "MIT"
6086
+ },
6087
  "node_modules/fast-deep-equal": {
6088
  "version": "3.1.3",
6089
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 
6565
  "node": ">= 0.4"
6566
  }
6567
  },
6568
+ "node_modules/hast-util-sanitize": {
6569
+ "version": "5.0.2",
6570
+ "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz",
6571
+ "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==",
6572
+ "license": "MIT",
6573
+ "dependencies": {
6574
+ "@types/hast": "^3.0.0",
6575
+ "@ungap/structured-clone": "^1.0.0",
6576
+ "unist-util-position": "^5.0.0"
6577
+ },
6578
+ "funding": {
6579
+ "type": "opencollective",
6580
+ "url": "https://opencollective.com/unified"
6581
+ }
6582
+ },
6583
+ "node_modules/hast-util-to-jsx-runtime": {
6584
+ "version": "2.3.6",
6585
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
6586
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
6587
+ "license": "MIT",
6588
+ "dependencies": {
6589
+ "@types/estree": "^1.0.0",
6590
+ "@types/hast": "^3.0.0",
6591
+ "@types/unist": "^3.0.0",
6592
+ "comma-separated-tokens": "^2.0.0",
6593
+ "devlop": "^1.0.0",
6594
+ "estree-util-is-identifier-name": "^3.0.0",
6595
+ "hast-util-whitespace": "^3.0.0",
6596
+ "mdast-util-mdx-expression": "^2.0.0",
6597
+ "mdast-util-mdx-jsx": "^3.0.0",
6598
+ "mdast-util-mdxjs-esm": "^2.0.0",
6599
+ "property-information": "^7.0.0",
6600
+ "space-separated-tokens": "^2.0.0",
6601
+ "style-to-js": "^1.0.0",
6602
+ "unist-util-position": "^5.0.0",
6603
+ "vfile-message": "^4.0.0"
6604
+ },
6605
+ "funding": {
6606
+ "type": "opencollective",
6607
+ "url": "https://opencollective.com/unified"
6608
+ }
6609
+ },
6610
+ "node_modules/hast-util-whitespace": {
6611
+ "version": "3.0.0",
6612
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
6613
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
6614
+ "license": "MIT",
6615
+ "dependencies": {
6616
+ "@types/hast": "^3.0.0"
6617
+ },
6618
+ "funding": {
6619
+ "type": "opencollective",
6620
+ "url": "https://opencollective.com/unified"
6621
+ }
6622
+ },
6623
  "node_modules/hermes-estree": {
6624
  "version": "0.25.1",
6625
  "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
 
6637
  "hermes-estree": "0.25.1"
6638
  }
6639
  },
6640
+ "node_modules/html-url-attributes": {
6641
+ "version": "3.0.1",
6642
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
6643
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
6644
+ "license": "MIT",
6645
+ "funding": {
6646
+ "type": "opencollective",
6647
+ "url": "https://opencollective.com/unified"
6648
+ }
6649
+ },
6650
  "node_modules/ignore": {
6651
  "version": "5.3.2",
6652
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
 
6683
  "node": ">=0.8.19"
6684
  }
6685
  },
6686
+ "node_modules/inline-style-parser": {
6687
+ "version": "0.2.7",
6688
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
6689
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
6690
+ "license": "MIT"
6691
+ },
6692
  "node_modules/internal-slot": {
6693
  "version": "1.1.0",
6694
  "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
 
6704
  "node": ">= 0.4"
6705
  }
6706
  },
6707
+ "node_modules/is-alphabetical": {
6708
+ "version": "2.0.1",
6709
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
6710
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
6711
+ "license": "MIT",
6712
+ "funding": {
6713
+ "type": "github",
6714
+ "url": "https://github.com/sponsors/wooorm"
6715
+ }
6716
+ },
6717
+ "node_modules/is-alphanumerical": {
6718
+ "version": "2.0.1",
6719
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
6720
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
6721
+ "license": "MIT",
6722
+ "dependencies": {
6723
+ "is-alphabetical": "^2.0.0",
6724
+ "is-decimal": "^2.0.0"
6725
+ },
6726
+ "funding": {
6727
+ "type": "github",
6728
+ "url": "https://github.com/sponsors/wooorm"
6729
+ }
6730
+ },
6731
  "node_modules/is-array-buffer": {
6732
  "version": "3.0.5",
6733
  "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
 
6891
  "url": "https://github.com/sponsors/ljharb"
6892
  }
6893
  },
6894
+ "node_modules/is-decimal": {
6895
+ "version": "2.0.1",
6896
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
6897
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
6898
+ "license": "MIT",
6899
+ "funding": {
6900
+ "type": "github",
6901
+ "url": "https://github.com/sponsors/wooorm"
6902
+ }
6903
+ },
6904
  "node_modules/is-extglob": {
6905
  "version": "2.1.1",
6906
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
 
6960
  "node": ">=0.10.0"
6961
  }
6962
  },
6963
+ "node_modules/is-hexadecimal": {
6964
+ "version": "2.0.1",
6965
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
6966
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
6967
+ "license": "MIT",
6968
+ "funding": {
6969
+ "type": "github",
6970
+ "url": "https://github.com/sponsors/wooorm"
6971
+ }
6972
+ },
6973
  "node_modules/is-map": {
6974
  "version": "2.0.3",
6975
  "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
 
7023
  "url": "https://github.com/sponsors/ljharb"
7024
  }
7025
  },
7026
+ "node_modules/is-plain-obj": {
7027
+ "version": "4.1.0",
7028
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
7029
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
7030
+ "license": "MIT",
7031
+ "engines": {
7032
+ "node": ">=12"
7033
+ },
7034
+ "funding": {
7035
+ "url": "https://github.com/sponsors/sindresorhus"
7036
+ }
7037
+ },
7038
  "node_modules/is-regex": {
7039
  "version": "1.2.1",
7040
  "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
 
7647
  "dev": true,
7648
  "license": "MIT"
7649
  },
7650
+ "node_modules/longest-streak": {
7651
+ "version": "3.1.0",
7652
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
7653
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
7654
+ "license": "MIT",
7655
+ "funding": {
7656
+ "type": "github",
7657
+ "url": "https://github.com/sponsors/wooorm"
7658
+ }
7659
+ },
7660
  "node_modules/loose-envify": {
7661
  "version": "1.4.0",
7662
  "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
 
7706
  "@jridgewell/sourcemap-codec": "^1.5.5"
7707
  }
7708
  },
7709
+ "node_modules/markdown-table": {
7710
+ "version": "3.0.4",
7711
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
7712
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
7713
+ "license": "MIT",
7714
+ "funding": {
7715
+ "type": "github",
7716
+ "url": "https://github.com/sponsors/wooorm"
7717
+ }
7718
+ },
7719
  "node_modules/math-intrinsics": {
7720
  "version": "1.1.0",
7721
  "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
 
7726
  "node": ">= 0.4"
7727
  }
7728
  },
7729
+ "node_modules/mdast-util-find-and-replace": {
7730
+ "version": "3.0.2",
7731
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
7732
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
7733
+ "license": "MIT",
7734
+ "dependencies": {
7735
+ "@types/mdast": "^4.0.0",
7736
+ "escape-string-regexp": "^5.0.0",
7737
+ "unist-util-is": "^6.0.0",
7738
+ "unist-util-visit-parents": "^6.0.0"
7739
+ },
7740
+ "funding": {
7741
+ "type": "opencollective",
7742
+ "url": "https://opencollective.com/unified"
7743
+ }
7744
  },
7745
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
7746
+ "version": "5.0.0",
7747
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
7748
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
 
7749
  "license": "MIT",
7750
  "engines": {
7751
+ "node": ">=12"
7752
+ },
7753
+ "funding": {
7754
+ "url": "https://github.com/sponsors/sindresorhus"
7755
  }
7756
  },
7757
+ "node_modules/mdast-util-from-markdown": {
7758
+ "version": "2.0.3",
7759
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
7760
+ "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
 
7761
  "license": "MIT",
7762
  "dependencies": {
7763
+ "@types/mdast": "^4.0.0",
7764
+ "@types/unist": "^3.0.0",
7765
+ "decode-named-character-reference": "^1.0.0",
7766
+ "devlop": "^1.0.0",
7767
+ "mdast-util-to-string": "^4.0.0",
7768
+ "micromark": "^4.0.0",
7769
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
7770
+ "micromark-util-decode-string": "^2.0.0",
7771
+ "micromark-util-normalize-identifier": "^2.0.0",
7772
+ "micromark-util-symbol": "^2.0.0",
7773
+ "micromark-util-types": "^2.0.0",
7774
+ "unist-util-stringify-position": "^4.0.0"
7775
  },
7776
+ "funding": {
7777
+ "type": "opencollective",
7778
+ "url": "https://opencollective.com/unified"
7779
  }
7780
  },
7781
+ "node_modules/mdast-util-gfm": {
7782
+ "version": "3.1.0",
7783
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
7784
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
7785
+ "license": "MIT",
 
7786
  "dependencies": {
7787
+ "mdast-util-from-markdown": "^2.0.0",
7788
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
7789
+ "mdast-util-gfm-footnote": "^2.0.0",
7790
+ "mdast-util-gfm-strikethrough": "^2.0.0",
7791
+ "mdast-util-gfm-table": "^2.0.0",
7792
+ "mdast-util-gfm-task-list-item": "^2.0.0",
7793
+ "mdast-util-to-markdown": "^2.0.0"
7794
  },
7795
+ "funding": {
7796
+ "type": "opencollective",
7797
+ "url": "https://opencollective.com/unified"
7798
  }
7799
  },
7800
+ "node_modules/mdast-util-gfm-autolink-literal": {
7801
+ "version": "2.0.1",
7802
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
7803
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
 
7804
  "license": "MIT",
7805
+ "dependencies": {
7806
+ "@types/mdast": "^4.0.0",
7807
+ "ccount": "^2.0.0",
7808
+ "devlop": "^1.0.0",
7809
+ "mdast-util-find-and-replace": "^3.0.0",
7810
+ "micromark-util-character": "^2.0.0"
7811
+ },
7812
  "funding": {
7813
+ "type": "opencollective",
7814
+ "url": "https://opencollective.com/unified"
7815
  }
7816
  },
7817
+ "node_modules/mdast-util-gfm-footnote": {
7818
+ "version": "2.1.0",
7819
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
7820
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
7821
  "license": "MIT",
7822
  "dependencies": {
7823
+ "@types/mdast": "^4.0.0",
7824
+ "devlop": "^1.1.0",
7825
+ "mdast-util-from-markdown": "^2.0.0",
7826
+ "mdast-util-to-markdown": "^2.0.0",
7827
+ "micromark-util-normalize-identifier": "^2.0.0"
7828
  },
7829
+ "funding": {
7830
+ "type": "opencollective",
7831
+ "url": "https://opencollective.com/unified"
7832
+ }
7833
+ },
7834
+ "node_modules/mdast-util-gfm-strikethrough": {
7835
+ "version": "2.0.0",
7836
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
7837
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
7838
+ "license": "MIT",
7839
+ "dependencies": {
7840
+ "@types/mdast": "^4.0.0",
7841
+ "mdast-util-from-markdown": "^2.0.0",
7842
+ "mdast-util-to-markdown": "^2.0.0"
7843
  },
7844
+ "funding": {
7845
+ "type": "opencollective",
7846
+ "url": "https://opencollective.com/unified"
7847
+ }
7848
+ },
7849
+ "node_modules/mdast-util-gfm-table": {
7850
+ "version": "2.0.0",
7851
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
7852
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
7853
+ "license": "MIT",
7854
+ "dependencies": {
7855
+ "@types/mdast": "^4.0.0",
7856
+ "devlop": "^1.0.0",
7857
+ "markdown-table": "^3.0.0",
7858
+ "mdast-util-from-markdown": "^2.0.0",
7859
+ "mdast-util-to-markdown": "^2.0.0"
7860
+ },
7861
+ "funding": {
7862
+ "type": "opencollective",
7863
+ "url": "https://opencollective.com/unified"
7864
+ }
7865
+ },
7866
+ "node_modules/mdast-util-gfm-task-list-item": {
7867
+ "version": "2.0.0",
7868
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
7869
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
7870
+ "license": "MIT",
7871
+ "dependencies": {
7872
+ "@types/mdast": "^4.0.0",
7873
+ "devlop": "^1.0.0",
7874
+ "mdast-util-from-markdown": "^2.0.0",
7875
+ "mdast-util-to-markdown": "^2.0.0"
7876
+ },
7877
+ "funding": {
7878
+ "type": "opencollective",
7879
+ "url": "https://opencollective.com/unified"
7880
+ }
7881
+ },
7882
+ "node_modules/mdast-util-mdx-expression": {
7883
+ "version": "2.0.1",
7884
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
7885
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
7886
+ "license": "MIT",
7887
+ "dependencies": {
7888
+ "@types/estree-jsx": "^1.0.0",
7889
+ "@types/hast": "^3.0.0",
7890
+ "@types/mdast": "^4.0.0",
7891
+ "devlop": "^1.0.0",
7892
+ "mdast-util-from-markdown": "^2.0.0",
7893
+ "mdast-util-to-markdown": "^2.0.0"
7894
+ },
7895
+ "funding": {
7896
+ "type": "opencollective",
7897
+ "url": "https://opencollective.com/unified"
7898
+ }
7899
+ },
7900
+ "node_modules/mdast-util-mdx-jsx": {
7901
+ "version": "3.2.0",
7902
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
7903
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
7904
+ "license": "MIT",
7905
+ "dependencies": {
7906
+ "@types/estree-jsx": "^1.0.0",
7907
+ "@types/hast": "^3.0.0",
7908
+ "@types/mdast": "^4.0.0",
7909
+ "@types/unist": "^3.0.0",
7910
+ "ccount": "^2.0.0",
7911
+ "devlop": "^1.1.0",
7912
+ "mdast-util-from-markdown": "^2.0.0",
7913
+ "mdast-util-to-markdown": "^2.0.0",
7914
+ "parse-entities": "^4.0.0",
7915
+ "stringify-entities": "^4.0.0",
7916
+ "unist-util-stringify-position": "^4.0.0",
7917
+ "vfile-message": "^4.0.0"
7918
+ },
7919
+ "funding": {
7920
+ "type": "opencollective",
7921
+ "url": "https://opencollective.com/unified"
7922
+ }
7923
+ },
7924
+ "node_modules/mdast-util-mdxjs-esm": {
7925
+ "version": "2.0.1",
7926
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
7927
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
7928
+ "license": "MIT",
7929
+ "dependencies": {
7930
+ "@types/estree-jsx": "^1.0.0",
7931
+ "@types/hast": "^3.0.0",
7932
+ "@types/mdast": "^4.0.0",
7933
+ "devlop": "^1.0.0",
7934
+ "mdast-util-from-markdown": "^2.0.0",
7935
+ "mdast-util-to-markdown": "^2.0.0"
7936
+ },
7937
+ "funding": {
7938
+ "type": "opencollective",
7939
+ "url": "https://opencollective.com/unified"
7940
+ }
7941
+ },
7942
+ "node_modules/mdast-util-phrasing": {
7943
+ "version": "4.1.0",
7944
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
7945
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
7946
+ "license": "MIT",
7947
+ "dependencies": {
7948
+ "@types/mdast": "^4.0.0",
7949
+ "unist-util-is": "^6.0.0"
7950
+ },
7951
+ "funding": {
7952
+ "type": "opencollective",
7953
+ "url": "https://opencollective.com/unified"
7954
+ }
7955
+ },
7956
+ "node_modules/mdast-util-to-hast": {
7957
+ "version": "13.2.1",
7958
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
7959
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
7960
+ "license": "MIT",
7961
+ "dependencies": {
7962
+ "@types/hast": "^3.0.0",
7963
+ "@types/mdast": "^4.0.0",
7964
+ "@ungap/structured-clone": "^1.0.0",
7965
+ "devlop": "^1.0.0",
7966
+ "micromark-util-sanitize-uri": "^2.0.0",
7967
+ "trim-lines": "^3.0.0",
7968
+ "unist-util-position": "^5.0.0",
7969
+ "unist-util-visit": "^5.0.0",
7970
+ "vfile": "^6.0.0"
7971
+ },
7972
+ "funding": {
7973
+ "type": "opencollective",
7974
+ "url": "https://opencollective.com/unified"
7975
+ }
7976
+ },
7977
+ "node_modules/mdast-util-to-markdown": {
7978
+ "version": "2.1.2",
7979
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
7980
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
7981
+ "license": "MIT",
7982
+ "dependencies": {
7983
+ "@types/mdast": "^4.0.0",
7984
+ "@types/unist": "^3.0.0",
7985
+ "longest-streak": "^3.0.0",
7986
+ "mdast-util-phrasing": "^4.0.0",
7987
+ "mdast-util-to-string": "^4.0.0",
7988
+ "micromark-util-classify-character": "^2.0.0",
7989
+ "micromark-util-decode-string": "^2.0.0",
7990
+ "unist-util-visit": "^5.0.0",
7991
+ "zwitch": "^2.0.0"
7992
+ },
7993
+ "funding": {
7994
+ "type": "opencollective",
7995
+ "url": "https://opencollective.com/unified"
7996
+ }
7997
+ },
7998
+ "node_modules/mdast-util-to-string": {
7999
+ "version": "4.0.0",
8000
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
8001
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
8002
+ "license": "MIT",
8003
+ "dependencies": {
8004
+ "@types/mdast": "^4.0.0"
8005
+ },
8006
+ "funding": {
8007
+ "type": "opencollective",
8008
+ "url": "https://opencollective.com/unified"
8009
+ }
8010
+ },
8011
+ "node_modules/mdn-data": {
8012
+ "version": "2.0.30",
8013
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
8014
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
8015
+ "license": "CC0-1.0"
8016
+ },
8017
+ "node_modules/merge2": {
8018
+ "version": "1.4.1",
8019
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
8020
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
8021
+ "dev": true,
8022
+ "license": "MIT",
8023
+ "engines": {
8024
+ "node": ">= 8"
8025
+ }
8026
+ },
8027
+ "node_modules/micromark": {
8028
+ "version": "4.0.2",
8029
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
8030
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
8031
+ "funding": [
8032
+ {
8033
+ "type": "GitHub Sponsors",
8034
+ "url": "https://github.com/sponsors/unifiedjs"
8035
+ },
8036
+ {
8037
+ "type": "OpenCollective",
8038
+ "url": "https://opencollective.com/unified"
8039
+ }
8040
+ ],
8041
+ "license": "MIT",
8042
+ "dependencies": {
8043
+ "@types/debug": "^4.0.0",
8044
+ "debug": "^4.0.0",
8045
+ "decode-named-character-reference": "^1.0.0",
8046
+ "devlop": "^1.0.0",
8047
+ "micromark-core-commonmark": "^2.0.0",
8048
+ "micromark-factory-space": "^2.0.0",
8049
+ "micromark-util-character": "^2.0.0",
8050
+ "micromark-util-chunked": "^2.0.0",
8051
+ "micromark-util-combine-extensions": "^2.0.0",
8052
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
8053
+ "micromark-util-encode": "^2.0.0",
8054
+ "micromark-util-normalize-identifier": "^2.0.0",
8055
+ "micromark-util-resolve-all": "^2.0.0",
8056
+ "micromark-util-sanitize-uri": "^2.0.0",
8057
+ "micromark-util-subtokenize": "^2.0.0",
8058
+ "micromark-util-symbol": "^2.0.0",
8059
+ "micromark-util-types": "^2.0.0"
8060
+ }
8061
+ },
8062
+ "node_modules/micromark-core-commonmark": {
8063
+ "version": "2.0.3",
8064
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
8065
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
8066
+ "funding": [
8067
+ {
8068
+ "type": "GitHub Sponsors",
8069
+ "url": "https://github.com/sponsors/unifiedjs"
8070
+ },
8071
+ {
8072
+ "type": "OpenCollective",
8073
+ "url": "https://opencollective.com/unified"
8074
+ }
8075
+ ],
8076
+ "license": "MIT",
8077
+ "dependencies": {
8078
+ "decode-named-character-reference": "^1.0.0",
8079
+ "devlop": "^1.0.0",
8080
+ "micromark-factory-destination": "^2.0.0",
8081
+ "micromark-factory-label": "^2.0.0",
8082
+ "micromark-factory-space": "^2.0.0",
8083
+ "micromark-factory-title": "^2.0.0",
8084
+ "micromark-factory-whitespace": "^2.0.0",
8085
+ "micromark-util-character": "^2.0.0",
8086
+ "micromark-util-chunked": "^2.0.0",
8087
+ "micromark-util-classify-character": "^2.0.0",
8088
+ "micromark-util-html-tag-name": "^2.0.0",
8089
+ "micromark-util-normalize-identifier": "^2.0.0",
8090
+ "micromark-util-resolve-all": "^2.0.0",
8091
+ "micromark-util-subtokenize": "^2.0.0",
8092
+ "micromark-util-symbol": "^2.0.0",
8093
+ "micromark-util-types": "^2.0.0"
8094
+ }
8095
+ },
8096
+ "node_modules/micromark-extension-gfm": {
8097
+ "version": "3.0.0",
8098
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
8099
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
8100
+ "license": "MIT",
8101
+ "dependencies": {
8102
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
8103
+ "micromark-extension-gfm-footnote": "^2.0.0",
8104
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
8105
+ "micromark-extension-gfm-table": "^2.0.0",
8106
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
8107
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
8108
+ "micromark-util-combine-extensions": "^2.0.0",
8109
+ "micromark-util-types": "^2.0.0"
8110
+ },
8111
+ "funding": {
8112
+ "type": "opencollective",
8113
+ "url": "https://opencollective.com/unified"
8114
+ }
8115
+ },
8116
+ "node_modules/micromark-extension-gfm-autolink-literal": {
8117
+ "version": "2.1.0",
8118
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
8119
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
8120
+ "license": "MIT",
8121
+ "dependencies": {
8122
+ "micromark-util-character": "^2.0.0",
8123
+ "micromark-util-sanitize-uri": "^2.0.0",
8124
+ "micromark-util-symbol": "^2.0.0",
8125
+ "micromark-util-types": "^2.0.0"
8126
+ },
8127
+ "funding": {
8128
+ "type": "opencollective",
8129
+ "url": "https://opencollective.com/unified"
8130
+ }
8131
+ },
8132
+ "node_modules/micromark-extension-gfm-footnote": {
8133
+ "version": "2.1.0",
8134
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
8135
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
8136
+ "license": "MIT",
8137
+ "dependencies": {
8138
+ "devlop": "^1.0.0",
8139
+ "micromark-core-commonmark": "^2.0.0",
8140
+ "micromark-factory-space": "^2.0.0",
8141
+ "micromark-util-character": "^2.0.0",
8142
+ "micromark-util-normalize-identifier": "^2.0.0",
8143
+ "micromark-util-sanitize-uri": "^2.0.0",
8144
+ "micromark-util-symbol": "^2.0.0",
8145
+ "micromark-util-types": "^2.0.0"
8146
+ },
8147
+ "funding": {
8148
+ "type": "opencollective",
8149
+ "url": "https://opencollective.com/unified"
8150
+ }
8151
+ },
8152
+ "node_modules/micromark-extension-gfm-strikethrough": {
8153
+ "version": "2.1.0",
8154
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
8155
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
8156
+ "license": "MIT",
8157
+ "dependencies": {
8158
+ "devlop": "^1.0.0",
8159
+ "micromark-util-chunked": "^2.0.0",
8160
+ "micromark-util-classify-character": "^2.0.0",
8161
+ "micromark-util-resolve-all": "^2.0.0",
8162
+ "micromark-util-symbol": "^2.0.0",
8163
+ "micromark-util-types": "^2.0.0"
8164
+ },
8165
+ "funding": {
8166
+ "type": "opencollective",
8167
+ "url": "https://opencollective.com/unified"
8168
+ }
8169
+ },
8170
+ "node_modules/micromark-extension-gfm-table": {
8171
+ "version": "2.1.1",
8172
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
8173
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
8174
+ "license": "MIT",
8175
+ "dependencies": {
8176
+ "devlop": "^1.0.0",
8177
+ "micromark-factory-space": "^2.0.0",
8178
+ "micromark-util-character": "^2.0.0",
8179
+ "micromark-util-symbol": "^2.0.0",
8180
+ "micromark-util-types": "^2.0.0"
8181
+ },
8182
+ "funding": {
8183
+ "type": "opencollective",
8184
+ "url": "https://opencollective.com/unified"
8185
+ }
8186
+ },
8187
+ "node_modules/micromark-extension-gfm-tagfilter": {
8188
+ "version": "2.0.0",
8189
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
8190
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
8191
+ "license": "MIT",
8192
+ "dependencies": {
8193
+ "micromark-util-types": "^2.0.0"
8194
+ },
8195
+ "funding": {
8196
+ "type": "opencollective",
8197
+ "url": "https://opencollective.com/unified"
8198
+ }
8199
+ },
8200
+ "node_modules/micromark-extension-gfm-task-list-item": {
8201
+ "version": "2.1.0",
8202
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
8203
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
8204
+ "license": "MIT",
8205
+ "dependencies": {
8206
+ "devlop": "^1.0.0",
8207
+ "micromark-factory-space": "^2.0.0",
8208
+ "micromark-util-character": "^2.0.0",
8209
+ "micromark-util-symbol": "^2.0.0",
8210
+ "micromark-util-types": "^2.0.0"
8211
+ },
8212
+ "funding": {
8213
+ "type": "opencollective",
8214
+ "url": "https://opencollective.com/unified"
8215
+ }
8216
+ },
8217
+ "node_modules/micromark-factory-destination": {
8218
+ "version": "2.0.1",
8219
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
8220
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
8221
+ "funding": [
8222
+ {
8223
+ "type": "GitHub Sponsors",
8224
+ "url": "https://github.com/sponsors/unifiedjs"
8225
+ },
8226
+ {
8227
+ "type": "OpenCollective",
8228
+ "url": "https://opencollective.com/unified"
8229
+ }
8230
+ ],
8231
+ "license": "MIT",
8232
+ "dependencies": {
8233
+ "micromark-util-character": "^2.0.0",
8234
+ "micromark-util-symbol": "^2.0.0",
8235
+ "micromark-util-types": "^2.0.0"
8236
+ }
8237
+ },
8238
+ "node_modules/micromark-factory-label": {
8239
+ "version": "2.0.1",
8240
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
8241
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
8242
+ "funding": [
8243
+ {
8244
+ "type": "GitHub Sponsors",
8245
+ "url": "https://github.com/sponsors/unifiedjs"
8246
+ },
8247
+ {
8248
+ "type": "OpenCollective",
8249
+ "url": "https://opencollective.com/unified"
8250
+ }
8251
+ ],
8252
+ "license": "MIT",
8253
+ "dependencies": {
8254
+ "devlop": "^1.0.0",
8255
+ "micromark-util-character": "^2.0.0",
8256
+ "micromark-util-symbol": "^2.0.0",
8257
+ "micromark-util-types": "^2.0.0"
8258
+ }
8259
+ },
8260
+ "node_modules/micromark-factory-space": {
8261
+ "version": "2.0.1",
8262
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
8263
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
8264
+ "funding": [
8265
+ {
8266
+ "type": "GitHub Sponsors",
8267
+ "url": "https://github.com/sponsors/unifiedjs"
8268
+ },
8269
+ {
8270
+ "type": "OpenCollective",
8271
+ "url": "https://opencollective.com/unified"
8272
+ }
8273
+ ],
8274
+ "license": "MIT",
8275
+ "dependencies": {
8276
+ "micromark-util-character": "^2.0.0",
8277
+ "micromark-util-types": "^2.0.0"
8278
+ }
8279
+ },
8280
+ "node_modules/micromark-factory-title": {
8281
+ "version": "2.0.1",
8282
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
8283
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
8284
+ "funding": [
8285
+ {
8286
+ "type": "GitHub Sponsors",
8287
+ "url": "https://github.com/sponsors/unifiedjs"
8288
+ },
8289
+ {
8290
+ "type": "OpenCollective",
8291
+ "url": "https://opencollective.com/unified"
8292
+ }
8293
+ ],
8294
+ "license": "MIT",
8295
+ "dependencies": {
8296
+ "micromark-factory-space": "^2.0.0",
8297
+ "micromark-util-character": "^2.0.0",
8298
+ "micromark-util-symbol": "^2.0.0",
8299
+ "micromark-util-types": "^2.0.0"
8300
+ }
8301
+ },
8302
+ "node_modules/micromark-factory-whitespace": {
8303
+ "version": "2.0.1",
8304
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
8305
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
8306
+ "funding": [
8307
+ {
8308
+ "type": "GitHub Sponsors",
8309
+ "url": "https://github.com/sponsors/unifiedjs"
8310
+ },
8311
+ {
8312
+ "type": "OpenCollective",
8313
+ "url": "https://opencollective.com/unified"
8314
+ }
8315
+ ],
8316
+ "license": "MIT",
8317
+ "dependencies": {
8318
+ "micromark-factory-space": "^2.0.0",
8319
+ "micromark-util-character": "^2.0.0",
8320
+ "micromark-util-symbol": "^2.0.0",
8321
+ "micromark-util-types": "^2.0.0"
8322
+ }
8323
+ },
8324
+ "node_modules/micromark-util-character": {
8325
+ "version": "2.1.1",
8326
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
8327
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
8328
+ "funding": [
8329
+ {
8330
+ "type": "GitHub Sponsors",
8331
+ "url": "https://github.com/sponsors/unifiedjs"
8332
+ },
8333
+ {
8334
+ "type": "OpenCollective",
8335
+ "url": "https://opencollective.com/unified"
8336
+ }
8337
+ ],
8338
+ "license": "MIT",
8339
+ "dependencies": {
8340
+ "micromark-util-symbol": "^2.0.0",
8341
+ "micromark-util-types": "^2.0.0"
8342
+ }
8343
+ },
8344
+ "node_modules/micromark-util-chunked": {
8345
+ "version": "2.0.1",
8346
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
8347
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
8348
+ "funding": [
8349
+ {
8350
+ "type": "GitHub Sponsors",
8351
+ "url": "https://github.com/sponsors/unifiedjs"
8352
+ },
8353
+ {
8354
+ "type": "OpenCollective",
8355
+ "url": "https://opencollective.com/unified"
8356
+ }
8357
+ ],
8358
+ "license": "MIT",
8359
+ "dependencies": {
8360
+ "micromark-util-symbol": "^2.0.0"
8361
+ }
8362
+ },
8363
+ "node_modules/micromark-util-classify-character": {
8364
+ "version": "2.0.1",
8365
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
8366
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
8367
+ "funding": [
8368
+ {
8369
+ "type": "GitHub Sponsors",
8370
+ "url": "https://github.com/sponsors/unifiedjs"
8371
+ },
8372
+ {
8373
+ "type": "OpenCollective",
8374
+ "url": "https://opencollective.com/unified"
8375
+ }
8376
+ ],
8377
+ "license": "MIT",
8378
+ "dependencies": {
8379
+ "micromark-util-character": "^2.0.0",
8380
+ "micromark-util-symbol": "^2.0.0",
8381
+ "micromark-util-types": "^2.0.0"
8382
+ }
8383
+ },
8384
+ "node_modules/micromark-util-combine-extensions": {
8385
+ "version": "2.0.1",
8386
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
8387
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
8388
+ "funding": [
8389
+ {
8390
+ "type": "GitHub Sponsors",
8391
+ "url": "https://github.com/sponsors/unifiedjs"
8392
+ },
8393
+ {
8394
+ "type": "OpenCollective",
8395
+ "url": "https://opencollective.com/unified"
8396
+ }
8397
+ ],
8398
+ "license": "MIT",
8399
+ "dependencies": {
8400
+ "micromark-util-chunked": "^2.0.0",
8401
+ "micromark-util-types": "^2.0.0"
8402
+ }
8403
+ },
8404
+ "node_modules/micromark-util-decode-numeric-character-reference": {
8405
+ "version": "2.0.2",
8406
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
8407
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
8408
+ "funding": [
8409
+ {
8410
+ "type": "GitHub Sponsors",
8411
+ "url": "https://github.com/sponsors/unifiedjs"
8412
+ },
8413
+ {
8414
+ "type": "OpenCollective",
8415
+ "url": "https://opencollective.com/unified"
8416
+ }
8417
+ ],
8418
+ "license": "MIT",
8419
+ "dependencies": {
8420
+ "micromark-util-symbol": "^2.0.0"
8421
+ }
8422
+ },
8423
+ "node_modules/micromark-util-decode-string": {
8424
+ "version": "2.0.1",
8425
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
8426
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
8427
+ "funding": [
8428
+ {
8429
+ "type": "GitHub Sponsors",
8430
+ "url": "https://github.com/sponsors/unifiedjs"
8431
+ },
8432
+ {
8433
+ "type": "OpenCollective",
8434
+ "url": "https://opencollective.com/unified"
8435
+ }
8436
+ ],
8437
+ "license": "MIT",
8438
+ "dependencies": {
8439
+ "decode-named-character-reference": "^1.0.0",
8440
+ "micromark-util-character": "^2.0.0",
8441
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
8442
+ "micromark-util-symbol": "^2.0.0"
8443
+ }
8444
+ },
8445
+ "node_modules/micromark-util-encode": {
8446
+ "version": "2.0.1",
8447
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
8448
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
8449
+ "funding": [
8450
+ {
8451
+ "type": "GitHub Sponsors",
8452
+ "url": "https://github.com/sponsors/unifiedjs"
8453
+ },
8454
+ {
8455
+ "type": "OpenCollective",
8456
+ "url": "https://opencollective.com/unified"
8457
+ }
8458
+ ],
8459
+ "license": "MIT"
8460
+ },
8461
+ "node_modules/micromark-util-html-tag-name": {
8462
+ "version": "2.0.1",
8463
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
8464
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
8465
+ "funding": [
8466
+ {
8467
+ "type": "GitHub Sponsors",
8468
+ "url": "https://github.com/sponsors/unifiedjs"
8469
+ },
8470
+ {
8471
+ "type": "OpenCollective",
8472
+ "url": "https://opencollective.com/unified"
8473
+ }
8474
+ ],
8475
+ "license": "MIT"
8476
+ },
8477
+ "node_modules/micromark-util-normalize-identifier": {
8478
+ "version": "2.0.1",
8479
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
8480
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
8481
+ "funding": [
8482
+ {
8483
+ "type": "GitHub Sponsors",
8484
+ "url": "https://github.com/sponsors/unifiedjs"
8485
+ },
8486
+ {
8487
+ "type": "OpenCollective",
8488
+ "url": "https://opencollective.com/unified"
8489
+ }
8490
+ ],
8491
+ "license": "MIT",
8492
+ "dependencies": {
8493
+ "micromark-util-symbol": "^2.0.0"
8494
+ }
8495
+ },
8496
+ "node_modules/micromark-util-resolve-all": {
8497
+ "version": "2.0.1",
8498
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
8499
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
8500
+ "funding": [
8501
+ {
8502
+ "type": "GitHub Sponsors",
8503
+ "url": "https://github.com/sponsors/unifiedjs"
8504
+ },
8505
+ {
8506
+ "type": "OpenCollective",
8507
+ "url": "https://opencollective.com/unified"
8508
+ }
8509
+ ],
8510
+ "license": "MIT",
8511
+ "dependencies": {
8512
+ "micromark-util-types": "^2.0.0"
8513
+ }
8514
+ },
8515
+ "node_modules/micromark-util-sanitize-uri": {
8516
+ "version": "2.0.1",
8517
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
8518
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
8519
+ "funding": [
8520
+ {
8521
+ "type": "GitHub Sponsors",
8522
+ "url": "https://github.com/sponsors/unifiedjs"
8523
+ },
8524
+ {
8525
+ "type": "OpenCollective",
8526
+ "url": "https://opencollective.com/unified"
8527
+ }
8528
+ ],
8529
+ "license": "MIT",
8530
+ "dependencies": {
8531
+ "micromark-util-character": "^2.0.0",
8532
+ "micromark-util-encode": "^2.0.0",
8533
+ "micromark-util-symbol": "^2.0.0"
8534
+ }
8535
+ },
8536
+ "node_modules/micromark-util-subtokenize": {
8537
+ "version": "2.1.0",
8538
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
8539
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
8540
+ "funding": [
8541
+ {
8542
+ "type": "GitHub Sponsors",
8543
+ "url": "https://github.com/sponsors/unifiedjs"
8544
+ },
8545
+ {
8546
+ "type": "OpenCollective",
8547
+ "url": "https://opencollective.com/unified"
8548
+ }
8549
+ ],
8550
+ "license": "MIT",
8551
+ "dependencies": {
8552
+ "devlop": "^1.0.0",
8553
+ "micromark-util-chunked": "^2.0.0",
8554
+ "micromark-util-symbol": "^2.0.0",
8555
+ "micromark-util-types": "^2.0.0"
8556
+ }
8557
+ },
8558
+ "node_modules/micromark-util-symbol": {
8559
+ "version": "2.0.1",
8560
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
8561
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
8562
+ "funding": [
8563
+ {
8564
+ "type": "GitHub Sponsors",
8565
+ "url": "https://github.com/sponsors/unifiedjs"
8566
+ },
8567
+ {
8568
+ "type": "OpenCollective",
8569
+ "url": "https://opencollective.com/unified"
8570
+ }
8571
+ ],
8572
+ "license": "MIT"
8573
+ },
8574
+ "node_modules/micromark-util-types": {
8575
+ "version": "2.0.2",
8576
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
8577
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
8578
+ "funding": [
8579
+ {
8580
+ "type": "GitHub Sponsors",
8581
+ "url": "https://github.com/sponsors/unifiedjs"
8582
+ },
8583
+ {
8584
+ "type": "OpenCollective",
8585
+ "url": "https://opencollective.com/unified"
8586
+ }
8587
+ ],
8588
+ "license": "MIT"
8589
+ },
8590
+ "node_modules/micromatch": {
8591
+ "version": "4.0.8",
8592
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
8593
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
8594
+ "dev": true,
8595
+ "license": "MIT",
8596
+ "dependencies": {
8597
+ "braces": "^3.0.3",
8598
+ "picomatch": "^2.3.1"
8599
+ },
8600
+ "engines": {
8601
+ "node": ">=8.6"
8602
+ }
8603
+ },
8604
+ "node_modules/minimatch": {
8605
+ "version": "3.1.2",
8606
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
8607
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
8608
+ "dev": true,
8609
+ "license": "ISC",
8610
+ "dependencies": {
8611
+ "brace-expansion": "^1.1.7"
8612
+ },
8613
+ "engines": {
8614
+ "node": "*"
8615
+ }
8616
+ },
8617
+ "node_modules/minimist": {
8618
+ "version": "1.2.8",
8619
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
8620
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
8621
+ "dev": true,
8622
+ "license": "MIT",
8623
+ "funding": {
8624
+ "url": "https://github.com/sponsors/ljharb"
8625
+ }
8626
+ },
8627
+ "node_modules/motion": {
8628
+ "version": "12.23.24",
8629
+ "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.24.tgz",
8630
+ "integrity": "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==",
8631
+ "license": "MIT",
8632
+ "dependencies": {
8633
+ "framer-motion": "^12.23.24",
8634
+ "tslib": "^2.4.0"
8635
+ },
8636
+ "peerDependencies": {
8637
+ "@emotion/is-prop-valid": "*",
8638
+ "react": "^18.0.0 || ^19.0.0",
8639
+ "react-dom": "^18.0.0 || ^19.0.0"
8640
+ },
8641
+ "peerDependenciesMeta": {
8642
+ "@emotion/is-prop-valid": {
8643
+ "optional": true
8644
  },
8645
  "react": {
8646
  "optional": true
 
9028
  "node": ">=6"
9029
  }
9030
  },
9031
+ "node_modules/parse-entities": {
9032
+ "version": "4.0.2",
9033
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
9034
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
9035
+ "license": "MIT",
9036
+ "dependencies": {
9037
+ "@types/unist": "^2.0.0",
9038
+ "character-entities-legacy": "^3.0.0",
9039
+ "character-reference-invalid": "^2.0.0",
9040
+ "decode-named-character-reference": "^1.0.0",
9041
+ "is-alphanumerical": "^2.0.0",
9042
+ "is-decimal": "^2.0.0",
9043
+ "is-hexadecimal": "^2.0.0"
9044
+ },
9045
+ "funding": {
9046
+ "type": "github",
9047
+ "url": "https://github.com/sponsors/wooorm"
9048
+ }
9049
+ },
9050
+ "node_modules/parse-entities/node_modules/@types/unist": {
9051
+ "version": "2.0.11",
9052
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
9053
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
9054
+ "license": "MIT"
9055
+ },
9056
  "node_modules/parse-json": {
9057
  "version": "5.2.0",
9058
  "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
 
9185
  "react-is": "^16.13.1"
9186
  }
9187
  },
9188
+ "node_modules/property-information": {
9189
+ "version": "7.1.0",
9190
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
9191
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
9192
+ "license": "MIT",
9193
+ "funding": {
9194
+ "type": "github",
9195
+ "url": "https://github.com/sponsors/wooorm"
9196
+ }
9197
+ },
9198
  "node_modules/punycode": {
9199
  "version": "2.3.1",
9200
  "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
 
9279
  "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
9280
  "license": "MIT"
9281
  },
9282
+ "node_modules/react-markdown": {
9283
+ "version": "10.1.0",
9284
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
9285
+ "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==",
9286
+ "license": "MIT",
9287
+ "dependencies": {
9288
+ "@types/hast": "^3.0.0",
9289
+ "@types/mdast": "^4.0.0",
9290
+ "devlop": "^1.0.0",
9291
+ "hast-util-to-jsx-runtime": "^2.0.0",
9292
+ "html-url-attributes": "^3.0.0",
9293
+ "mdast-util-to-hast": "^13.0.0",
9294
+ "remark-parse": "^11.0.0",
9295
+ "remark-rehype": "^11.0.0",
9296
+ "unified": "^11.0.0",
9297
+ "unist-util-visit": "^5.0.0",
9298
+ "vfile": "^6.0.0"
9299
+ },
9300
+ "funding": {
9301
+ "type": "opencollective",
9302
+ "url": "https://opencollective.com/unified"
9303
+ },
9304
+ "peerDependencies": {
9305
+ "@types/react": ">=18",
9306
+ "react": ">=18"
9307
+ }
9308
+ },
9309
  "node_modules/react-remove-scroll": {
9310
  "version": "2.7.1",
9311
  "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
 
9487
  "regjsparser": "bin/parser"
9488
  }
9489
  },
9490
+ "node_modules/rehype-sanitize": {
9491
+ "version": "6.0.0",
9492
+ "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz",
9493
+ "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==",
9494
+ "license": "MIT",
9495
+ "dependencies": {
9496
+ "@types/hast": "^3.0.0",
9497
+ "hast-util-sanitize": "^5.0.0"
9498
+ },
9499
+ "funding": {
9500
+ "type": "opencollective",
9501
+ "url": "https://opencollective.com/unified"
9502
+ }
9503
+ },
9504
+ "node_modules/remark-gfm": {
9505
+ "version": "4.0.1",
9506
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
9507
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
9508
+ "license": "MIT",
9509
+ "dependencies": {
9510
+ "@types/mdast": "^4.0.0",
9511
+ "mdast-util-gfm": "^3.0.0",
9512
+ "micromark-extension-gfm": "^3.0.0",
9513
+ "remark-parse": "^11.0.0",
9514
+ "remark-stringify": "^11.0.0",
9515
+ "unified": "^11.0.0"
9516
+ },
9517
+ "funding": {
9518
+ "type": "opencollective",
9519
+ "url": "https://opencollective.com/unified"
9520
+ }
9521
+ },
9522
+ "node_modules/remark-parse": {
9523
+ "version": "11.0.0",
9524
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
9525
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
9526
+ "license": "MIT",
9527
+ "dependencies": {
9528
+ "@types/mdast": "^4.0.0",
9529
+ "mdast-util-from-markdown": "^2.0.0",
9530
+ "micromark-util-types": "^2.0.0",
9531
+ "unified": "^11.0.0"
9532
+ },
9533
+ "funding": {
9534
+ "type": "opencollective",
9535
+ "url": "https://opencollective.com/unified"
9536
+ }
9537
+ },
9538
+ "node_modules/remark-rehype": {
9539
+ "version": "11.1.2",
9540
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
9541
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
9542
+ "license": "MIT",
9543
+ "dependencies": {
9544
+ "@types/hast": "^3.0.0",
9545
+ "@types/mdast": "^4.0.0",
9546
+ "mdast-util-to-hast": "^13.0.0",
9547
+ "unified": "^11.0.0",
9548
+ "vfile": "^6.0.0"
9549
+ },
9550
+ "funding": {
9551
+ "type": "opencollective",
9552
+ "url": "https://opencollective.com/unified"
9553
+ }
9554
+ },
9555
+ "node_modules/remark-stringify": {
9556
+ "version": "11.0.0",
9557
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
9558
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
9559
+ "license": "MIT",
9560
+ "dependencies": {
9561
+ "@types/mdast": "^4.0.0",
9562
+ "mdast-util-to-markdown": "^2.0.0",
9563
+ "unified": "^11.0.0"
9564
+ },
9565
+ "funding": {
9566
+ "type": "opencollective",
9567
+ "url": "https://opencollective.com/unified"
9568
+ }
9569
+ },
9570
  "node_modules/resolve": {
9571
  "version": "1.22.11",
9572
  "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
 
9936
  "node": ">=0.10.0"
9937
  }
9938
  },
9939
+ "node_modules/space-separated-tokens": {
9940
+ "version": "2.0.2",
9941
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
9942
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
9943
+ "license": "MIT",
9944
+ "funding": {
9945
+ "type": "github",
9946
+ "url": "https://github.com/sponsors/wooorm"
9947
+ }
9948
+ },
9949
  "node_modules/stable-hash": {
9950
  "version": "0.0.5",
9951
  "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
 
10080
  "url": "https://github.com/sponsors/ljharb"
10081
  }
10082
  },
10083
+ "node_modules/stringify-entities": {
10084
+ "version": "4.0.4",
10085
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
10086
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
10087
+ "license": "MIT",
10088
+ "dependencies": {
10089
+ "character-entities-html4": "^2.0.0",
10090
+ "character-entities-legacy": "^3.0.0"
10091
+ },
10092
+ "funding": {
10093
+ "type": "github",
10094
+ "url": "https://github.com/sponsors/wooorm"
10095
+ }
10096
+ },
10097
  "node_modules/strip-bom": {
10098
  "version": "3.0.0",
10099
  "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
 
10117
  "url": "https://github.com/sponsors/sindresorhus"
10118
  }
10119
  },
10120
+ "node_modules/style-to-js": {
10121
+ "version": "1.1.21",
10122
+ "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
10123
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
10124
+ "license": "MIT",
10125
+ "dependencies": {
10126
+ "style-to-object": "1.0.14"
10127
+ }
10128
+ },
10129
+ "node_modules/style-to-object": {
10130
+ "version": "1.0.14",
10131
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
10132
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
10133
+ "license": "MIT",
10134
+ "dependencies": {
10135
+ "inline-style-parser": "0.2.7"
10136
+ }
10137
+ },
10138
  "node_modules/styled-jsx": {
10139
  "version": "5.1.6",
10140
  "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
 
10306
  "node": ">=8.0"
10307
  }
10308
  },
10309
+ "node_modules/trim-lines": {
10310
+ "version": "3.0.1",
10311
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
10312
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
10313
+ "license": "MIT",
10314
+ "funding": {
10315
+ "type": "github",
10316
+ "url": "https://github.com/sponsors/wooorm"
10317
+ }
10318
+ },
10319
+ "node_modules/trough": {
10320
+ "version": "2.2.0",
10321
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
10322
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
10323
+ "license": "MIT",
10324
+ "funding": {
10325
+ "type": "github",
10326
+ "url": "https://github.com/sponsors/wooorm"
10327
+ }
10328
+ },
10329
  "node_modules/ts-api-utils": {
10330
  "version": "2.1.0",
10331
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
 
10566
  "node": ">=4"
10567
  }
10568
  },
10569
+ "node_modules/unified": {
10570
+ "version": "11.0.5",
10571
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
10572
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
10573
+ "license": "MIT",
10574
+ "dependencies": {
10575
+ "@types/unist": "^3.0.0",
10576
+ "bail": "^2.0.0",
10577
+ "devlop": "^1.0.0",
10578
+ "extend": "^3.0.0",
10579
+ "is-plain-obj": "^4.0.0",
10580
+ "trough": "^2.0.0",
10581
+ "vfile": "^6.0.0"
10582
+ },
10583
+ "funding": {
10584
+ "type": "opencollective",
10585
+ "url": "https://opencollective.com/unified"
10586
+ }
10587
+ },
10588
+ "node_modules/unist-util-is": {
10589
+ "version": "6.0.1",
10590
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
10591
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
10592
+ "license": "MIT",
10593
+ "dependencies": {
10594
+ "@types/unist": "^3.0.0"
10595
+ },
10596
+ "funding": {
10597
+ "type": "opencollective",
10598
+ "url": "https://opencollective.com/unified"
10599
+ }
10600
+ },
10601
+ "node_modules/unist-util-position": {
10602
+ "version": "5.0.0",
10603
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
10604
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
10605
+ "license": "MIT",
10606
+ "dependencies": {
10607
+ "@types/unist": "^3.0.0"
10608
+ },
10609
+ "funding": {
10610
+ "type": "opencollective",
10611
+ "url": "https://opencollective.com/unified"
10612
+ }
10613
+ },
10614
+ "node_modules/unist-util-stringify-position": {
10615
+ "version": "4.0.0",
10616
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
10617
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
10618
+ "license": "MIT",
10619
+ "dependencies": {
10620
+ "@types/unist": "^3.0.0"
10621
+ },
10622
+ "funding": {
10623
+ "type": "opencollective",
10624
+ "url": "https://opencollective.com/unified"
10625
+ }
10626
+ },
10627
+ "node_modules/unist-util-visit": {
10628
+ "version": "5.1.0",
10629
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
10630
+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
10631
+ "license": "MIT",
10632
+ "dependencies": {
10633
+ "@types/unist": "^3.0.0",
10634
+ "unist-util-is": "^6.0.0",
10635
+ "unist-util-visit-parents": "^6.0.0"
10636
+ },
10637
+ "funding": {
10638
+ "type": "opencollective",
10639
+ "url": "https://opencollective.com/unified"
10640
+ }
10641
+ },
10642
+ "node_modules/unist-util-visit-parents": {
10643
+ "version": "6.0.2",
10644
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
10645
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
10646
+ "license": "MIT",
10647
+ "dependencies": {
10648
+ "@types/unist": "^3.0.0",
10649
+ "unist-util-is": "^6.0.0"
10650
+ },
10651
+ "funding": {
10652
+ "type": "opencollective",
10653
+ "url": "https://opencollective.com/unified"
10654
+ }
10655
+ },
10656
  "node_modules/unrs-resolver": {
10657
  "version": "1.11.1",
10658
  "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
 
10771
  }
10772
  }
10773
  },
10774
+ "node_modules/vfile": {
10775
+ "version": "6.0.3",
10776
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
10777
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
10778
+ "license": "MIT",
10779
+ "dependencies": {
10780
+ "@types/unist": "^3.0.0",
10781
+ "vfile-message": "^4.0.0"
10782
+ },
10783
+ "funding": {
10784
+ "type": "opencollective",
10785
+ "url": "https://opencollective.com/unified"
10786
+ }
10787
+ },
10788
+ "node_modules/vfile-message": {
10789
+ "version": "4.0.3",
10790
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
10791
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
10792
+ "license": "MIT",
10793
+ "dependencies": {
10794
+ "@types/unist": "^3.0.0",
10795
+ "unist-util-stringify-position": "^4.0.0"
10796
+ },
10797
+ "funding": {
10798
+ "type": "opencollective",
10799
+ "url": "https://opencollective.com/unified"
10800
+ }
10801
+ },
10802
  "node_modules/which": {
10803
  "version": "2.0.2",
10804
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
 
10955
  "peerDependencies": {
10956
  "zod": "^3.25.0 || ^4.0.0"
10957
  }
10958
+ },
10959
+ "node_modules/zwitch": {
10960
+ "version": "2.0.4",
10961
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
10962
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
10963
+ "license": "MIT",
10964
+ "funding": {
10965
+ "type": "github",
10966
+ "url": "https://github.com/sponsors/wooorm"
10967
+ }
10968
  }
10969
  }
10970
  }
frontend/package.json CHANGED
@@ -22,7 +22,10 @@
22
  "react-dom": "^19.2.1",
23
  "react-dropzone": "^14.3.8",
24
  "react-icons": "^5.5.0",
 
25
  "react-use-measure": "^2.1.7",
 
 
26
  "tailwind-merge": "^3.3.1"
27
  },
28
  "devDependencies": {
 
22
  "react-dom": "^19.2.1",
23
  "react-dropzone": "^14.3.8",
24
  "react-icons": "^5.5.0",
25
+ "react-markdown": "^10.1.0",
26
  "react-use-measure": "^2.1.7",
27
+ "rehype-sanitize": "^6.0.0",
28
+ "remark-gfm": "^4.0.1",
29
  "tailwind-merge": "^3.3.1"
30
  },
31
  "devDependencies": {
models/deepseek_v3.py CHANGED
@@ -5,8 +5,7 @@ class DeepSeek_V3:
5
  self.client = InferenceClient(token=token)
6
  self.model_id = "deepseek-ai/DeepSeek-V3"
7
 
8
- def generate(self, prompt, max_tokens=500, temperature=0.1):
9
- response = ""
10
  try:
11
  for message in self.client.chat_completion(
12
  model=self.model_id,
@@ -17,7 +16,10 @@ class DeepSeek_V3:
17
  ):
18
  if message.choices:
19
  content = message.choices[0].delta.content
20
- if content: response += content
 
21
  except Exception as e:
22
- return f" DeepSeek API Busy: {e}"
23
- return response
 
 
 
5
  self.client = InferenceClient(token=token)
6
  self.model_id = "deepseek-ai/DeepSeek-V3"
7
 
8
+ def generate_stream(self, prompt, max_tokens=500, temperature=0.1):
 
9
  try:
10
  for message in self.client.chat_completion(
11
  model=self.model_id,
 
16
  ):
17
  if message.choices:
18
  content = message.choices[0].delta.content
19
+ if content:
20
+ yield content
21
  except Exception as e:
22
+ yield f" DeepSeek API Busy: {e}"
23
+
24
+ def generate(self, prompt, max_tokens=500, temperature=0.1):
25
+ return "".join(self.generate_stream(prompt, max_tokens=max_tokens, temperature=temperature))
models/llama_3_8b.py CHANGED
@@ -5,8 +5,7 @@ class Llama3_8B:
5
  self.client = InferenceClient(token=token)
6
  self.model_id = "meta-llama/Meta-Llama-3-8B-Instruct"
7
 
8
- def generate(self, prompt, max_tokens=500, temperature=0.1):
9
- response = ""
10
  for message in self.client.chat_completion(
11
  model=self.model_id,
12
  messages=[{"role": "user", "content": prompt}],
@@ -16,5 +15,8 @@ class Llama3_8B:
16
  ):
17
  if message.choices:
18
  content = message.choices[0].delta.content
19
- if content: response += content
20
- return response
 
 
 
 
5
  self.client = InferenceClient(token=token)
6
  self.model_id = "meta-llama/Meta-Llama-3-8B-Instruct"
7
 
8
+ def generate_stream(self, prompt, max_tokens=500, temperature=0.1):
 
9
  for message in self.client.chat_completion(
10
  model=self.model_id,
11
  messages=[{"role": "user", "content": prompt}],
 
15
  ):
16
  if message.choices:
17
  content = message.choices[0].delta.content
18
+ if content:
19
+ yield content
20
+
21
+ def generate(self, prompt, max_tokens=500, temperature=0.1):
22
+ return "".join(self.generate_stream(prompt, max_tokens=max_tokens, temperature=temperature))
models/mistral_7b.py CHANGED
@@ -5,8 +5,7 @@ class Mistral_7b:
5
  self.client = InferenceClient(api_key=token)
6
  self.model_id = "mistralai/Mistral-7B-Instruct-v0.2:featherless-ai"
7
 
8
- def generate(self, prompt, max_tokens=500, temperature=0.1):
9
- response = ""
10
  try:
11
  stream = self.client.chat.completions.create(
12
  model=self.model_id,
@@ -18,9 +17,10 @@ class Mistral_7b:
18
  for chunk in stream:
19
  if chunk.choices and chunk.choices[0].delta.content:
20
  content = chunk.choices[0].delta.content
21
- response += content
22
 
23
  except Exception as e:
24
- return f" Mistral Featherless Error: {e}"
25
-
26
- return response
 
 
5
  self.client = InferenceClient(api_key=token)
6
  self.model_id = "mistralai/Mistral-7B-Instruct-v0.2:featherless-ai"
7
 
8
+ def generate_stream(self, prompt, max_tokens=500, temperature=0.1):
 
9
  try:
10
  stream = self.client.chat.completions.create(
11
  model=self.model_id,
 
17
  for chunk in stream:
18
  if chunk.choices and chunk.choices[0].delta.content:
19
  content = chunk.choices[0].delta.content
20
+ yield content
21
 
22
  except Exception as e:
23
+ yield f" Mistral Featherless Error: {e}"
24
+
25
+ def generate(self, prompt, max_tokens=500, temperature=0.1):
26
+ return "".join(self.generate_stream(prompt, max_tokens=max_tokens, temperature=temperature))
models/qwen_2_5.py CHANGED
@@ -5,8 +5,7 @@ class Qwen2_5:
5
  self.client = InferenceClient(token=token)
6
  self.model_id = "Qwen/Qwen2.5-72B-Instruct"
7
 
8
- def generate(self, prompt, max_tokens=500, temperature=0.1):
9
- response = ""
10
  for message in self.client.chat_completion(
11
  model=self.model_id,
12
  messages=[{"role": "user", "content": prompt}],
@@ -16,5 +15,8 @@ class Qwen2_5:
16
  ):
17
  if message.choices:
18
  content = message.choices[0].delta.content
19
- if content: response += content
20
- return response
 
 
 
 
5
  self.client = InferenceClient(token=token)
6
  self.model_id = "Qwen/Qwen2.5-72B-Instruct"
7
 
8
+ def generate_stream(self, prompt, max_tokens=500, temperature=0.1):
 
9
  for message in self.client.chat_completion(
10
  model=self.model_id,
11
  messages=[{"role": "user", "content": prompt}],
 
15
  ):
16
  if message.choices:
17
  content = message.choices[0].delta.content
18
+ if content:
19
+ yield content
20
+
21
+ def generate(self, prompt, max_tokens=500, temperature=0.1):
22
+ return "".join(self.generate_stream(prompt, max_tokens=max_tokens, temperature=temperature))
models/tiny_aya.py CHANGED
@@ -5,9 +5,7 @@ class TinyAya:
5
  self.client = InferenceClient(token=token)
6
  self.model_id = "CohereLabs/tiny-aya-global"
7
 
8
- def generate(self, prompt, max_tokens=500, temperature=0.1):
9
-
10
- response = ""
11
  try:
12
  for message in self.client.chat_completion(
13
  model=self.model_id,
@@ -18,8 +16,10 @@ class TinyAya:
18
  ):
19
  if message.choices:
20
  content = message.choices[0].delta.content
21
- if content: response += content
 
22
  except Exception as e:
23
- return f" TinyAya Error: {e}"
24
-
25
- return response
 
 
5
  self.client = InferenceClient(token=token)
6
  self.model_id = "CohereLabs/tiny-aya-global"
7
 
8
+ def generate_stream(self, prompt, max_tokens=500, temperature=0.1):
 
 
9
  try:
10
  for message in self.client.chat_completion(
11
  model=self.model_id,
 
16
  ):
17
  if message.choices:
18
  content = message.choices[0].delta.content
19
+ if content:
20
+ yield content
21
  except Exception as e:
22
+ yield f" TinyAya Error: {e}"
23
+
24
+ def generate(self, prompt, max_tokens=500, temperature=0.1):
25
+ return "".join(self.generate_stream(prompt, max_tokens=max_tokens, temperature=temperature))
requirements.txt CHANGED
@@ -16,6 +16,7 @@ fastapi==0.121.1
16
  filelock==3.25.2
17
  frozenlist==1.8.0
18
  fsspec==2026.2.0
 
19
  greenlet==3.3.2
20
  h11==0.16.0
21
  hf-xet==1.4.2
 
16
  filelock==3.25.2
17
  frozenlist==1.8.0
18
  fsspec==2026.2.0
19
+ groq
20
  greenlet==3.3.2
21
  h11==0.16.0
22
  hf-xet==1.4.2
retriever/generator.py CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  class RAGGenerator:
2
  def generate_prompt(self, query, retrieved_contexts):
3
  """Prepares the academic prompt template."""
@@ -6,6 +9,12 @@ class RAGGenerator:
6
  return f"""You are an expert academic assistant. Use the following pieces of retrieved context to answer the question.
7
  If the answer isn't in the context, say you don't know based on the provided documents.
8
 
 
 
 
 
 
 
9
  Context:
10
  {context_text}
11
 
@@ -16,4 +25,9 @@ Answer:"""
16
  def get_answer(self, model_instance, query, retrieved_contexts, **kwargs):
17
  """Uses a specific model instance to generate the final answer."""
18
  prompt = self.generate_prompt(query, retrieved_contexts)
19
- return model_instance.generate(prompt, **kwargs)
 
 
 
 
 
 
1
+ #changed the prompt to output as markdown, plus some formating details
2
+ #also added get answer stream for incremental token rendering on the frontend
3
+ # --@Qamar
4
  class RAGGenerator:
5
  def generate_prompt(self, query, retrieved_contexts):
6
  """Prepares the academic prompt template."""
 
9
  return f"""You are an expert academic assistant. Use the following pieces of retrieved context to answer the question.
10
  If the answer isn't in the context, say you don't know based on the provided documents.
11
 
12
+ Formatting requirements:
13
+ - Write the response in clean Markdown.
14
+ - Use short headings and bullet points when helpful.
15
+ - Use fenced code blocks for code.
16
+ - Use simple markdown tables only when they improve clarity.
17
+
18
  Context:
19
  {context_text}
20
 
 
25
  def get_answer(self, model_instance, query, retrieved_contexts, **kwargs):
26
  """Uses a specific model instance to generate the final answer."""
27
  prompt = self.generate_prompt(query, retrieved_contexts)
28
+ return model_instance.generate(prompt, **kwargs)
29
+
30
+ def get_answer_stream(self, model_instance, query, retrieved_contexts, **kwargs):
31
+ """Yields model output chunks so the frontend can render incremental tokens."""
32
+ prompt = self.generate_prompt(query, retrieved_contexts)
33
+ return model_instance.generate_stream(prompt, **kwargs)
retriever/processor.py CHANGED
@@ -14,11 +14,16 @@ import pandas as pd
14
 
15
 
16
  class ChunkProcessor:
17
- def __init__(self, model_name='all-MiniLM-L6-v2', verbose: bool = True):
18
  self.model_name = model_name
19
  self.encoder = SentenceTransformer(model_name)
20
  self.verbose = verbose
21
- self.hf_embeddings = HuggingFaceEmbeddings(model_name=model_name)
 
 
 
 
 
22
 
23
  # ------------------------------------------------------------------
24
  # Splitters
@@ -72,7 +77,7 @@ class ChunkProcessor:
72
 
73
  elif technique == "semantic":
74
  return SemanticChunker(
75
- self.hf_embeddings,
76
  breakpoint_threshold_type=kwargs.get('breakpoint_threshold_type', "percentile"),
77
  # Using 70 because 95 was giving way too big chunks
78
  breakpoint_threshold_amount=kwargs.get('breakpoint_threshold_amount', 70)
 
14
 
15
 
16
  class ChunkProcessor:
17
+ def __init__(self, model_name='all-MiniLM-L6-v2', verbose: bool = True, load_hf_embeddings: bool = False):
18
  self.model_name = model_name
19
  self.encoder = SentenceTransformer(model_name)
20
  self.verbose = verbose
21
+ self.hf_embeddings = HuggingFaceEmbeddings(model_name=model_name) if load_hf_embeddings else None
22
+
23
+ def _get_hf_embeddings(self):
24
+ if self.hf_embeddings is None:
25
+ self.hf_embeddings = HuggingFaceEmbeddings(model_name=self.model_name)
26
+ return self.hf_embeddings
27
 
28
  # ------------------------------------------------------------------
29
  # Splitters
 
77
 
78
  elif technique == "semantic":
79
  return SemanticChunker(
80
+ self._get_hf_embeddings(),
81
  breakpoint_threshold_type=kwargs.get('breakpoint_threshold_type', "percentile"),
82
  # Using 70 because 95 was giving way too big chunks
83
  breakpoint_threshold_amount=kwargs.get('breakpoint_threshold_amount', 70)
retriever/retriever.py CHANGED
@@ -7,13 +7,32 @@ from typing import Optional, List
7
 
8
  class HybridRetriever:
9
  def __init__(self, final_chunks, embed_model, rerank_model_name='cross-encoder/ms-marco-MiniLM-L-6-v2', verbose: bool = True):
 
10
  self.final_chunks = final_chunks
11
  self.embed_model = embed_model
 
 
12
  self.rerank_model = CrossEncoder(rerank_model_name)
 
13
  self.verbose = verbose
14
-
 
15
  self.tokenized_corpus = [chunk['metadata']['text'].lower().split() for chunk in final_chunks]
 
 
 
16
  self.bm25 = BM25Okapi(self.tokenized_corpus)
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  # ------------------------------------------------------------------
19
  # Retrieval
 
7
 
8
  class HybridRetriever:
9
  def __init__(self, final_chunks, embed_model, rerank_model_name='cross-encoder/ms-marco-MiniLM-L-6-v2', verbose: bool = True):
10
+ init_start = time.perf_counter()
11
  self.final_chunks = final_chunks
12
  self.embed_model = embed_model
13
+
14
+ reranker_start = time.perf_counter()
15
  self.rerank_model = CrossEncoder(rerank_model_name)
16
+ reranker_time = time.perf_counter() - reranker_start
17
  self.verbose = verbose
18
+
19
+ tokenization_start = time.perf_counter()
20
  self.tokenized_corpus = [chunk['metadata']['text'].lower().split() for chunk in final_chunks]
21
+ tokenization_time = time.perf_counter() - tokenization_start
22
+
23
+ bm25_start = time.perf_counter()
24
  self.bm25 = BM25Okapi(self.tokenized_corpus)
25
+ bm25_time = time.perf_counter() - bm25_start
26
+
27
+ total_time = time.perf_counter() - init_start
28
+ print(
29
+ "HybridRetriever init complete | "
30
+ f"chunks={len(final_chunks)} | "
31
+ f"reranker_load={reranker_time:.3f}s | "
32
+ f"tokenize={tokenization_time:.3f}s | "
33
+ f"bm25_build={bm25_time:.3f}s | "
34
+ f"total={total_time:.3f}s"
35
+ )
36
 
37
  # ------------------------------------------------------------------
38
  # Retrieval
vector_db.py CHANGED
@@ -1,7 +1,14 @@
1
  import time
2
  import re
 
 
 
3
  from pinecone import Pinecone, ServerlessSpec
4
 
 
 
 
 
5
  def slugify_technique(name):
6
  """Converts 'Sentence Splitter' to 'sentence-splitter' for Pinecone naming."""
7
  return re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')
@@ -108,6 +115,80 @@ def upsert_to_pinecone(index, chunks, batch_size=100):
108
  batch = chunks[i : i + batch_size]
109
  index.upsert(vectors=batch)
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  def load_chunks_from_pinecone(index, batch_size: int = 100) -> list[dict[str, any]]:
113
  """
 
1
  import time
2
  import re
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List
6
  from pinecone import Pinecone, ServerlessSpec
7
 
8
+
9
+ # Added cacheing to reduce consecutive startup time
10
+ # --@Qamar
11
+
12
  def slugify_technique(name):
13
  """Converts 'Sentence Splitter' to 'sentence-splitter' for Pinecone naming."""
14
  return re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')
 
115
  batch = chunks[i : i + batch_size]
116
  index.upsert(vectors=batch)
117
 
118
+ # Some methods for loading chunks back from Pinecone with local caching to speed up BM25 initialization
119
+
120
+ def _sanitize_index_name(index_name: str) -> str:
121
+ return re.sub(r'[^a-zA-Z0-9._-]+', '-', index_name).strip('-') or 'default-index'
122
+
123
+
124
+ def _chunk_cache_path(cache_dir: str, index_name: str) -> Path:
125
+ cache_root = Path(cache_dir)
126
+ cache_root.mkdir(parents=True, exist_ok=True)
127
+ safe_name = _sanitize_index_name(index_name)
128
+ return cache_root / f"bm25_chunks_{safe_name}.json"
129
+
130
+
131
+ def _read_chunk_cache(path: Path) -> Dict[str, Any]:
132
+ with path.open("r", encoding="utf-8") as f:
133
+ return json.load(f)
134
+
135
+
136
+ def _write_chunk_cache(path: Path, payload: Dict[str, Any]) -> None:
137
+ with path.open("w", encoding="utf-8") as f:
138
+ json.dump(payload, f)
139
+
140
+
141
+ def load_chunks_with_local_cache(
142
+ index,
143
+ index_name: str,
144
+ cache_dir: str = ".cache",
145
+ batch_size: int = 100,
146
+ force_refresh: bool = False,
147
+ ) -> tuple[List[Dict[str, Any]], str]:
148
+
149
+ cache_file = _chunk_cache_path(cache_dir=cache_dir, index_name=index_name)
150
+ stats = index.describe_index_stats()
151
+ current_count = stats.get("total_vector_count", 0)
152
+
153
+ if not force_refresh and cache_file.exists():
154
+ try:
155
+ cached_payload = _read_chunk_cache(cache_file)
156
+ cached_meta = cached_payload.get("meta", {})
157
+ cached_count = cached_meta.get("vector_count", -1)
158
+ cached_chunks = cached_payload.get("chunks", [])
159
+
160
+ if cached_count == current_count and cached_chunks:
161
+ print(
162
+ f" Loaded BM25 chunk cache: {cache_file} "
163
+ f"(chunks={len(cached_chunks)}, vectors={cached_count})"
164
+ )
165
+ return cached_chunks, "cache"
166
+
167
+ print(
168
+ " BM25 cache stale or empty. "
169
+ f"cache_vectors={cached_count}, pinecone_vectors={current_count}. Refreshing..."
170
+ )
171
+ except Exception as e:
172
+ print(f" Failed to read BM25 cache ({cache_file}): {e}. Refreshing from Pinecone...")
173
+
174
+ chunks = load_chunks_from_pinecone(index=index, batch_size=batch_size)
175
+ payload = {
176
+ "meta": {
177
+ "index_name": index_name,
178
+ "vector_count": current_count,
179
+ "updated_at_epoch_s": int(time.time()),
180
+ },
181
+ "chunks": chunks,
182
+ }
183
+
184
+ try:
185
+ _write_chunk_cache(cache_file, payload)
186
+ print(f" Saved BM25 chunk cache: {cache_file} (chunks={len(chunks)})")
187
+ except Exception as e:
188
+ print(f" Failed to write BM25 cache ({cache_file}): {e}")
189
+
190
+ return chunks, "pinecone"
191
+
192
 
193
  def load_chunks_from_pinecone(index, batch_size: int = 100) -> list[dict[str, any]]:
194
  """