Ramail Khan commited on
Commit
74dd328
·
unverified ·
2 Parent(s): b4cdea6447e50c

Merge pull request #5 from ramailkk/qamar/frontend-update

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.3,
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,8 +1,10 @@
 
 
 
1
  class RAGGenerator:
2
  def generate_prompt(self, query, retrieved_contexts):
3
  """Prepares the academic prompt template."""
4
  context_text = "\n\n".join([f"--- Source {i+1} ---\n{c}" for i, c in enumerate(retrieved_contexts)])
5
-
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
 
@@ -16,4 +18,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."""
7
  context_text = "\n\n".join([f"--- Source {i+1} ---\n{c}" for i, c in enumerate(retrieved_contexts)])
 
8
  return f"""You are an expert academic assistant. Use the following pieces of retrieved context to answer the question.
9
  If the answer isn't in the context, say you don't know based on the provided documents.
10
 
 
18
  def get_answer(self, model_instance, query, retrieved_contexts, **kwargs):
19
  """Uses a specific model instance to generate the final answer."""
20
  prompt = self.generate_prompt(query, retrieved_contexts)
21
+ return model_instance.generate(prompt, **kwargs)
22
+
23
+ def get_answer_stream(self, model_instance, query, retrieved_contexts, **kwargs):
24
+ """Yields model output chunks so the frontend can render incremental tokens."""
25
+ prompt = self.generate_prompt(query, retrieved_contexts)
26
+ 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
  """