From 912a224b6b356d1e591202cf3845b9a7d55be6a2 Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Tue, 1 Sep 2026 04:21:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=E5=B8=82=E5=9C=BA=E5=88=86=E6=9E=90?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端:AI分析服务、工具调用、定时任务 - 前端:报告列表、详情页、Mermaid图表支持 - 支持OpenAI API兼容模型 - 收盘后自动分析生成报告 --- backend/database.py | 14 + backend/main.py | 9 +- backend/routes/ai_analysis.py | 127 +++ backend/services/ai_collector.py | 56 ++ backend/services/ai_config.py | 12 + backend/services/ai_service.py | 187 ++++ backend/services/ai_tools.py | 234 +++++ package.json | 2 + pnpm-lock.yaml | 1337 +++++++++++++++++++++++++++++ src/components/Mermaid.tsx | 53 ++ src/lib/ai-analysis-api.ts | 59 ++ src/routeTree.gen.ts | 68 ++ src/routes/ai-analysis.$id.tsx | 138 +++ src/routes/ai-analysis._index.tsx | 103 +++ src/routes/ai-analysis.tsx | 29 + src/routes/index.tsx | 8 +- src/styles.css | 93 ++ 17 files changed, 2525 insertions(+), 4 deletions(-) create mode 100644 backend/routes/ai_analysis.py create mode 100644 backend/services/ai_collector.py create mode 100644 backend/services/ai_config.py create mode 100644 backend/services/ai_service.py create mode 100644 backend/services/ai_tools.py create mode 100644 src/components/Mermaid.tsx create mode 100644 src/lib/ai-analysis-api.ts mode change 100755 => 100644 src/routeTree.gen.ts create mode 100644 src/routes/ai-analysis.$id.tsx create mode 100644 src/routes/ai-analysis._index.tsx create mode 100644 src/routes/ai-analysis.tsx diff --git a/backend/database.py b/backend/database.py index 3453b62..fbec163 100644 --- a/backend/database.py +++ b/backend/database.py @@ -71,6 +71,20 @@ CREATE TABLE IF NOT EXISTS daily_top_themes ( created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')), UNIQUE(trade_date, theme_code) ); + +CREATE TABLE IF NOT EXISTS ai_reports ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + trade_date TEXT NOT NULL, + report_type TEXT NOT NULL DEFAULT 'daily', + title TEXT NOT NULL, + content TEXT NOT NULL, + summary TEXT, + tools_used TEXT, + model TEXT NOT NULL, + tokens_used INTEGER, + created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')), + UNIQUE(trade_date, report_type) +); """ diff --git a/backend/main.py b/backend/main.py index f3e42d4..4700e51 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,8 +7,9 @@ from contextlib import asynccontextmanager from dotenv import load_dotenv from database import init_db -from routes import stock, collections, shares, themes, core_stocks, fuyao, market_dashboard +from routes import stock, collections, shares, themes, core_stocks, fuyao, market_dashboard, ai_analysis from services.daily_collector import collector_loop, cache_cleanup_loop +from services.ai_collector import ai_analysis_loop load_dotenv() @@ -18,12 +19,13 @@ async def lifespan(app: FastAPI): init_db() collector_task = asyncio.create_task(collector_loop()) cache_cleanup_task = asyncio.create_task(cache_cleanup_loop()) + ai_analysis_task = asyncio.create_task(ai_analysis_loop()) try: yield finally: - for t in (collector_task, cache_cleanup_task): + for t in (collector_task, cache_cleanup_task, ai_analysis_task): t.cancel() - for t in (collector_task, cache_cleanup_task): + for t in (collector_task, cache_cleanup_task, ai_analysis_task): try: await t except asyncio.CancelledError: @@ -47,6 +49,7 @@ app.include_router(themes.router, prefix="/api/themes") app.include_router(core_stocks.router, prefix="/api/core-stocks") app.include_router(fuyao.router, prefix="/api/v2") app.include_router(market_dashboard.router, prefix="/api") +app.include_router(ai_analysis.router, prefix="/api") # 生产模式:后端同时托管前端静态文件 # catch-all 路由在 API 路由之后注册,所以 API 优先级更高 diff --git a/backend/routes/ai_analysis.py b/backend/routes/ai_analysis.py new file mode 100644 index 0000000..8d9dec7 --- /dev/null +++ b/backend/routes/ai_analysis.py @@ -0,0 +1,127 @@ +"""AI 分析报告路由(/api/ai-analysis) + +提供: +- 报告列表 +- 报告详情 +- 手动触发分析(12小时频率限制) +- 重新生成报告 +- 检查某日是否有报告 +""" + +import json +from datetime import datetime, timezone, timedelta + +from fastapi import APIRouter, HTTPException +from fastapi.responses import JSONResponse + +from database import get_connection, dict_from_row + +_CST = timezone(timedelta(hours=8)) +_NO_CACHE_HEADERS = {"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0"} + +router = APIRouter() + + +def _has_ai_report(trade_date: str) -> bool: + conn = get_connection() + try: + row = conn.execute( + "SELECT 1 FROM ai_reports WHERE trade_date = ? AND report_type = 'daily' LIMIT 1", + (trade_date,) + ).fetchone() + return row is not None + finally: + conn.close() + + +def _can_trigger(trade_date: str) -> tuple[bool, str]: + """检查是否可以触发分析(12小时频率限制)""" + conn = get_connection() + try: + row = conn.execute( + "SELECT created_at FROM ai_reports WHERE trade_date = ? ORDER BY id DESC LIMIT 1", + (trade_date,) + ).fetchone() + if row: + last_time = datetime.strptime(row["created_at"], "%Y-%m-%d %H:%M:%S") + now = datetime.now(_CST) + if (now - last_time).total_seconds() < 12 * 3600: + return False, "12小时内已触发过,请稍后再试" + return True, "" + finally: + conn.close() + + +@router.get("/ai-analysis", summary="AI 分析报告列表") +async def list_reports(): + conn = get_connection() + try: + rows = conn.execute( + "SELECT id, trade_date, report_type, title, summary, tools_used, model, tokens_used, created_at " + "FROM ai_reports ORDER BY trade_date DESC, id DESC LIMIT 50" + ).fetchall() + items = [] + for r in rows: + item = dict_from_row(r) + item["toolsUsed"] = json.loads(item.pop("tools_used") or "[]") + items.append(item) + return JSONResponse({"data": items}, headers=_NO_CACHE_HEADERS) + finally: + conn.close() + + +@router.get("/ai-analysis/{report_id}", summary="AI 分析报告详情") +async def get_report(report_id: int): + conn = get_connection() + try: + row = conn.execute("SELECT * FROM ai_reports WHERE id = ?", (report_id,)).fetchone() + if not row: + raise HTTPException(status_code=404, detail="报告不存在") + item = dict_from_row(row) + item["toolsUsed"] = json.loads(item.pop("tools_used") or "[]") + return JSONResponse({"data": item}, headers=_NO_CACHE_HEADERS) + finally: + conn.close() + + +@router.get("/ai-analysis/check/{trade_date}", summary="检查某日是否有 AI 分析报告") +async def check_report(trade_date: str): + has = _has_ai_report(trade_date) + return JSONResponse({"data": {"hasReport": has, "tradeDate": trade_date}}) + + +@router.post("/ai-analysis/trigger", summary="手动触发今日 AI 分析") +async def trigger_analysis(): + now = datetime.now(_CST) + trade_date = now.strftime("%Y-%m-%d") + + if _has_ai_report(trade_date): + raise HTTPException(status_code=400, detail="今日已有分析报告") + + can, msg = _can_trigger(trade_date) + if not can: + raise HTTPException(status_code=429, detail=msg) + + from services.ai_service import collect_ai_analysis + result = await collect_ai_analysis(trade_date) + return JSONResponse({"data": result}) + + +@router.post("/ai-analysis/{report_id}/regenerate", summary="重新生成 AI 分析报告") +async def regenerate_report(report_id: int): + conn = get_connection() + try: + row = conn.execute("SELECT trade_date FROM ai_reports WHERE id = ?", (report_id,)).fetchone() + if not row: + raise HTTPException(status_code=404, detail="报告不存在") + trade_date = row["trade_date"] + finally: + conn.close() + + can, msg = _can_trigger(trade_date) + if not can: + raise HTTPException(status_code=429, detail=msg) + + from services.ai_service import collect_ai_analysis + result = await collect_ai_analysis(trade_date) + return JSONResponse({"data": result}) diff --git a/backend/services/ai_collector.py b/backend/services/ai_collector.py new file mode 100644 index 0000000..ffdefc8 --- /dev/null +++ b/backend/services/ai_collector.py @@ -0,0 +1,56 @@ +"""AI 分析定时任务 + +收盘后自动触发 AI 分析: +- 15:01 collector_loop 采集核心股+题材数据 +- 15:10 ai_analysis_loop 触发 AI 分析(等数据采集完成) +""" + +import asyncio +import traceback +from datetime import datetime, time as dtime, timezone, timedelta +from typing import Optional + +from database import get_connection + +_CST = timezone(timedelta(hours=8)) + +CHECK_INTERVAL_SECONDS = 300 # 每5分钟检查 +AI_ANALYSIS_TIME = dtime(15, 10) # 15:10 后触发 + + +def _is_trading_day(d: datetime) -> bool: + """仅按工作日判断""" + return d.weekday() < 5 + + +def _has_ai_report(trade_date: str) -> bool: + """检查某日是否已有 AI 分析报告""" + conn = get_connection() + try: + row = conn.execute( + "SELECT 1 FROM ai_reports WHERE trade_date = ? AND report_type = 'daily' LIMIT 1", + (trade_date,) + ).fetchone() + return row is not None + finally: + conn.close() + + +async def ai_analysis_loop(stop: Optional[asyncio.Event] = None) -> None: + """后台循环:每个交易日 15:10 后自动触发 AI 分析(幂等)""" + while True: + try: + now = datetime.now(_CST) + if _is_trading_day(now) and now.time() >= AI_ANALYSIS_TIME: + trade_date = now.strftime("%Y-%m-%d") + if not _has_ai_report(trade_date): + print(f"[ai-collector] 开始 AI 分析 {trade_date}") + from services.ai_service import collect_ai_analysis + result = await collect_ai_analysis(trade_date) + print(f"[ai-collector] AI 分析完成 {trade_date},token 消耗: {result.get('tokens_used', 0)}") + except Exception: + print("[ai-collector] AI 分析异常:") + traceback.print_exc() + if stop is not None and stop.is_set(): + break + await asyncio.sleep(CHECK_INTERVAL_SECONDS) diff --git a/backend/services/ai_config.py b/backend/services/ai_config.py new file mode 100644 index 0000000..93a1afb --- /dev/null +++ b/backend/services/ai_config.py @@ -0,0 +1,12 @@ +"""AI 分析配置管理(从环境变量读取)""" + +import os +from dotenv import load_dotenv + +load_dotenv() + +AI_API_BASE = os.getenv("AI_API_BASE", "https://openteam.oneisall.xyz/v1") +AI_API_KEY = os.getenv("AI_API_KEY", "") +AI_MODEL = os.getenv("AI_MODEL", "deepseek-v4-flash") +AI_MAX_TOKENS = int(os.getenv("AI_MAX_TOKENS", "4096")) +AI_TEMPERATURE = float(os.getenv("AI_TEMPERATURE", "0.7")) diff --git a/backend/services/ai_service.py b/backend/services/ai_service.py new file mode 100644 index 0000000..26eb81e --- /dev/null +++ b/backend/services/ai_service.py @@ -0,0 +1,187 @@ +"""AI 分析核心服务 + +负责: +1. 调用 OpenAI 兼容 API 进行分析 +2. Function Calling 循环(AI 可主动获取数据) +3. 保存报告到数据库 +""" + +import json +import httpx +from datetime import datetime, timezone, timedelta + +from services.ai_config import AI_API_BASE, AI_API_KEY, AI_MODEL, AI_MAX_TOKENS, AI_TEMPERATURE +from services.ai_tools import TOOLS, execute_tool +from database import get_connection + +_CST = timezone(timedelta(hours=8)) + +SYSTEM_PROMPT = """你是一位专业的A股市场分析师,擅长从数据中发现投资机会。 + +你的分析风格: +- 数据驱动,基于真实数据而非主观臆断 +- 逻辑清晰,先总后分,层层递进 +- 观点明确,给出具体的操作建议 +- 风险提示,每次推荐都需说明风险点 + +可用工具: +- get_market_dashboard: 获取市场整体数据(指数/涨跌统计/行业强度/事件情报/市场温度) +- get_theme_history: 获取指定日期的题材涨幅排行 +- get_active_core_stocks: 获取核心股追踪数据(10日涨幅矩阵+所属题材) +- get_stock_quote: 获取个股实时行情 +- get_fund_flow: 获取个股资金流向 + +重要规则: +1. 你必须先调用工具获取数据,然后基于数据进行分析 +2. 不要凭空编造数据,所有数据必须来自工具返回 +3. 如果工具返回空数据,如实说明数据不可用 +4. 分析完成后给出明确的结论和建议""" + +DAILY_ANALYSIS_PROMPT = """请对 {trade_date} 的A股市场进行收盘分析,生成一份完整的分析报告。 + +请先调用以下工具获取数据: +1. get_market_dashboard - 获取市场整体数据 +2. get_theme_history(date="{trade_date}") - 获取今日题材涨幅 +3. get_active_core_stocks - 获取核心股数据 + +然后基于数据生成报告,结构如下: + +## 一、市场总览 +- 主要指数表现(上证、深证、创业板) +- 涨跌家数统计 +- 市场温度评估 + +## 二、题材热点分析 +- 今日涨幅前5题材 +- 持续活跃的题材 +- 新兴热点题材 + +## 三、核心股追踪 +- 连板股分析 +- 核心股表现 +- 龙头股辨识 + +## 四、关注方向 +- 明日值得关注的题材方向 +- 潜在的交易机会 + +## 五、风险提示 +- 需要警惕的风险因素 +- 操作建议 + +请用 Markdown 格式输出,适当使用表格展示数据对比。""" + + +async def call_llm(messages: list, tools: list = None) -> dict: + """调用 OpenAI 兼容 API""" + async with httpx.AsyncClient() as client: + payload = { + "model": AI_MODEL, + "messages": messages, + "max_tokens": AI_MAX_TOKENS, + "temperature": AI_TEMPERATURE, + } + if tools: + payload["tools"] = tools + payload["tool_choice"] = "auto" + + resp = await client.post( + f"{AI_API_BASE}/chat/completions", + headers={ + "Authorization": f"Bearer {AI_API_KEY}", + "Content-Type": "application/json", + }, + json=payload, + timeout=120, + ) + resp.raise_for_status() + return resp.json() + + +async def collect_ai_analysis(trade_date: str) -> dict: + """AI 分析主流程 + + Args: + trade_date: 交易日 YYYY-MM-DD + + Returns: + {"id": int, "tokens_used": int, "tools_used": list} + """ + messages = [ + {"role": "system", "content": SYSTEM_PROMPT}, + {"role": "user", "content": DAILY_ANALYSIS_PROMPT.format(trade_date=trade_date)} + ] + + tools_used = [] + total_tokens = 0 + final_content = "" + was_truncated = False + + for i in range(8): + response = await call_llm(messages, tools=TOOLS) + total_tokens += response.get("usage", {}).get("total_tokens", 0) + + choice = response["choices"][0] + message = choice["message"] + messages.append(message) + + finish_reason = choice.get("finish_reason", "") + + if finish_reason == "stop": + final_content = message.get("content") or "" + break + + if finish_reason == "length": + # 响应被截断,保存已生成的内容 + was_truncated = True + final_content = message.get("content") or "" + if not final_content: + # 如果当前消息没有内容,尝试从之前的消息中提取 + for msg in reversed(messages): + if msg.get("role") == "assistant" and msg.get("content"): + final_content = msg["content"] + break + print(f"[ai-service] 警告:响应被截断 (finish_reason=length)") + break + + if finish_reason == "tool_calls": + for tool_call in message.get("tool_calls", []): + func_name = tool_call["function"]["name"] + func_args = json.loads(tool_call["function"]["arguments"]) + tools_used.append(func_name) + + result = await execute_tool(func_name, func_args) + messages.append({ + "role": "tool", + "tool_call_id": tool_call["id"], + "content": result + }) + + summary = final_content[:200].replace("\n", " ") if final_content else "" + report_id = _save_report(trade_date, final_content, summary, tools_used, total_tokens) + + return {"id": report_id, "tokens_used": total_tokens, "tools_used": tools_used, "truncated": was_truncated} + + +def _save_report(trade_date: str, content: str, summary: str, tools_used: list, tokens_used: int) -> int: + """保存报告到数据库""" + conn = get_connection() + try: + cursor = conn.execute( + """INSERT OR REPLACE INTO ai_reports + (trade_date, report_type, title, content, summary, tools_used, model, tokens_used) + VALUES (?, 'daily', ?, ?, ?, ?, ?, ?)""", + ( + trade_date, + f"{trade_date} A股收盘分析", + content, + summary, + json.dumps(tools_used), + AI_MODEL, + tokens_used, + ) + ) + conn.commit() + return cursor.lastrowid + finally: + conn.close() diff --git a/backend/services/ai_tools.py b/backend/services/ai_tools.py new file mode 100644 index 0000000..96f99ea --- /dev/null +++ b/backend/services/ai_tools.py @@ -0,0 +1,234 @@ +"""Function Calling 工具定义和执行器 + +让 AI 可以主动调用现有 API 获取市场数据: +- get_market_dashboard: 市场看板 +- get_theme_history: 题材热点历史 +- get_active_core_stocks: 核心股追踪 +- get_stock_quote: 个股实时行情 +- get_fund_flow: 资金流向 +""" + +import json +from services.ai_config import AI_API_BASE, AI_API_KEY, AI_MODEL, AI_MAX_TOKENS, AI_TEMPERATURE + +TOOLS = [ + { + "type": "function", + "function": { + "name": "get_market_dashboard", + "description": "获取A股市场看板数据,包含主要指数行情、涨跌统计、行业强度、概念热度、事件情报、市场温度评分", + "parameters": {"type": "object", "properties": {}, "required": []} + } + }, + { + "type": "function", + "function": { + "name": "get_theme_history", + "description": "获取指定交易日的题材涨幅排行前20", + "parameters": { + "type": "object", + "properties": { + "date": {"type": "string", "description": "交易日 YYYY-MM-DD"} + }, + "required": ["date"] + } + } + }, + { + "type": "function", + "function": { + "name": "get_active_core_stocks", + "description": "获取活跃核心股列表,包含最近10日涨幅矩阵和所属题材", + "parameters": {"type": "object", "properties": {}, "required": []} + } + }, + { + "type": "function", + "function": { + "name": "get_stock_quote", + "description": "获取单只股票实时行情(价格、涨跌幅、成交量、换手率等)", + "parameters": { + "type": "object", + "properties": { + "code": {"type": "string", "description": "6位股票代码,如 600519"} + }, + "required": ["code"] + } + } + }, + { + "type": "function", + "function": { + "name": "get_fund_flow", + "description": "获取个股资金流向数据(主力净流入、超大单/大单/中单/小单流入流出)", + "parameters": { + "type": "object", + "properties": { + "code": {"type": "string", "description": "6位股票代码"}, + "name": {"type": "string", "description": "股票名称"}, + "days": {"type": "integer", "description": "获取天数,默认30"} + }, + "required": ["code", "name"] + } + } + } +] + + +async def execute_tool(tool_name: str, arguments: dict) -> str: + """执行工具调用,返回 JSON 字符串""" + try: + if tool_name == "get_market_dashboard": + return await _get_market_dashboard() + elif tool_name == "get_theme_history": + return await _get_theme_history(arguments.get("date", "")) + elif tool_name == "get_active_core_stocks": + return await _get_active_core_stocks() + elif tool_name == "get_stock_quote": + return await _get_stock_quote(arguments.get("code", "")) + elif tool_name == "get_fund_flow": + return await _get_fund_flow( + arguments.get("code", ""), + arguments.get("name", ""), + arguments.get("days", 30) + ) + else: + return json.dumps({"error": f"未知工具: {tool_name}"}) + except Exception as e: + return json.dumps({"error": str(e)}) + + +async def _get_market_dashboard() -> str: + """获取市场看板数据""" + from routes.market_dashboard import _build_dashboard + data = await _build_dashboard() + return json.dumps(data, ensure_ascii=False, default=str) + + +async def _get_theme_history(date: str) -> str: + """获取指定交易日题材涨幅前20""" + from database import get_connection + conn = get_connection() + try: + rows = conn.execute( + "SELECT * FROM daily_top_themes WHERE trade_date = ? ORDER BY rank ASC", + (date,) + ).fetchall() + items = [dict(r) for r in rows] + return json.dumps({"date": date, "items": items}, ensure_ascii=False) + finally: + conn.close() + + +async def _get_active_core_stocks() -> str: + """获取活跃核心股列表""" + from database import get_connection + from datetime import date as date_cls + conn = get_connection() + try: + # 最近10个有数据的交易日 + rows = conn.execute( + "SELECT DISTINCT trade_date FROM daily_core_stocks ORDER BY trade_date DESC LIMIT 10" + ).fetchall() + dates = [r["trade_date"] for r in reversed(rows)] + + if not dates: + return json.dumps({"dates": [], "stocks": []}, ensure_ascii=False) + + placeholders = ",".join("?" * len(dates)) + rows = conn.execute( + f"""SELECT trade_date, stock_code, stock_name, f3, cover_count FROM daily_core_stocks + WHERE trade_date IN ({placeholders}) + ORDER BY trade_date DESC, rank ASC""", + dates, + ).fetchall() + + stock_days = {} + for r in rows: + code = r["stock_code"] + s = stock_days.setdefault(code, { + "stockCode": code, + "stockName": r["stock_name"], + "coverCount": r["cover_count"], + "dailyGains": {}, + "appearCount": 0, + "lastAppear": None, + }) + s["dailyGains"][r["trade_date"]] = r["f3"] + s["appearCount"] += 1 + if s["lastAppear"] is None or r["trade_date"] > s["lastAppear"]: + s["lastAppear"] = r["trade_date"] + + stocks = list(stock_days.values()) + stocks.sort(key=lambda x: x.get("lastAppear") or "", reverse=True) + stocks.sort(key=lambda x: -x["appearCount"]) + + themes_rows = conn.execute( + f"""SELECT stock_code, theme_code, theme_name FROM daily_core_stock_themes + WHERE trade_date IN ({placeholders})""", + dates, + ).fetchall() + themes_by_stock = {} + for t in themes_rows: + per = themes_by_stock.setdefault(t["stock_code"], {}) + per.setdefault(t["theme_code"], {"theme_code": t["theme_code"], "theme_name": t["theme_name"]}) + for s in stocks: + s["themes"] = list(themes_by_stock.get(s["stockCode"], {}).values()) + + latest = dates[-1] if dates else None + for s in stocks: + if s.get("lastAppear") and latest: + try: + d1 = date_cls.fromisoformat(latest) + d2 = date_cls.fromisoformat(s["lastAppear"]) + s["daysSinceLastAppear"] = (d1 - d2).days + except ValueError: + s["daysSinceLastAppear"] = 0 + else: + s["daysSinceLastAppear"] = 0 + + return json.dumps({"dates": dates, "stocks": stocks}, ensure_ascii=False) + finally: + conn.close() + + +async def _get_stock_quote(code: str) -> str: + """获取个股实时行情""" + from services.tencent import fetch_quote + data = await fetch_quote(code) + if not data: + return json.dumps({"error": f"未找到股票 {code} 的数据"}) + return json.dumps(data, ensure_ascii=False) + + +async def _get_fund_flow(code: str, name: str, days: int) -> str: + """获取个股资金流向""" + from services.tencent import fetch_quote, get_market_prefix + from database import get_connection + + # 先尝试从本地数据库获取历史资金流向 + conn = get_connection() + try: + rows = conn.execute( + "SELECT * FROM cache WHERE key LIKE ? AND expires_at > datetime('now','localtime')", + (f"fund_flow:{code}%",) + ).fetchall() + if rows: + return rows[0]["value"] + finally: + conn.close() + + # 降级:通过腾讯获取基础行情数据 + quote = await fetch_quote(code) + if not quote: + return json.dumps({"error": f"未找到股票 {code} 的数据"}) + + return json.dumps({ + "code": code, + "name": name or quote.get("name", ""), + "currentPrice": quote.get("currentPrice", 0), + "changePercent": quote.get("changePercent", 0), + "volume": quote.get("volume", 0), + "amount": quote.get("amount", 0), + "note": "资金流向详细数据需通过东方财富API获取" + }, ensure_ascii=False) diff --git a/package.json b/package.json index 61282e2..95cb844 100755 --- a/package.json +++ b/package.json @@ -51,10 +51,12 @@ "input-otp": "^1.4.2", "lightweight-charts": "^5.2.1", "lucide-react": "^0.575.0", + "mermaid": "^11.17.2", "react": "^19.2.7", "react-day-picker": "^9.14.0", "react-dom": "^19.2.7", "react-hook-form": "^7.81.0", + "react-markdown": "^10.1.0", "react-resizable-panels": "^4.12.1", "recharts": "^2.15.4", "sonner": "^2.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f1e7fa..43e9e2e 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,6 +131,9 @@ importers: lucide-react: specifier: ^0.575.0 version: 0.575.0(react@19.2.7) + mermaid: + specifier: ^11.17.2 + version: 11.17.2 react: specifier: ^19.2.7 version: 19.2.7 @@ -143,6 +146,9 @@ importers: react-hook-form: specifier: ^7.81.0 version: 7.81.0(react@19.2.7) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.2.17)(react@19.2.7) react-resizable-panels: specifier: ^4.12.1 version: 4.12.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -195,6 +201,9 @@ importers: packages: + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -282,6 +291,12 @@ packages: resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + '@date-fns/tz@1.5.0': resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==} @@ -461,6 +476,12 @@ packages: peerDependencies: react-hook-form: ^7.55.0 + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.4': + resolution: {integrity: sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -477,6 +498,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mermaid-js/parser@1.2.1': + resolution: {integrity: sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==} + '@radix-ui/number@1.1.2': resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} @@ -1402,36 +1426,117 @@ packages: '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + '@types/d3-color@3.1.3': resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + '@types/d3-ease@3.0.2': resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + '@types/d3-force@3.0.10': resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.1': + resolution: {integrity: sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} '@types/d3-path@3.1.1': resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + '@types/d3-scale@4.0.9': resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + '@types/d3-shape@3.1.8': resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + '@types/d3-time@3.0.4': resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} '@types/d3-timer@3.0.2': resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@22.20.0': resolution: {integrity: sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==} @@ -1443,6 +1548,21 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + '@vitejs/plugin-react@5.2.0': resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1460,6 +1580,9 @@ packages: babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + baseline-browser-mapping@2.10.42: resolution: {integrity: sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==} engines: {node: '>=6.0.0'} @@ -1473,6 +1596,21 @@ packages: caniuse-lite@1.0.30001800: resolution: {integrity: sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -1490,31 +1628,98 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@7.0.0: + resolution: {integrity: sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.34.2: + resolution: {integrity: sha512-Cm2jaj1X/PBNlzV9yH8zcfGOxO7U+CJ/+mxSBVPSchLaugdp4jtlGx5qaHtPRZ6tgiZ5P+o1XoRfJA+ba6KM3g==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + d3-dispatch@3.0.1: resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} engines: {node: '>=12'} + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + d3-force@3.0.0: resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} engines: {node: '>=12'} @@ -1523,22 +1728,55 @@ packages: resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} engines: {node: '>=12'} + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + d3-path@3.1.0: resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} engines: {node: '>=12'} + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + d3-quadtree@3.0.1: resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} engines: {node: '>=12'} + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + d3-scale@4.0.2: resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} engines: {node: '>=12'} + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + d3-shape@3.2.0: resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} engines: {node: '>=12'} @@ -1555,12 +1793,32 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} + date-fns-jalali@4.1.0-0: resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} date-fns@4.4.0: resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} + dayjs@1.11.23: + resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1573,6 +1831,16 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1580,6 +1848,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -1587,6 +1858,9 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dompurify@3.4.14: + resolution: {integrity: sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==} + electron-to-chromium@1.5.387: resolution: {integrity: sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==} @@ -1607,6 +1881,9 @@ packages: resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} + es-toolkit@1.52.0: + resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} + esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} @@ -1616,6 +1893,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + eventemitter3@4.0.1: resolution: {integrity: sha512-MnI0l35oYL2C/c80rjJN7qu50MDx39yYE7y7oYck2YA3v+y7EaAenY8IU8AP4d1RWqE8VAKWFGSh3rfP87ll3g==} @@ -1626,6 +1906,9 @@ packages: resolution: {integrity: sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==} engines: {node: '>=6.0.0'} + fastdom@1.0.12: + resolution: {integrity: sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1668,16 +1951,57 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + iconv-lite@0.6.0: + resolution: {integrity: sha512-43ZpGYZ9QtuutX5l6WC1DSO8ane9N+Ct5qPLF2OV7vM9abM69gnAbVkh66ibaZd3aOGkoP1ZmringlKhLBkw2Q==} + engines: {node: '>=0.10.0'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + inline-style-parser@0.1.1: + resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + input-otp@1.5.0: resolution: {integrity: sha512-3AcfdW1sNG0FmSA5hHMBXG7jNW5CdcdKs8ln8JOmn6S2SRkoXoJx+2UwC+ZvjflfnOdHJx0SfQ9/YjlxVYLtGQ==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + isbot@5.1.44: resolution: {integrity: sha512-PGEHtwMnKbZpeSEXW2Utx+/JWed7dp6DiH0WWg33vGSDA7RUvpUeJSVlLrVkQ1RCpvDOUc/eH9ql7VsdbBZ8pA==} engines: {node: '>=18'} @@ -1699,6 +2023,19 @@ packages: engines: {node: '>=6'} hasBin: true + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1776,9 +2113,15 @@ packages: lightweight-charts@5.2.1: resolution: {integrity: sha512-IVwoK1RLFiLPubaKIjNbtjWLnpPMqiABSrTay6whmNa8L1+19292VtHJ+BWyPUuLCwF0tcQlhEWd1CLB2a1nsQ==} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1794,6 +2137,101 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + marked@16.3.0: + resolution: {integrity: sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==} + engines: {node: '>= 20'} + hasBin: true + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.0.0: + resolution: {integrity: sha512-Ov3aWCYpb31/SkHNRlREvSZsF9ETBW/rXw4PooawZO8qy2MviDq4TI6kxX6zFmGa/zUX36STKIC/IpASQk596w==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mermaid@11.17.2: + resolution: {integrity: sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==} + + micromark-core-commonmark@2.0.0: + resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==} + + micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + + micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + + micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + + micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + + micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + + micromark-util-character@2.0.0: + resolution: {integrity: sha512-DtHHqgTRz32ylwiGSZwoK+BTx4gaq//ig71g20pwBwRLxo3CfInyD+NyTrriLOhPz5WQOfAOSLLbKOaaGXWz9Q==} + + micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + + micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + + micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + + micromark-util-decode-numeric-character-reference@2.0.0: + resolution: {integrity: sha512-pIgcsGxpHEtTG/rPJRz/HOLSqp5VTuIIjXlPI+6JSDlK2oljApusG6KzpS8AF0ENUMCHlC/IBb5B9xdFiVlm5Q==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + + micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + + micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + + micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + + micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + + micromark-util-subtokenize@2.0.0: + resolution: {integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==} + + micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + + micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + + micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + motion-dom@11.18.1: resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==} @@ -1816,6 +2254,15 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1826,6 +2273,12 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + postcss@8.5.16: resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} engines: {node: ^10 || ^12 || >=14} @@ -1838,6 +2291,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + react-day-picker@9.14.0: resolution: {integrity: sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==} engines: {node: '>=18'} @@ -1861,6 +2317,12 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-refresh@0.18.0: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} @@ -1931,11 +2393,29 @@ packages: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rollup@4.62.2: resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -1967,6 +2447,24 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + strictdom@1.0.1: + resolution: {integrity: sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + style-to-js@1.0.0: + resolution: {integrity: sha512-OiaJVLD+ybcul+6oAdwTOKLFewvYapPLG4H3JiYBYUS0yoi29Ag1IiqprbVARaxiAXvkk+4B8aNcG7lyEDKebg==} + + style-to-object@0.3.0: + resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==} + + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + tailwind-merge@3.6.0: resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} @@ -1980,10 +2478,24 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -2013,6 +2525,24 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unified@11.0.0: + resolution: {integrity: sha512-65zgPyv0vyXnJpw4fbGmoXjP0/6cBmnnesl4lSPGU2tYuzLWtuicRBFcaV6kgzitGrBHj6pgXYomMw1VQe/cQg==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + unplugin@3.3.0: resolution: {integrity: sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2077,12 +2607,22 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uuid@14.0.2: + resolution: {integrity: sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==} + hasBin: true + vaul@1.1.2: resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} @@ -2143,8 +2683,16 @@ packages: zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.8.0 + tinyexec: 1.3.0 + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -2259,6 +2807,10 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 + '@braintree/sanitize-url@7.1.2': {} + + '@chevrotain/types@11.1.2': {} + '@date-fns/tz@1.5.0': {} '@esbuild/aix-ppc64@0.28.1': @@ -2361,6 +2913,14 @@ snapshots: '@standard-schema/utils': 0.3.0 react-hook-form: 7.81.0(react@19.2.7) + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.4': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2380,6 +2940,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mermaid-js/parser@1.2.1': + dependencies: + '@chevrotain/types': 11.1.2 + '@radix-ui/number@1.1.2': {} '@radix-ui/primitive@1.1.4': {} @@ -3281,32 +3845,143 @@ snapshots: '@types/d3-array@3.2.2': {} + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + '@types/d3-color@3.1.3': {} + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + '@types/d3-ease@3.0.2': {} + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + '@types/d3-force@3.0.10': {} + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.1': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 '@types/d3-path@3.1.1': {} + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.4': {} + + '@types/d3-scale-chromatic@3.1.0': {} + '@types/d3-scale@4.0.9': dependencies: '@types/d3-time': 3.0.4 + '@types/d3-selection@3.0.11': {} + '@types/d3-shape@3.1.8': dependencies: '@types/d3-path': 3.1.1 + '@types/d3-time-format@4.0.3': {} + '@types/d3-time@3.0.4': {} '@types/d3-timer@3.0.2': {} + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.1 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + '@types/estree@1.0.9': {} + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + '@types/node@22.20.0': dependencies: undici-types: 6.21.0 @@ -3319,6 +3994,20 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.4.0': {} + + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@vitejs/plugin-react@5.2.0(vite@7.3.6(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.5))': dependencies: '@babel/core': 7.29.7 @@ -3346,6 +4035,8 @@ snapshots: transitivePeerDependencies: - supports-color + bail@2.0.2: {} + baseline-browser-mapping@2.10.42: {} browserslist@4.28.4: @@ -3358,6 +4049,16 @@ snapshots: caniuse-lite@1.0.30001800: {} + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + chokidar@5.0.0: dependencies: readdirp: 5.0.0 @@ -3380,22 +4081,89 @@ snapshots: - '@types/react' - '@types/react-dom' + comma-separated-tokens@2.0.3: {} + + commander@7.0.0: {} + + commander@8.3.0: {} + convert-source-map@2.0.0: {} cookie-es@3.1.1: {} + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.2): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.34.2 + + cytoscape-fcose@2.2.0(cytoscape@3.34.2): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.34.2 + + cytoscape@3.34.2: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + d3-array@3.2.4: dependencies: internmap: 2.0.3 + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + d3-color@3.1.0: {} + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + d3-dispatch@3.0.1: {} + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.0.0 + iconv-lite: 0.6.0 + rw: 1.3.3 + d3-ease@3.0.1: {} + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + d3-force@3.0.0: dependencies: d3-dispatch: 3.0.1 @@ -3404,14 +4172,36 @@ snapshots: d3-format@3.1.2: {} + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 + d3-path@1.0.9: {} + d3-path@3.1.0: {} + d3-polygon@3.0.1: {} + d3-quadtree@3.0.1: {} + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + d3-scale@4.0.2: dependencies: d3-array: 3.2.4 @@ -3420,6 +4210,12 @@ snapshots: d3-time: 3.1.0 d3-time-format: 4.1.0 + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + d3-shape@3.2.0: dependencies: d3-path: 3.1.0 @@ -3434,20 +4230,91 @@ snapshots: d3-timer@3.0.1: {} + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + date-fns-jalali@4.1.0-0: {} date-fns@4.4.0: {} + dayjs@1.11.23: {} + debug@4.4.3: dependencies: ms: 2.1.3 decimal.js-light@2.5.1: {} + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + + dequal@2.0.3: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diff@8.0.4: {} dom-helpers@5.2.1: @@ -3455,6 +4322,10 @@ snapshots: '@babel/runtime': 7.29.7 csstype: 3.2.3 + dompurify@3.4.14: + optionalDependencies: + '@types/trusted-types': 2.0.7 + electron-to-chromium@1.5.387: {} embla-carousel-react@8.6.0(react@19.2.7): @@ -3474,6 +4345,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + es-toolkit@1.52.0: {} + esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -3505,12 +4378,18 @@ snapshots: escalade@3.2.0: {} + estree-util-is-identifier-name@3.0.0: {} + eventemitter3@4.0.1: {} fancy-canvas@2.1.0: {} fast-equals@5.4.1: {} + fastdom@1.0.12: + dependencies: + strictdom: 1.0.1 + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 @@ -3535,13 +4414,64 @@ snapshots: graceful-fs@4.2.11: {} + hachure-fill@0.5.2: {} + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.0.0 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + html-url-attributes@3.0.1: {} + + iconv-lite@0.6.0: + dependencies: + safer-buffer: 2.1.2 + + import-meta-resolve@4.2.0: {} + + inline-style-parser@0.1.1: {} + input-otp@1.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) + internmap@1.0.1: {} + internmap@2.0.3: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-hexadecimal@2.0.1: {} + + is-plain-obj@4.1.0: {} + isbot@5.1.44: {} jiti@2.7.0: {} @@ -3552,6 +4482,16 @@ snapshots: json5@2.2.3: {} + katex@0.16.47: + dependencies: + commander: 8.3.0 + + khroma@2.1.0: {} + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + lightningcss-android-arm64@1.32.0: optional: true @@ -3605,8 +4545,12 @@ snapshots: dependencies: fancy-canvas: 2.1.0 + lodash-es@4.18.1: {} + lodash@4.18.1: {} + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -3623,6 +4567,254 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + marked@16.3.0: {} + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.0 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.0.0 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.4.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mermaid@11.17.2: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.4 + '@mermaid-js/parser': 1.2.1 + '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.34.2 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.2) + cytoscape-fcose: 2.2.0(cytoscape@3.34.2) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.23 + dompurify: 3.4.14 + es-toolkit: 1.52.0 + fastdom: 1.0.12 + katex: 0.16.47 + khroma: 2.1.0 + marked: 16.3.0 + roughjs: 4.6.6 + stylis: 4.4.0 + ts-dedent: 2.3.0 + uuid: 14.0.2 + + micromark-core-commonmark@2.0.0: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.0 + micromark-factory-label: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-factory-title: 2.0.0 + micromark-factory-whitespace: 2.0.0 + micromark-util-character: 2.0.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-html-tag-name: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-subtokenize: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-destination@2.0.0: + dependencies: + micromark-util-character: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-label@2.0.0: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-space@2.0.0: + dependencies: + micromark-util-character: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-title@2.0.0: + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-whitespace@2.0.0: + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-character@2.0.0: + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-chunked@2.0.0: + dependencies: + micromark-util-symbol: 2.0.0 + + micromark-util-classify-character@2.0.0: + dependencies: + micromark-util-character: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-combine-extensions@2.0.0: + dependencies: + micromark-util-chunked: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-decode-numeric-character-reference@2.0.0: + dependencies: + micromark-util-symbol: 2.0.0 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.0 + micromark-util-symbol: 2.0.0 + + micromark-util-encode@2.0.0: {} + + micromark-util-html-tag-name@2.0.0: {} + + micromark-util-normalize-identifier@2.0.0: + dependencies: + micromark-util-symbol: 2.0.0 + + micromark-util-resolve-all@2.0.0: + dependencies: + micromark-util-types: 2.0.0 + + micromark-util-sanitize-uri@2.0.0: + dependencies: + micromark-util-character: 2.0.0 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 + + micromark-util-subtokenize@2.0.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-symbol@2.0.0: {} + + micromark-util-types@2.0.0: {} + + micromark@4.0.0: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.0.0 + micromark-util-chunked: 2.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.0 + micromark-util-encode: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-subtokenize: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + transitivePeerDependencies: + - supports-color + motion-dom@11.18.1: dependencies: motion-utils: 11.18.1 @@ -3637,12 +4829,33 @@ snapshots: object-assign@4.1.1: {} + package-manager-detector@1.8.0: {} + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + path-data-parser@0.1.0: {} + pathe@2.0.3: {} picocolors@1.1.1: {} picomatch@4.0.5: {} + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + postcss@8.5.16: dependencies: nanoid: 3.3.15 @@ -3657,6 +4870,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@7.2.0: {} + react-day-picker@9.14.0(react@19.2.7): dependencies: '@date-fns/tz': 1.5.0 @@ -3678,6 +4893,24 @@ snapshots: react-is@18.3.1: {} + react-markdown@10.1.0(@types/react@19.2.17)(react@19.2.7): + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@types/react': 19.2.17 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.7 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-refresh@0.18.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.7): @@ -3750,6 +4983,25 @@ snapshots: tiny-invariant: 1.3.3 victory-vendor: 36.9.2 + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.0 + unified: 11.0.0 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.0 + vfile: 6.0.3 + + robust-predicates@3.0.3: {} + rollup@4.62.2: dependencies: '@types/estree': 1.0.9 @@ -3781,6 +5033,17 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.62.2 fsevents: 2.3.3 + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + scheduler@0.27.0: {} semver@6.3.1: {} @@ -3800,6 +5063,25 @@ snapshots: source-map-js@1.2.1: {} + space-separated-tokens@2.0.2: {} + + strictdom@1.0.1: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + style-to-js@1.0.0: + dependencies: + style-to-object: 0.3.0 + + style-to-object@0.3.0: + dependencies: + inline-style-parser: 0.1.1 + + stylis@4.4.0: {} + tailwind-merge@3.6.0: {} tailwindcss@4.3.2: {} @@ -3808,11 +5090,19 @@ snapshots: tiny-invariant@1.3.3: {} + tinyexec@1.3.0: {} + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + ts-dedent@2.3.0: {} + tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -3832,6 +5122,39 @@ snapshots: undici-types@6.21.0: {} + unified@11.0.0: + dependencies: + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.4.0 + bail: 2.0.2 + devlop: 1.1.0 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + unplugin@3.3.0(esbuild@0.28.1)(rollup@4.62.2)(vite@7.3.6(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.5)): dependencies: '@jridgewell/remapping': 2.3.5 @@ -3867,6 +5190,8 @@ snapshots: dependencies: react: 19.2.7 + uuid@14.0.2: {} + vaul@1.1.2(@types/react-dom@19.2.4(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@radix-ui/react-dialog': 1.1.18(@types/react-dom@19.2.4(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -3876,6 +5201,16 @@ snapshots: - '@types/react' - '@types/react-dom' + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + victory-vendor@36.9.2: dependencies: '@types/d3-array': 3.2.2 @@ -3925,3 +5260,5 @@ snapshots: zod@3.25.76: {} zod@4.4.3: {} + + zwitch@2.0.4: {} diff --git a/src/components/Mermaid.tsx b/src/components/Mermaid.tsx new file mode 100644 index 0000000..b7480bf --- /dev/null +++ b/src/components/Mermaid.tsx @@ -0,0 +1,53 @@ +import { useEffect, useRef, useState } from "react"; +import mermaid from "mermaid"; + +mermaid.initialize({ + startOnLoad: false, + theme: "dark", + themeVariables: { + primaryColor: "#21262d", + primaryTextColor: "#e6edf3", + primaryBorderColor: "#30363d", + lineColor: "#58a6ff", + secondaryColor: "#161b22", + tertiaryColor: "#0d1117", + fontFamily: "inherit", + fontSize: "14px", + }, +}); + +interface MermaidProps { + chart: string; +} + +export function Mermaid({ chart }: MermaidProps) { + const ref = useRef(null); + const [svg, setSvg] = useState(""); + const [error, setError] = useState(""); + + useEffect(() => { + if (!ref.current) return; + const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`; + + mermaid + .render(id, chart) + .then(({ svg }) => setSvg(svg)) + .catch((err) => setError(err.message || "图表渲染失败")); + }, [chart]); + + if (error) { + return ( +
+        {error}
+      
+ ); + } + + return ( +
+ ); +} diff --git a/src/lib/ai-analysis-api.ts b/src/lib/ai-analysis-api.ts new file mode 100644 index 0000000..10164af --- /dev/null +++ b/src/lib/ai-analysis-api.ts @@ -0,0 +1,59 @@ +// AI 分析报告 API 客户端 + +import { getApiBaseUrl } from "./api-client"; + +const API_BASE = getApiBaseUrl(); + +export interface AiReport { + id: number; + trade_date: string; + report_type: string; + title: string; + content: string; + summary: string | null; + toolsUsed: string[]; + model: string; + tokens_used: number; + created_at: string; +} + +export async function fetchAiReports(): Promise { + const resp = await fetch(`${API_BASE}/api/ai-analysis`); + if (!resp.ok) throw new Error(`请求失败 (${resp.status})`); + const result = await resp.json(); + return result.data || []; +} + +export async function fetchAiReport(id: number): Promise { + const resp = await fetch(`${API_BASE}/api/ai-analysis/${id}`); + if (!resp.ok) throw new Error(`请求失败 (${resp.status})`); + const result = await resp.json(); + return result.data; +} + +export async function checkAiReport(tradeDate: string): Promise { + const resp = await fetch(`${API_BASE}/api/ai-analysis/check/${tradeDate}`); + if (!resp.ok) return false; + const result = await resp.json(); + return result.data?.hasReport || false; +} + +export async function triggerAiAnalysis(): Promise<{ id: number; tokens_used: number }> { + const resp = await fetch(`${API_BASE}/api/ai-analysis/trigger`, { method: "POST" }); + if (!resp.ok) { + const err = await resp.json().catch(() => ({})); + throw new Error(err.detail || "触发失败"); + } + const result = await resp.json(); + return result.data; +} + +export async function regenerateAiReport(id: number): Promise<{ id: number; tokens_used: number }> { + const resp = await fetch(`${API_BASE}/api/ai-analysis/${id}/regenerate`, { method: "POST" }); + if (!resp.ok) { + const err = await resp.json().catch(() => ({})); + throw new Error(err.detail || "重新生成失败"); + } + const result = await resp.json(); + return result.data; +} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts old mode 100755 new mode 100644 index f3900f0..56f50ef --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -14,10 +14,13 @@ import { Route as ThemeHistoryRouteImport } from './routes/theme-history' import { Route as HotMapRouteImport } from './routes/hot-map' import { Route as DashboardRouteImport } from './routes/dashboard' import { Route as CoreStocksRouteImport } from './routes/core-stocks' +import { Route as AiAnalysisRouteImport } from './routes/ai-analysis' import { Route as IndexRouteImport } from './routes/index' import { Route as ThemeCodeRouteImport } from './routes/theme.$code' import { Route as StockCodeRouteImport } from './routes/stock.$code' import { Route as ShareCodeRouteImport } from './routes/share.$code' +import { Route as AiAnalysisIndexRouteImport } from './routes/ai-analysis._index' +import { Route as AiAnalysisIdRouteImport } from './routes/ai-analysis.$id' const ThemesRoute = ThemesRouteImport.update({ id: '/themes', @@ -44,6 +47,11 @@ const CoreStocksRoute = CoreStocksRouteImport.update({ path: '/core-stocks', getParentRoute: () => rootRouteImport, } as any) +const AiAnalysisRoute = AiAnalysisRouteImport.update({ + id: '/ai-analysis', + path: '/ai-analysis', + getParentRoute: () => rootRouteImport, +} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -64,25 +72,38 @@ const ShareCodeRoute = ShareCodeRouteImport.update({ path: '/share/$code', getParentRoute: () => rootRouteImport, } as any) +const AiAnalysisIndexRoute = AiAnalysisIndexRouteImport.update({ + id: '/_index', + getParentRoute: () => AiAnalysisRoute, +} as any) +const AiAnalysisIdRoute = AiAnalysisIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => AiAnalysisRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/ai-analysis': typeof AiAnalysisRouteWithChildren '/core-stocks': typeof CoreStocksRoute '/dashboard': typeof DashboardRoute '/hot-map': typeof HotMapRoute '/theme-history': typeof ThemeHistoryRoute '/themes': typeof ThemesRoute + '/ai-analysis/$id': typeof AiAnalysisIdRoute '/share/$code': typeof ShareCodeRoute '/stock/$code': typeof StockCodeRoute '/theme/$code': typeof ThemeCodeRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/ai-analysis': typeof AiAnalysisRouteWithChildren '/core-stocks': typeof CoreStocksRoute '/dashboard': typeof DashboardRoute '/hot-map': typeof HotMapRoute '/theme-history': typeof ThemeHistoryRoute '/themes': typeof ThemesRoute + '/ai-analysis/$id': typeof AiAnalysisIdRoute '/share/$code': typeof ShareCodeRoute '/stock/$code': typeof StockCodeRoute '/theme/$code': typeof ThemeCodeRoute @@ -90,11 +111,14 @@ export interface FileRoutesByTo { export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/ai-analysis': typeof AiAnalysisRouteWithChildren '/core-stocks': typeof CoreStocksRoute '/dashboard': typeof DashboardRoute '/hot-map': typeof HotMapRoute '/theme-history': typeof ThemeHistoryRoute '/themes': typeof ThemesRoute + '/ai-analysis/$id': typeof AiAnalysisIdRoute + '/ai-analysis/_index': typeof AiAnalysisIndexRoute '/share/$code': typeof ShareCodeRoute '/stock/$code': typeof StockCodeRoute '/theme/$code': typeof ThemeCodeRoute @@ -103,33 +127,40 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' + | '/ai-analysis' | '/core-stocks' | '/dashboard' | '/hot-map' | '/theme-history' | '/themes' + | '/ai-analysis/$id' | '/share/$code' | '/stock/$code' | '/theme/$code' fileRoutesByTo: FileRoutesByTo to: | '/' + | '/ai-analysis' | '/core-stocks' | '/dashboard' | '/hot-map' | '/theme-history' | '/themes' + | '/ai-analysis/$id' | '/share/$code' | '/stock/$code' | '/theme/$code' id: | '__root__' | '/' + | '/ai-analysis' | '/core-stocks' | '/dashboard' | '/hot-map' | '/theme-history' | '/themes' + | '/ai-analysis/$id' + | '/ai-analysis/_index' | '/share/$code' | '/stock/$code' | '/theme/$code' @@ -137,6 +168,7 @@ export interface FileRouteTypes { } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + AiAnalysisRoute: typeof AiAnalysisRouteWithChildren CoreStocksRoute: typeof CoreStocksRoute DashboardRoute: typeof DashboardRoute HotMapRoute: typeof HotMapRoute @@ -184,6 +216,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof CoreStocksRouteImport parentRoute: typeof rootRouteImport } + '/ai-analysis': { + id: '/ai-analysis' + path: '/ai-analysis' + fullPath: '/ai-analysis' + preLoaderRoute: typeof AiAnalysisRouteImport + parentRoute: typeof rootRouteImport + } '/': { id: '/' path: '/' @@ -212,11 +251,40 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ShareCodeRouteImport parentRoute: typeof rootRouteImport } + '/ai-analysis/_index': { + id: '/ai-analysis/_index' + path: '' + fullPath: '/ai-analysis' + preLoaderRoute: typeof AiAnalysisIndexRouteImport + parentRoute: typeof AiAnalysisRoute + } + '/ai-analysis/$id': { + id: '/ai-analysis/$id' + path: '/$id' + fullPath: '/ai-analysis/$id' + preLoaderRoute: typeof AiAnalysisIdRouteImport + parentRoute: typeof AiAnalysisRoute + } } } +interface AiAnalysisRouteChildren { + AiAnalysisIdRoute: typeof AiAnalysisIdRoute + AiAnalysisIndexRoute: typeof AiAnalysisIndexRoute +} + +const AiAnalysisRouteChildren: AiAnalysisRouteChildren = { + AiAnalysisIdRoute: AiAnalysisIdRoute, + AiAnalysisIndexRoute: AiAnalysisIndexRoute, +} + +const AiAnalysisRouteWithChildren = AiAnalysisRoute._addFileChildren( + AiAnalysisRouteChildren, +) + const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + AiAnalysisRoute: AiAnalysisRouteWithChildren, CoreStocksRoute: CoreStocksRoute, DashboardRoute: DashboardRoute, HotMapRoute: HotMapRoute, diff --git a/src/routes/ai-analysis.$id.tsx b/src/routes/ai-analysis.$id.tsx new file mode 100644 index 0000000..b8073a3 --- /dev/null +++ b/src/routes/ai-analysis.$id.tsx @@ -0,0 +1,138 @@ +import * as React from "react"; +import { Link, createFileRoute } from "@tanstack/react-router"; +import { ArrowLeft, BrainCircuit, Clock, Loader2, AlertCircle, RefreshCw, Wrench } from "lucide-react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import Markdown from "react-markdown"; +import { Mermaid } from "../components/Mermaid"; +import { fetchAiReport, regenerateAiReport } from "../lib/ai-analysis-api"; +import { Button } from "../components/ui/button"; +import { Card, CardContent } from "../components/ui/card"; +import { toast } from "sonner"; + +export const Route = createFileRoute("/ai-analysis/$id")({ + component: AiReportDetailPage, +}); + +function AiReportDetailPage() { + const { id } = Route.useParams(); + const queryClient = useQueryClient(); + const reportId = Number(id); + + const { data: report, isLoading, isError, refetch } = useQuery({ + queryKey: ["ai-report", reportId], + queryFn: () => fetchAiReport(reportId), + staleTime: 60_000, + retry: false, + }); + + const regenerateMutation = useMutation({ + mutationFn: () => regenerateAiReport(reportId), + onSuccess: () => { + toast.success("重新生成完成"); + queryClient.invalidateQueries({ queryKey: ["ai-report", reportId] }); + queryClient.invalidateQueries({ queryKey: ["ai-reports"] }); + }, + onError: (err: Error) => { + toast.error(err.message || "重新生成失败"); + }, + }); + + return ( +
+
+ {/* Header */} +
+
+ + + +

+ + {report?.title || "AI 分析报告"} +

+
+ +
+ + {/* Content */} + {isLoading ? ( +
+ +

加载中...

+
+ ) : isError ? ( +
+ +

报告加载失败

+ +
+ ) : report ? ( + <> + {/* Meta Info */} + + +
+ + + {report.created_at} + + {report.model} + {report.tokens_used?.toLocaleString()} tokens + {report.toolsUsed && report.toolsUsed.length > 0 && ( + + + {report.toolsUsed.length} 个工具 + + )} +
+
+
+ + {/* Report Content */} + + + ; + } + return ( + + {children} + + ); + }, + }} + > + {report.content} + + + + + {/* Disclaimer */} +

+ 本报告由 AI 生成,仅供参考,不构成投资建议 +

+ + ) : null} +
+
+ ); +} diff --git a/src/routes/ai-analysis._index.tsx b/src/routes/ai-analysis._index.tsx new file mode 100644 index 0000000..1dc63c6 --- /dev/null +++ b/src/routes/ai-analysis._index.tsx @@ -0,0 +1,103 @@ +import * as React from "react"; +import { Link, createFileRoute } from "@tanstack/react-router"; +import { Clock, Zap, Loader2, AlertCircle, BrainCircuit } from "lucide-react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { fetchAiReports, triggerAiAnalysis } from "../lib/ai-analysis-api"; +import { Button } from "../components/ui/button"; +import { Card, CardContent } from "../components/ui/card"; +import { toast } from "sonner"; + +export const Route = createFileRoute("/ai-analysis/_index")({ + component: AiAnalysisIndex, +}); + +function AiAnalysisIndex() { + const queryClient = useQueryClient(); + + const { data: reports, isLoading, isError, refetch } = useQuery({ + queryKey: ["ai-reports"], + queryFn: fetchAiReports, + staleTime: 30_000, + retry: false, + }); + + const triggerMutation = useMutation({ + mutationFn: triggerAiAnalysis, + onSuccess: () => { + toast.success("AI 分析已开始,请稍候..."); + queryClient.invalidateQueries({ queryKey: ["ai-reports"] }); + }, + onError: (err: Error) => { + toast.error(err.message || "触发失败"); + }, + }); + + return ( + <> +
+ +
+ + {isLoading ? ( +
+ +

加载中...

+
+ ) : isError ? ( +
+ +

数据加载失败

+ +
+ ) : reports && reports.length > 0 ? ( +
+ {reports.map((report) => ( + + +
+
+

{report.title}

+ {report.summary && ( +

{report.summary}

+ )} +
+ + + {report.created_at} + + {report.model} + {report.tokens_used?.toLocaleString()} tokens +
+
+ + 查看 → + +
+
+
+ ))} +
+ ) : ( +
+ +

暂无分析报告

+

点击"立即分析"生成今日报告

+
+ )} + + ); +} diff --git a/src/routes/ai-analysis.tsx b/src/routes/ai-analysis.tsx new file mode 100644 index 0000000..c9c0013 --- /dev/null +++ b/src/routes/ai-analysis.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import { Link, Outlet, createFileRoute } from "@tanstack/react-router"; +import { ArrowLeft, BrainCircuit } from "lucide-react"; + +export const Route = createFileRoute("/ai-analysis")({ + component: AiAnalysisLayout, +}); + +function AiAnalysisLayout() { + return ( +
+
+ {/* Header */} +
+ + + +

+ + AI 市场分析 +

+
+ + {/* Child routes render here */} + +
+
+ ); +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index ce6c1d2..9a91ab5 100755 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -10,7 +10,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; -import { Search, Plus, Share2, Trash2, TrendingUp, Loader2, Flame, Network, BarChart3 } from "lucide-react"; +import { Search, Plus, Share2, Trash2, TrendingUp, Loader2, Flame, Network, BarChart3, BrainCircuit } from "lucide-react"; import { toast } from "sonner"; export const Route = createFileRoute("/")({ @@ -230,6 +230,12 @@ function Index() { 热点穿透 + + +
diff --git a/src/styles.css b/src/styles.css index e2d3743..428693e 100755 --- a/src/styles.css +++ b/src/styles.css @@ -146,3 +146,96 @@ font-family: var(--font-sans); } } + +/* AI Report Content Styles */ +.ai-report-content { + color: #c9d1d9; + line-height: 1.7; +} + +.ai-report-content h1, +.ai-report-content h2, +.ai-report-content h3, +.ai-report-content h4 { + color: #e6edf3; + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; +} + +.ai-report-content h1 { font-size: 1.5em; } +.ai-report-content h2 { font-size: 1.3em; } +.ai-report-content h3 { font-size: 1.15em; } + +.ai-report-content p { + margin-bottom: 0.8em; +} + +.ai-report-content strong { + color: #e6edf3; + font-weight: 600; +} + +.ai-report-content ul, +.ai-report-content ol { + margin: 0.5em 0; + padding-left: 1.5em; +} + +.ai-report-content li { + margin-bottom: 0.3em; +} + +.ai-report-content blockquote { + border-left: 3px solid #30363d; + padding-left: 1em; + margin: 0.8em 0; + color: #8b949e; +} + +.ai-report-content code { + background: #0d1117; + padding: 0.15em 0.4em; + border-radius: 4px; + font-size: 0.9em; + color: #79c0ff; +} + +.ai-report-content pre { + background: #0d1117; + padding: 1em; + border-radius: 6px; + overflow-x: auto; + margin: 0.8em 0; +} + +.ai-report-content pre code { + background: none; + padding: 0; + color: #c9d1d9; +} + +.ai-report-content table { + width: 100%; + border-collapse: collapse; + margin: 0.8em 0; +} + +.ai-report-content th, +.ai-report-content td { + border: 1px solid #30363d; + padding: 0.5em 0.8em; + text-align: left; +} + +.ai-report-content th { + background: #21262d; + color: #e6edf3; + font-weight: 600; +} + +.ai-report-content hr { + border: none; + border-top: 1px solid #30363d; + margin: 1.5em 0; +}