From 4a04faf92668d4af5edc5f793e4353d2a2e432a1 Mon Sep 17 00:00:00 2001 From: fengmengqi Date: Fri, 10 Apr 2026 16:29:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Web=20=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=8F=8A=E6=9C=8D=E5=8A=A1=E7=AB=AF=20SSE=20=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E6=94=AF=E6=8C=81=EF=BC=8C=E6=89=A9=E5=B1=95=E5=A4=9A?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端: - server: 实现完整的 HTTP 会话管理(CRUD)+ SSE 事件流推送, 支持双通道架构(POST 发消息 + GET SSE 接收流式响应) - runtime: ContentBlock 新增 Thinking / RedactedThinking 变体, 支持思考过程和已编辑思考的序列化/反序列化 - api: 注册 GLM 系列模型(glm-4/5 等)到模型注册表, 扩展 XAI/OpenAI 兼容提供商的请求构建逻辑 前端: - 基于 Ant Design X 构建完整聊天界面:Bubble.List 消息列表、 Sender 输入框、Conversations 会话管理、Think 思考过程折叠、 ThoughtChain 工具调用链展示 - XMarkdown 集成:代码高亮、Mermaid 图表、LaTeX 公式、 自定义脚注、流式渲染(incomplete 占位符) - SSE Hook 对接服务端事件流,手动管理 AssistantBuffer 累积 delta - 深色/浅色主题切换,会话侧边栏(新建/切换/删除) --- .claw.json | 5 + .env | 3 + .gitignore | 3 + CLAUDE.md | 113 + CLAW.md | 15 + Cargo.lock | 135 + README.md | 7 +- crates/api/README.md | 53 + crates/api/src/providers/claw_provider.rs | 2 +- crates/api/src/providers/mod.rs | 63 + crates/api/src/providers/openai_compat.rs | 41 +- crates/api/src/sse.rs | 94 +- crates/api/src/types.rs | 8 + crates/claw-cli/Cargo.toml | 1 + crates/claw-cli/README.md | 58 + crates/claw-cli/src/main.rs | 59 +- crates/commands/README.md | 55 + crates/commands/src/lib.rs | 6 +- crates/compat-harness/README.md | 48 + crates/lsp/README.md | 56 + crates/lsp/src/client.rs | 12 +- crates/plugins/README.md | 59 + crates/plugins/src/hooks.rs | 1 + crates/runtime/README.md | 60 + crates/runtime/src/compact.rs | 14 +- crates/runtime/src/conversation.rs | 12 + crates/runtime/src/hooks.rs | 7 +- crates/runtime/src/json.rs | 3 +- crates/runtime/src/mcp_stdio.rs | 27 +- crates/runtime/src/session.rs | 35 + crates/rusty-claude-cli/README.md | 64 + crates/rusty-claude-cli/src/main.rs | 18 +- crates/server/Cargo.toml | 12 + crates/server/README.md | 57 + crates/server/src/lib.rs | 1053 ++++- crates/server/src/main.rs | 74 + crates/tools/README.md | 51 + crates/tools/src/lib.rs | 19 +- frontend/.gitignore | 3 + frontend/README.md | 50 + frontend/index.html | 12 + frontend/package-lock.json | 4910 ++++++++++++++++++++ frontend/package.json | 29 + frontend/public/favicon.svg | 1 + frontend/src/App.tsx | 343 ++ frontend/src/api.ts | 57 + frontend/src/components/ChatView.tsx | 475 ++ frontend/src/components/SessionSidebar.tsx | 116 + frontend/src/components/ToolChain.tsx | 85 + frontend/src/components/WelcomeScreen.tsx | 41 + frontend/src/hooks/useSSE.ts | 50 + frontend/src/main.tsx | 15 + frontend/src/types.ts | 160 + frontend/src/vite-env.d.ts | 1 + frontend/tsconfig.json | 18 + frontend/vite.config.ts | 14 + 56 files changed, 8611 insertions(+), 172 deletions(-) create mode 100644 .claw.json create mode 100644 .env create mode 100644 CLAUDE.md create mode 100644 CLAW.md create mode 100644 crates/api/README.md create mode 100644 crates/claw-cli/README.md create mode 100644 crates/commands/README.md create mode 100644 crates/compat-harness/README.md create mode 100644 crates/lsp/README.md create mode 100644 crates/plugins/README.md create mode 100644 crates/runtime/README.md create mode 100644 crates/rusty-claude-cli/README.md create mode 100644 crates/server/README.md create mode 100644 crates/server/src/main.rs create mode 100644 crates/tools/README.md create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/public/favicon.svg create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/api.ts create mode 100644 frontend/src/components/ChatView.tsx create mode 100644 frontend/src/components/SessionSidebar.tsx create mode 100644 frontend/src/components/ToolChain.tsx create mode 100644 frontend/src/components/WelcomeScreen.tsx create mode 100644 frontend/src/hooks/useSSE.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/types.ts create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vite.config.ts diff --git a/.claw.json b/.claw.json new file mode 100644 index 0000000..e0e4c18 --- /dev/null +++ b/.claw.json @@ -0,0 +1,5 @@ +{ + "permissions": { + "defaultMode": "dontAsk" + } +} diff --git a/.env b/.env new file mode 100644 index 0000000..a42ce33 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +ANTHROPIC_API_KEY="9494feba6f7c45f48c3dfc35a85ffd89.2WUCscxcSp92ETNg" +ANTHROPIC_BASE_URL="https://open.bigmodel.cn/api/anthropic" +CLAW_MODEL="glm-5" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 19e1a8e..0f950c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ target/ .omx/ .clawd-agents/ +# Claw Code local artifacts +.claw/ +.claude/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6c64189 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,113 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Claw Code is a local coding-agent CLI tool written in safe Rust. It is a clean-room implementation inspired by Claude Code, providing an interactive REPL, one-shot prompts, workspace-aware tools, local agent workflows, and plugin support. The project name and references throughout use "claw" / "Claw Code". + +## Build & Run Commands + +```bash +# Build release binary (produces target/release/claw) +cargo build --release -p claw-cli + +# Run from source (interactive REPL) +cargo run --bin claw -- + +# Run one-shot prompt +cargo run --bin claw -- prompt "summarize this workspace" + +# Install locally +cargo install --path crates/claw-cli --locked + +# Run the HTTP server binary +cargo run --bin claw-server +``` + +## Verification Commands + +```bash +# Format check +cargo fmt + +# Lint (workspace-level clippy with deny warnings) +cargo clippy --workspace --all-targets -- -D warnings + +# Run all tests +cargo test --workspace + +# Run tests for a specific crate +cargo test -p api +cargo test -p runtime + +# Run a single test by name +cargo test -p -- + +# Integration tests in crates/api/tests/ use mock TCP servers (no network needed) +# One smoke test is #[ignore] — run with: cargo test -p api -- --ignored +``` + +## Workspace Architecture + +Cargo workspace with `resolver = "2"`. All crates live under `crates/`. + +### Crate Dependency Graph + +``` +claw-cli ──→ api, runtime, tools, commands, plugins, compat-harness +server ──→ api, runtime, tools, plugins, commands +tools ──→ api, runtime, plugins +commands ──→ runtime, plugins +api ──→ runtime +runtime ──→ lsp, plugins +plugins ──→ (standalone, serde only) +lsp ──→ (standalone) +compat-harness ──→ commands, tools, runtime +``` + +### Core Crates + +- **`claw-cli`** — User-facing binary (`claw`). REPL loop with markdown rendering (pulldown-cmark + syntect), argument parsing, OAuth flow. Entry point: `crates/claw-cli/src/main.rs`. + +- **`runtime`** — Session management, conversation runtime, permissions, system prompt construction, context compaction, MCP stdio management, and hook execution. Key types: `Session` (versioned message history), `ConversationRuntime` (generic over `ApiClient` + `ToolExecutor` traits), `PermissionMode`, `McpServerManager`, `HookRunner`. + +- **`api`** — HTTP client for LLM providers with SSE streaming. `ClawApiClient` (Anthropic-compatible), `OpenAiCompatClient`, and `Provider` trait. `ProviderKind` enum distinguishes ClawApi, Xai, OpenAi. Request/response types: `MessageRequest`, `StreamEvent`, `ToolDefinition`. + +- **`tools`** — Built-in tool definitions and dispatch. `GlobalToolRegistry` is a lazy-static singleton. Tools: Read, Write, Edit, Glob, Grep, Bash, LSP, Task*, Cron*, Worktree*. Each tool has a `ToolSpec` with JSON schema. + +- **`commands`** — Slash command registry and handlers (`/help`, `/config`, `/compact`, `/resume`, `/plugins`, `/agents`, `/doctor`, etc.). `SlashCommandSpec` defines each command's name, aliases, description, and category. + +- **`plugins`** — Plugin discovery and lifecycle. `PluginManager` loads builtin, bundled, and external (from `~/.claw/plugins/`) plugins. Plugins can provide additional tools via `PluginTool`. + +- **`server`** — Axum-based HTTP server (`claw-server`). REST endpoints for session CRUD + SSE event streaming. `AppState` holds shared session store. + +- **`lsp`** — Language Server Protocol types and process management for code intelligence features. + +- **`compat-harness`** — Extracts command/tool/bootstrap-plan manifests from upstream TypeScript source files (for compatibility tracking). Uses `CLAUDE_CODE_UPSTREAM` env var to locate the upstream repo. + +## Key Architectural Patterns + +- **Trait-based abstraction**: `ApiClient`, `ToolExecutor`, `Provider` traits enable swappable implementations. `ConversationRuntime` is generic over client and executor. +- **Static registries**: `GlobalToolRegistry` and slash command specs use lazy-static initialization with compile-time definitions. +- **SSE streaming**: API responses stream through `MessageStream` (async iterator) to the terminal renderer or server SSE endpoints. +- **Permission model**: `PermissionMode` enum — ReadOnly, WorkspaceWrite, DangerFullAccess. Configurable via `.claw.json` (`permissions.defaultMode`). +- **Hook system**: Pre/post tool execution hooks via `HookRunner` in the runtime crate. + +## Configuration & Environment + +- `.claw.json` — Project-level config (permissions, etc.) +- `.claw/` — Project directory for hooks, plugins, local settings +- `~/.claw/` — User-level config directory +- `.env` — API keys (gitignored): `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `XAI_API_KEY`, `XAI_BASE_URL` +- `CLAW.md` — Workspace instructions loaded into the system prompt (analogous to CLAUDE.md but for claw itself) + +## Lint Rules + +- `unsafe_code` is **forbidden** at workspace level +- Clippy `all` + `pedantic` lints are warnings; some pedantic lints are allowed (`module_name_repetitions`, `missing_panics_doc`, `missing_errors_doc`) +- CI runs: `cargo check --workspace`, `cargo test --workspace`, `cargo build --release` on Ubuntu and macOS + +## Language + +Code comments, commit messages, and documentation are primarily in Chinese (中文). UI strings and exported symbol names are in English. diff --git a/CLAW.md b/CLAW.md new file mode 100644 index 0000000..42b687a --- /dev/null +++ b/CLAW.md @@ -0,0 +1,15 @@ +# CLAW.md + +This file provides guidance to Claw Code (clawcode.dev) when working with code in this repository. + +## Detected stack +- Languages: Rust. +- Frameworks: none detected from the supported starter markers. + +## Verification +- Run Rust verification from the repo root: `cargo fmt`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace` + +## Working agreement +- Prefer small, reviewable changes and keep generated bootstrap files aligned with actual repo workflows. +- Keep shared defaults in `.claw.json`; reserve `.claw/settings.local.json` for machine-local overrides. +- Do not overwrite existing `CLAW.md` content automatically; update it intentionally when repo workflows change. diff --git a/Cargo.lock b/Cargo.lock index f956d2e..8526ba0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "api" version = "0.1.0" @@ -56,6 +65,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "axum" version = "0.8.8" @@ -178,6 +193,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "claw-cli" version = "0.1.0" @@ -186,6 +214,7 @@ dependencies = [ "commands", "compat-harness", "crossterm", + "dotenvy", "plugins", "pulldown-cmark", "runtime", @@ -223,6 +252,12 @@ dependencies = [ "tools", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -306,6 +341,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "endian-type" version = "0.1.2" @@ -619,6 +660,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -907,6 +972,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1490,13 +1564,21 @@ dependencies = [ name = "server" version = "0.1.0" dependencies = [ + "api", "async-stream", "axum", + "chrono", + "commands", + "dotenvy", + "plugins", "reqwest", "runtime", "serde", "serde_json", "tokio", + "tools", + "tower", + "tower-http", ] [[package]] @@ -2078,12 +2160,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/README.md b/README.md index fb4ef6b..e120180 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,11 @@ Rust 工作区是当前主要的产品界面。`claw` 二进制文件在单个 - Cargo - 你想使用的模型的提供商凭据 -### 身份验证 - -兼容 Anthropic 的模型: +你可以通过环境变量或在项目根目录创建 **`.env`** 文件来配置 API 密钥: +**配置 Claude (推荐):** ```bash -export ANTHROPIC_API_KEY="..." +ANTHROPIC_API_KEY="..." # 使用兼容的端点时可选 export ANTHROPIC_BASE_URL="https://api.anthropic.com" ``` diff --git a/crates/api/README.md b/crates/api/README.md new file mode 100644 index 0000000..d45d176 --- /dev/null +++ b/crates/api/README.md @@ -0,0 +1,53 @@ +# API 模块 (api) + +本模块提供了与大型语言模型 (LLM) 服务提供商(主要是 Anthropic 的 Claude 和兼容 OpenAI 的服务)进行交互的高层抽象和客户端。 + +## 概览 + +`api` 模块负责以下职责: +- 标准化与不同 AI 提供商的通信。 +- 通过服务器发送事件 (SSE) 处理流式响应。 +- 管理身份验证源(API 密钥、OAuth 令牌)。 +- 提供消息、工具和使用情况跟踪的共享数据结构。 + +## 关键特性 + +- **提供商抽象 (Provider Abstraction)**:支持多种 AI 后端,包括: + - `ClawApiClient`: Claude 模型的主要提供商。 + - `OpenAiCompatClient`: 支持兼容 OpenAI 的 API(如本地模型、专门的提供商)。 +- **流式支持 (Streaming Support)**:健壮的 SSE 解析实现 (`SseParser`),用于处理实时的内容生成。 +- **工具集成 (Tool Integration)**:为 `ToolDefinition`、`ToolChoice` 和 `ToolResultContentBlock` 提供强类型定义,支持智能代理 (Agentic) 工作流。 +- **身份验证管理 (Auth Management)**:用于解析启动身份验证源和管理 OAuth 令牌的实用工具。 +- **模型智能 (Model Intelligence)**:解析模型别名和计算最大标记 (Token) 限制的元数据及辅助函数。 + +## 实现逻辑 + +### 核心模块 + +- **`client.rs`**: 定义了 `ProviderClient` 特性 (Trait) 和基础客户端逻辑。它使用 `reqwest` 处理 HTTP 请求,并管理消息流的生命周期。 +- **`types.rs`**: 包含 API 的核心数据模型,如 `InputMessage`、`OutputContentBlock` 以及 `MessageRequest`/`MessageResponse`。 +- **`sse.rs`**: 实现了一个状态化的 SSE 解析器,能够处理分段的数据块并发出类型化的 `StreamEvent`。 +- **`providers/`**: 包含针对不同 LLM 端点的特定逻辑,将它们的独特格式映射到本模块使用的共享类型。 + +### 数据流 + +1. 构建包含模型详情、消息和工具定义的 `MessageRequest`。 +2. `ApiClient` 将此请求转换为提供商特定的 HTTP 请求。 +3. 如果启用了流式传输,客户端返回一个 `MessageStream`,该流使用 `SseParser` 来产生 `StreamEvent`。 +4. 最终响应包含用于跟踪 Token 消耗的 `Usage` 信息。 + +## 使用示例 + +```rust +use api::{ApiClient, MessageRequest, InputMessage}; + +// 示例初始化(已简化) +let client = ApiClient::new(auth_source); +let request = MessageRequest { + model: "claude-3-5-sonnet-20241022".to_string(), + messages: vec![InputMessage::user("你好,世界!")], + ..Default::default() +}; + +let stream = client.create_message_stream(request).await?; +``` diff --git a/crates/api/src/providers/claw_provider.rs b/crates/api/src/providers/claw_provider.rs index d9046cd..38c523f 100644 --- a/crates/api/src/providers/claw_provider.rs +++ b/crates/api/src/providers/claw_provider.rs @@ -634,7 +634,7 @@ struct ApiErrorEnvelope { #[derive(Debug, Deserialize)] struct ApiErrorBody { - #[serde(rename = "type")] + #[serde(alias = "code", rename = "type")] error_type: String, message: String, } diff --git a/crates/api/src/providers/mod.rs b/crates/api/src/providers/mod.rs index 192afd6..80e60db 100644 --- a/crates/api/src/providers/mod.rs +++ b/crates/api/src/providers/mod.rs @@ -138,6 +138,69 @@ const MODEL_REGISTRY: &[(&str, ProviderMetadata)] = &[ default_base_url: openai_compat::DEFAULT_XAI_BASE_URL, }, ), + ( + "glm-4-plus", + ProviderMetadata { + provider: ProviderKind::ClawApi, + auth_env: "ANTHROPIC_API_KEY", + base_url_env: "ANTHROPIC_BASE_URL", + default_base_url: claw_provider::DEFAULT_BASE_URL, + }, + ), + ( + "glm-4-0520", + ProviderMetadata { + provider: ProviderKind::ClawApi, + auth_env: "ANTHROPIC_API_KEY", + base_url_env: "ANTHROPIC_BASE_URL", + default_base_url: claw_provider::DEFAULT_BASE_URL, + }, + ), + ( + "glm-4", + ProviderMetadata { + provider: ProviderKind::ClawApi, + auth_env: "ANTHROPIC_API_KEY", + base_url_env: "ANTHROPIC_BASE_URL", + default_base_url: claw_provider::DEFAULT_BASE_URL, + }, + ), + ( + "glm-4-air", + ProviderMetadata { + provider: ProviderKind::ClawApi, + auth_env: "ANTHROPIC_API_KEY", + base_url_env: "ANTHROPIC_BASE_URL", + default_base_url: claw_provider::DEFAULT_BASE_URL, + }, + ), + ( + "glm-4-flash", + ProviderMetadata { + provider: ProviderKind::ClawApi, + auth_env: "ANTHROPIC_API_KEY", + base_url_env: "ANTHROPIC_BASE_URL", + default_base_url: claw_provider::DEFAULT_BASE_URL, + }, + ), + ( + "glm-5", + ProviderMetadata { + provider: ProviderKind::ClawApi, + auth_env: "ANTHROPIC_API_KEY", + base_url_env: "ANTHROPIC_BASE_URL", + default_base_url: claw_provider::DEFAULT_BASE_URL, + }, + ), + ( + "glm-5.1", + ProviderMetadata { + provider: ProviderKind::ClawApi, + auth_env: "ANTHROPIC_API_KEY", + base_url_env: "ANTHROPIC_BASE_URL", + default_base_url: claw_provider::DEFAULT_BASE_URL, + }, + ), ]; #[must_use] diff --git a/crates/api/src/providers/openai_compat.rs b/crates/api/src/providers/openai_compat.rs index e8210ae..52f3695 100644 --- a/crates/api/src/providers/openai_compat.rs +++ b/crates/api/src/providers/openai_compat.rs @@ -251,7 +251,7 @@ impl MessageStream { } if self.done { - self.pending.extend(self.state.finish()?); + self.pending.extend(self.state.finish()); if let Some(event) = self.pending.pop_front() { return Ok(Some(event)); } @@ -261,7 +261,7 @@ impl MessageStream { match self.response.chunk().await? { Some(chunk) => { for parsed in self.parser.push(&chunk)? { - self.pending.extend(self.state.ingest_chunk(parsed)?); + self.pending.extend(self.state.ingest_chunk(parsed)); } } None => { @@ -297,6 +297,7 @@ impl OpenAiSseParser { } #[derive(Debug)] +#[allow(clippy::struct_excessive_bools)] struct StreamState { model: String, message_started: bool, @@ -322,7 +323,7 @@ impl StreamState { } } - fn ingest_chunk(&mut self, chunk: ChatCompletionChunk) -> Result, ApiError> { + fn ingest_chunk(&mut self, chunk: ChatCompletionChunk) -> Vec { let mut events = Vec::new(); if !self.message_started { self.message_started = true; @@ -377,7 +378,7 @@ impl StreamState { state.apply(tool_call); let block_index = state.block_index(); if !state.started { - if let Some(start_event) = state.start_event()? { + if let Some(start_event) = state.start_event() { state.started = true; events.push(StreamEvent::ContentBlockStart(start_event)); } else { @@ -410,12 +411,12 @@ impl StreamState { } } - Ok(events) + events } - fn finish(&mut self) -> Result, ApiError> { + fn finish(&mut self) -> Vec { if self.finished { - return Ok(Vec::new()); + return Vec::new(); } self.finished = true; @@ -429,7 +430,7 @@ impl StreamState { for state in self.tool_calls.values_mut() { if !state.started { - if let Some(start_event) = state.start_event()? { + if let Some(start_event) = state.start_event() { state.started = true; events.push(StreamEvent::ContentBlockStart(start_event)); if let Some(delta_event) = state.delta_event() { @@ -464,7 +465,7 @@ impl StreamState { })); events.push(StreamEvent::MessageStop(MessageStopEvent {})); } - Ok(events) + events } } @@ -497,22 +498,20 @@ impl ToolCallState { self.openai_index + 1 } - fn start_event(&self) -> Result, ApiError> { - let Some(name) = self.name.clone() else { - return Ok(None); - }; + fn start_event(&self) -> Option { + let name = self.name.clone()?; let id = self .id .clone() .unwrap_or_else(|| format!("tool_call_{}", self.openai_index)); - Ok(Some(ContentBlockStartEvent { + Some(ContentBlockStartEvent { index: self.block_index(), content_block: OutputContentBlock::ToolUse { id, name, input: json!({}), }, - })) + }) } fn delta_event(&mut self) -> Option { @@ -678,6 +677,14 @@ fn translate_message(message: &InputMessage) -> Vec { } })), InputContentBlock::ToolResult { .. } => {} + InputContentBlock::Thinking { thinking, .. } => { + text.push_str("\n"); + text.push_str(thinking); + text.push_str("\n\n"); + } + InputContentBlock::RedactedThinking { .. } => { + text.push_str("\n\n\n"); + } } } if text.is_empty() && tool_calls.is_empty() { @@ -708,7 +715,9 @@ fn translate_message(message: &InputMessage) -> Vec { "content": flatten_tool_result_content(content), "is_error": is_error, })), - InputContentBlock::ToolUse { .. } => None, + InputContentBlock::ToolUse { .. } + | InputContentBlock::Thinking { .. } + | InputContentBlock::RedactedThinking { .. } => None, }) .collect(), } diff --git a/crates/api/src/sse.rs b/crates/api/src/sse.rs index 5f54e50..44ac73d 100644 --- a/crates/api/src/sse.rs +++ b/crates/api/src/sse.rs @@ -1,5 +1,7 @@ use crate::error::ApiError; use crate::types::StreamEvent; +use serde_json::Value; +use reqwest::StatusCode; #[derive(Debug, Default)] pub struct SseParser { @@ -95,9 +97,75 @@ pub fn parse_frame(frame: &str) -> Result, ApiError> { return Ok(None); } - serde_json::from_str::(&payload) - .map(Some) - .map_err(ApiError::from) + if matches!(event_name, Some("error")) { + return Err(parse_error_event(&payload)); + } + + // Some "Anthropic-compatible" gateways put the event type in the SSE `event:` field, + // and omit the `{ "type": ... }` discriminator from the JSON `data:` payload. + // Our Rust enums are tagged with `#[serde(tag = "type")]`, so we synthesize it here. + match serde_json::from_str::(&payload) { + Ok(event) => Ok(Some(event)), + Err(error) => { + // Best-effort: if we have an SSE event name and the payload is a JSON object + // without a `type` field, inject it and retry. + let Some(event_name) = event_name else { + return Err(ApiError::from(error)); + }; + let Ok(Value::Object(mut object)) = serde_json::from_str::(&payload) else { + return Err(ApiError::from(error)); + }; + if object + .get("type") + .and_then(Value::as_str) + .is_some_and(|value| value == "error") + { + return Err(parse_error_object(&object, payload)); + } + if object.contains_key("type") { + return Err(ApiError::from(error)); + } + object.insert("type".to_string(), Value::String(event_name.to_string())); + serde_json::from_value::(Value::Object(object)) + .map(Some) + .map_err(ApiError::from) + } + } +} + +fn parse_error_event(payload: &str) -> ApiError { + match serde_json::from_str::(payload) { + Ok(Value::Object(object)) => parse_error_object(&object, payload.to_string()), + _ => ApiError::Api { + status: StatusCode::BAD_GATEWAY, + error_type: Some("stream_error".to_string()), + message: Some(payload.to_string()), + body: payload.to_string(), + retryable: false, + }, + } +} + +fn parse_error_object(object: &serde_json::Map, body: String) -> ApiError { + let nested = object.get("error").and_then(Value::as_object); + let error_type = nested + .and_then(|error| error.get("type")) + .or_else(|| object.get("type")) + .and_then(Value::as_str) + .map(ToOwned::to_owned); + let message = nested + .and_then(|error| error.get("message")) + .or_else(|| object.get("message")) + .and_then(Value::as_str) + .map(ToOwned::to_owned); + + ApiError::Api { + status: StatusCode::BAD_GATEWAY, + error_type, + message, + body, + retryable: false, + } } #[cfg(test)] @@ -195,6 +263,26 @@ mod tests { assert_eq!(event, None); } + #[test] + fn parses_event_name_when_payload_omits_type() { + let frame = concat!("event: message_stop\n", "data: {}\n\n"); + let event = parse_frame(frame).expect("frame should parse"); + assert_eq!(event, Some(StreamEvent::MessageStop(crate::types::MessageStopEvent {}))); + } + + #[test] + fn surfaces_stream_error_events() { + let frame = concat!( + "event: error\n", + "data: {\"error\":{\"type\":\"invalid_request_error\",\"message\":\"bad input\"}}\n\n" + ); + let error = parse_frame(frame).expect_err("error frame should surface"); + assert_eq!( + error.to_string(), + "api returned 502 Bad Gateway (invalid_request_error): bad input" + ); + } + #[test] fn parses_split_json_across_data_lines() { let frame = concat!( diff --git a/crates/api/src/types.rs b/crates/api/src/types.rs index c060be6..80c6ead 100644 --- a/crates/api/src/types.rs +++ b/crates/api/src/types.rs @@ -75,6 +75,14 @@ pub enum InputContentBlock { #[serde(default, skip_serializing_if = "std::ops::Not::not")] is_error: bool, }, + Thinking { + thinking: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + signature: Option, + }, + RedactedThinking { + data: Value, + }, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/crates/claw-cli/Cargo.toml b/crates/claw-cli/Cargo.toml index 074718a..5ee8607 100644 --- a/crates/claw-cli/Cargo.toml +++ b/crates/claw-cli/Cargo.toml @@ -22,6 +22,7 @@ serde_json.workspace = true syntect = "5" tokio = { version = "1", features = ["rt-multi-thread", "time"] } tools = { path = "../tools" } +dotenvy = "0.15" [lints] workspace = true diff --git a/crates/claw-cli/README.md b/crates/claw-cli/README.md new file mode 100644 index 0000000..d4b8f97 --- /dev/null +++ b/crates/claw-cli/README.md @@ -0,0 +1,58 @@ +# Claw CLI 模块 (claw-cli) + +本模块实现了 Claw 应用程序的主要命令行界面 (CLI)。它提供了交互式的 REPL 环境和非交互式的命令执行功能。 + +## 概览 + +`claw-cli` 模块是整个项目的“胶水”,负责编排 `runtime`、`api`、`tools` 和 `plugins` 模块之间的交互。它捕获用户输入,管理应用程序状态,并以用户友好的格式渲染 AI 响应。 + +## 关键特性 + +- **交互式 REPL**:一个功能齐全的 Read-Eval-Print Loop,用于与 AI 对话,支持: + - 通过 `rustyline` 实现多行输入和命令历史。 + - 实时的 Markdown 流式显示和代码语法高亮。 + - 为耗时的工具调用显示动态的加载动画 (Spinner) 和进度指示器。 +- **子命令**: + - `prompt`:运行单次 Prompt 并退出(单次模式)。 + - `login`/`logout`:处理与 Claw 平台的 OAuth 身份验证。 + - `init`:初始化新项目/仓库以配合 Claw 使用。 + - `resume`:恢复并继续之前的对话会话。 + - `agents`/`skills`:管理并发现可用的智能体和技能。 +- **权限管理**:对工具执行权限的细粒度控制: + - `read-only`:安全的分析模式。 + - `workspace-write`:允许在当前工作区内进行修改。 + - `danger-full-access`:高级任务的无限制访问。 +- **OAuth 流程**:集成了本地 HTTP 服务器,无缝处理用户机器上的 OAuth 回调重定向。 + +## 实现逻辑 + +### 核心模块 + +- **`main.rs`**: 主要入口点。负责解析命令行参数、初始化环境,并调度到适当的操作(REPL 或子命令)。 +- **`render/`**: 包含 `TerminalRenderer` 和 Markdown 流渲染逻辑。使用 `syntect` 进行语法高亮,并使用 `crossterm` 进行终端操作。 +- **`input/`**: 处理用户输入捕获,包括对多行 Prompt 和斜杠命令 (Slash Command) 的特殊处理。 +- **`init.rs`**: 处理项目级初始化和仓库设置。 + +### 交互循环流程 + +1. CLI 初始化 `ConversationRuntime` 并加载项目上下文。 +2. 使用 `rustyline` 进入捕获用户输入的循环。 +3. 检查用户输入是否为“斜杠命令”(如 `/compact`、`/model`)。 +4. 普通 Prompt 通过 `runtime` 发送给 AI。 +5. AI 事件(文本增量、工具调用)逐步渲染到终端。 +6. 会话定期保存,以便将来可以恢复。 + +## 使用方法 + +主二进制程序名为 `claw`。 + +```bash +# 启动交互式 REPL +claw + +# 运行单次 Prompt +claw prompt "解释一下这个项目的架构" + +# 登录服务 +claw login +``` diff --git a/crates/claw-cli/src/main.rs b/crates/claw-cli/src/main.rs index 2b7d6f1..3bde1cc 100644 --- a/crates/claw-cli/src/main.rs +++ b/crates/claw-cli/src/main.rs @@ -59,6 +59,7 @@ const INTERNAL_PROGRESS_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(3); type AllowedToolSet = BTreeSet; fn main() { + dotenvy::dotenv().ok(); if let Err(error) = run() { eprintln!("{}", render_cli_error(&error.to_string())); std::process::exit(1); @@ -171,7 +172,7 @@ impl CliOutputFormat { #[allow(clippy::too_many_lines)] fn parse_args(args: &[String]) -> Result { - let mut model = DEFAULT_MODEL.to_string(); + let mut model = env::var("CLAW_MODEL").unwrap_or_else(|_| DEFAULT_MODEL.to_string()); let mut output_format = CliOutputFormat::Text; let mut permission_mode = default_permission_mode(); let mut wants_version = false; @@ -2504,6 +2505,12 @@ fn render_export_text(session: &Session) -> String { for block in &message.blocks { match block { ContentBlock::Text { text } => lines.push(text.clone()), + ContentBlock::Thinking { thinking, .. } => { + lines.push(format!("[thinking] {thinking}")); + } + ContentBlock::RedactedThinking { .. } => { + lines.push("[thinking] ".to_string()); + } ContentBlock::ToolUse { id, name, input } => { lines.push(format!("[tool_use id={id} name={name}] {input}")); } @@ -3158,8 +3165,18 @@ impl ApiClient for DefaultRuntimeClient { input.push_str(&partial_json); } } - ContentBlockDelta::ThinkingDelta { .. } - | ContentBlockDelta::SignatureDelta { .. } => {} + ContentBlockDelta::ThinkingDelta { thinking } => { + if !thinking.is_empty() { + if self.emit_output { + // Use a dimmed style for thinking blocks + write!(out, "\x1b[2m{thinking}\x1b[0m") + .and_then(|()| out.flush()) + .map_err(|error| RuntimeError::new(error.to_string()))?; + } + events.push(AssistantEvent::ThinkingDelta(thinking)); + } + } + ContentBlockDelta::SignatureDelta { .. } => {} }, ApiStreamEvent::ContentBlockStop(_) => { if let Some(rendered) = markdown_stream.flush(&renderer) { @@ -3237,7 +3254,10 @@ fn final_assistant_text(summary: &runtime::TurnSummary) -> String { .iter() .filter_map(|block| match block { ContentBlock::Text { text } => Some(text.as_str()), - _ => None, + ContentBlock::Thinking { thinking, .. } => Some(thinking.as_str()), + ContentBlock::RedactedThinking { .. } + | ContentBlock::ToolUse { .. } + | ContentBlock::ToolResult { .. } => None, }) .collect::>() .join("") @@ -3256,7 +3276,10 @@ fn collect_tool_uses(summary: &runtime::TurnSummary) -> Vec { "name": name, "input": input, })), - _ => None, + ContentBlock::Thinking { .. } + | ContentBlock::RedactedThinking { .. } + | ContentBlock::Text { .. } + | ContentBlock::ToolResult { .. } => None, }) .collect() } @@ -3278,7 +3301,10 @@ fn collect_tool_results(summary: &runtime::TurnSummary) -> Vec None, + ContentBlock::Thinking { .. } + | ContentBlock::RedactedThinking { .. } + | ContentBlock::Text { .. } + | ContentBlock::ToolUse { .. } => None, }) .collect() } @@ -3819,7 +3845,16 @@ fn push_output_block( }; *pending_tool = Some((id, name, initial_input)); } - OutputContentBlock::Thinking { .. } | OutputContentBlock::RedactedThinking { .. } => {} + OutputContentBlock::Thinking { thinking, .. } => { + if !thinking.is_empty() { + // Dimmed style for thinking + write!(out, "\x1b[2m{thinking}\x1b[0m") + .and_then(|()| out.flush()) + .map_err(|error| RuntimeError::new(error.to_string()))?; + events.push(AssistantEvent::ThinkingDelta(thinking)); + } + } + OutputContentBlock::RedactedThinking { .. } => {} } Ok(()) } @@ -3928,6 +3963,16 @@ fn convert_messages(messages: &[ConversationMessage]) -> Vec { .iter() .map(|block| match block { ContentBlock::Text { text } => InputContentBlock::Text { text: text.clone() }, + ContentBlock::Thinking { + thinking, + signature, + } => InputContentBlock::Thinking { + thinking: thinking.clone(), + signature: signature.clone(), + }, + ContentBlock::RedactedThinking { data } => InputContentBlock::RedactedThinking { + data: serde_json::from_str(&data.render()).unwrap_or(serde_json::Value::Null), + }, ContentBlock::ToolUse { id, name, input } => InputContentBlock::ToolUse { id: id.clone(), name: name.clone(), diff --git a/crates/commands/README.md b/crates/commands/README.md new file mode 100644 index 0000000..dfe68cc --- /dev/null +++ b/crates/commands/README.md @@ -0,0 +1,55 @@ +# 命令模块 (commands) + +本模块负责定义和管理 Claw 交互界面中使用的“斜杠命令”(Slash Commands),并提供相关的解析和执行逻辑。 + +## 概览 + +`commands` 模块的主要职责包括: +- 定义所有可用的斜杠命令及其元数据(别名、说明、类别等)。 +- 提供命令注册表 (`CommandRegistry`),用于在 CLI 中发现和分发命令。 +- 实现复杂的管理命令,如插件管理 (`/plugins`)、智能体查看 (`/agents`) 和技能查看 (`/skills`)。 +- 提供命令建议功能,支持基于编辑距离 (Levenshtein distance) 的模糊匹配。 + +## 关键特性 + +- **斜杠命令规范 (SlashCommandSpec)**:每个命令都包含详尽的元数据,包括所属类别(核心、工作区、会话、Git、自动化)以及是否支持在恢复会话时执行。 +- **命令分类**: + - **核心 (Core)**:`/help`, `/status`, `/model`, `/permissions`, `/cost` 等。 + - **工作区 (Workspace)**:`/config`, `/memory`, `/diff`, `/teleport` 等。 + - **会话 (Session)**:`/clear`, `/resume`, `/export`, `/session` 等。 + - **Git 交互**:`/branch`, `/commit`, `/pr`, `/issue` 等。 + - **自动化 (Automation)**:`/plugins`, `/agents`, `/skills`, `/ultraplan` 等。 +- **模糊匹配与建议**:当用户输入错误的命令时,系统会自动推荐最接近的合法命令。 +- **插件集成**:`/plugins` 命令允许用户动态安装、启用、禁用或卸载插件,并能通知运行时重新加载环境。 + +## 实现逻辑 + +### 核心模块 + +- **`lib.rs`**: 包含了绝大部分逻辑。 + - **`SlashCommand` 枚举**: 定义了所有命令的强类型表示。 + - **`SlashCommandSpec` 结构体**: 存储命令的静态配置信息。 + - **`handle_plugins_slash_command`**: 处理复杂的插件管理工作流。 + - **`suggest_slash_commands`**: 实现基于 Levenshtein 距离的建议算法。 + +### 工作流程 + +1. 用户在 REPL 中输入以 `/` 开头的字符串。 +2. `claw-cli` 调用 `SlashCommand::parse` 进行解析。 +3. 解析后的命令被分发到相应的处理器。 +4. 处理结果(通常包含要显示给用户的消息,以及可选的会话更新或运行时重新加载请求)返回给 CLI。 + +## 使用示例 (内部) + +```rust +use commands::{SlashCommand, suggest_slash_commands}; + +// 解析命令 +if let Some(cmd) = SlashCommand::parse("/model sonnet") { + // 处理模型切换逻辑 +} + +// 获取建议 +let suggestions = suggest_slash_commands("hpel", 3); +// 返回 ["/help"] +``` diff --git a/crates/commands/src/lib.rs b/crates/commands/src/lib.rs index da7f1a4..5537028 100644 --- a/crates/commands/src/lib.rs +++ b/crates/commands/src/lib.rs @@ -603,7 +603,7 @@ pub fn suggest_slash_commands(input: &str, limit: usize) -> Vec { }) .collect::>(); - ranked.sort_by(|left, right| left.cmp(right)); + ranked.sort(); ranked.dedup_by(|left, right| left.2 == right.2); ranked .into_iter() @@ -842,7 +842,7 @@ pub fn handle_branch_slash_command( Ok(if trimmed.is_empty() { "Branch\n Result no branches found".to_string() } else { - format!("Branch\n Result listed\n\n{}", trimmed) + format!("Branch\n Result listed\n\n{trimmed}") }) } Some("create") => { @@ -882,7 +882,7 @@ pub fn handle_worktree_slash_command( Ok(if trimmed.is_empty() { "Worktree\n Result no worktrees found".to_string() } else { - format!("Worktree\n Result listed\n\n{}", trimmed) + format!("Worktree\n Result listed\n\n{trimmed}") }) } Some("add") => { diff --git a/crates/compat-harness/README.md b/crates/compat-harness/README.md new file mode 100644 index 0000000..762d15e --- /dev/null +++ b/crates/compat-harness/README.md @@ -0,0 +1,48 @@ +# 兼容性测试套件模块 (compat-harness) + +本模块提供了一套工具,专门用于分析和提取上游引用实现(如原始的 `claude-code` TypeScript 源码)中的元数据,以确保 Rust 版本的实现与其保持功能兼容。 + +## 概览 + +`compat-harness` 的主要职责是: +- 定位上游仓库的源码路径。 +- 从 TypeScript 源码文件中提取命令 (`commands`)、工具 (`tools`) 和启动阶段 (`bootstrap phases`) 的定义。 +- 自动生成功能清单 (`ExtractedManifest`),供运行时或测试使用,以验证 Rust 版本的覆盖率。 + +## 关键特性 + +- **上游路径解析 (UpstreamPaths)**:能够自动识别多种常见的上游仓库目录结构,并支持通过环境变量 `CLAUDE_CODE_UPSTREAM` 进行覆盖。 +- **静态代码分析**:通过解析 TypeScript 源码,识别特定的代码模式(如 `export const INTERNAL_ONLY_COMMANDS` 或基于 `feature()` 的功能开关)。 +- **清单提取 (Manifest Extraction)**: + - **命令提取**:识别内置命令、仅限内部使用的命令以及受功能开关控制的命令。 + - **工具提取**:识别基础工具和条件加载的工具。 + - **启动计划提取**:分析 CLI 入口文件,重建启动时的各个阶段(如 `FastPathVersion`, `MainRuntime` 等)。 + +## 实现逻辑 + +### 核心模块 + +- **`lib.rs`**: 包含了核心的提取逻辑。 + - **`UpstreamPaths` 结构体**: 封装了寻找 `commands.ts`、`tools.ts` 和 `cli.tsx` 的逻辑。 + - **`extract_commands` & `extract_tools`**: 使用字符串解析技术,识别 TypeScript 的 `import` 和赋值操作,提取符号名称。 + - **`extract_bootstrap_plan`**: 搜索特定的标志性字符串(如 `--version` 或 `daemon-worker`),从而推断出上游程序的启动流程。 + +### 工作流程 + +1. 模块根据预设路径或环境变量寻找上游 `claude-code` 仓库。 +2. 读取关键的 `.ts` 或 `.tsx` 文件内容。 +3. 执行正则表达式风格的行解析,提取出所有定义的命令和工具名称。 +4. 将提取结果组织成 `ExtractedManifest` 对象。 + +## 使用示例 (内部测试) + +```rust +use compat_harness::{UpstreamPaths, extract_manifest}; + +// 指定工作区目录,自动寻找上游路径 +let paths = UpstreamPaths::from_workspace_dir("path/to/workspace"); +// 提取功能清单 +if let Ok(manifest) = extract_manifest(&paths) { + println!("上游发现 {} 个工具", manifest.tools.entries().len()); +} +``` diff --git a/crates/lsp/README.md b/crates/lsp/README.md new file mode 100644 index 0000000..d60d316 --- /dev/null +++ b/crates/lsp/README.md @@ -0,0 +1,56 @@ +# LSP 模块 (lsp) + +本模块实现了语言服务协议 (Language Server Protocol, LSP) 的客户端功能,允许系统通过集成的编程语言服务器获取代码的语义信息、错误诊断和符号导航。 + +## 概览 + +`lsp` 模块的主要职责是: +- 管理多个 LSP 服务器的生命周期(启动、初始化、关闭)。 +- 与服务器进行异步 JSON-RPC 通信。 +- 提供跨语言的代码智能功能,如: + - **转到定义 (Go to Definition)** + - **查找引用 (Find References)** + - **工作区诊断 (Workspace Diagnostics)** +- 为 AI 提示词 (Prompt) 提供上下文增强,将代码中的实时错误和符号关系反馈给 LLM。 + +## 关键特性 + +- **LspManager**: 核心管理类,负责协调不同语言的服务器配置和文档状态。 +- **上下文增强 (Context Enrichment)**:定义了 `LspContextEnrichment` 结构,能够将复杂的 LSP 响应(如诊断信息和定义)转换为易于 AI 理解的 Markdown 格式。 +- **多服务器支持**:支持根据文件扩展名将请求路由到不同的语言服务器(如 `rust-analyzer`, `pyright` 等)。 +- **同步机制**:处理文档的 `didOpen`、`didChange` 和 `didSave` 消息,确保服务器拥有最新的代码视图。 + +## 实现逻辑 + +### 核心模块 + +- **`manager.rs`**: 实现了 `LspManager`。它维护一个服务器池,并提供高层 API 来执行跨服务器的请求。 +- **`client.rs`**: 实现底层的 LSP 客户端逻辑,处理基于 `tokio` 的异步 I/O 和 JSON-RPC 消息的分帧与解析。 +- **`types.rs`**: 定义了本模块使用的专用数据类型,并对 `lsp-types` 库中的类型进行了简化和包装,以便于内部使用。 +- **`error.rs`**: 定义了 LSP 相关的错误处理。 + +### 工作流程 + +1. 系统根据配置初始化 `LspManager`。 +2. 当打开一个文件时,`LspManager` 启动相应的服务器并发送 `initialize` 请求。 +3. `LspManager` 跟踪文档的打开状态,并在内容变化时同步到服务器。 +4. 当需要对某个符号进行分析时,调用 `go_to_definition` 等方法,模块负责发送请求并解析返回的 `Location`。 +5. 诊断信息异步通过 `textDocument/publishDiagnostics` 通知到达,模块会缓存这些信息供后续查询。 + +## 使用示例 (内部) + +```rust +use lsp::{LspManager, LspServerConfig}; + +// 配置并初始化管理器 +let configs = vec![LspServerConfig { + name: "rust-analyzer".to_string(), + command: "rust-analyzer".to_string(), + ..Default::default() +}]; +let manager = LspManager::new(configs)?; + +// 获取某个位置的上下文增强信息 +let enrichment = manager.context_enrichment(&file_path, position).await?; +println!("{}", enrichment.render_prompt_section()); +``` diff --git a/crates/lsp/src/client.rs b/crates/lsp/src/client.rs index 7ec663b..4661ed1 100644 --- a/crates/lsp/src/client.rs +++ b/crates/lsp/src/client.rs @@ -15,11 +15,13 @@ use tokio::sync::{oneshot, Mutex}; use crate::error::LspError; use crate::types::{LspServerConfig, SymbolLocation}; +type PendingRequestMap = BTreeMap>>; + pub(crate) struct LspClient { config: LspServerConfig, writer: Mutex>, child: Mutex, - pending_requests: Arc>>>>, + pending_requests: Arc>, diagnostics: Arc>>>, open_documents: Mutex>, next_request_id: AtomicI64, @@ -59,7 +61,7 @@ impl LspClient { client.spawn_reader(stdout); if let Some(stderr) = stderr { - client.spawn_stderr_drain(stderr); + Self::spawn_stderr_drain(stderr); } client.initialize().await?; Ok(client) @@ -282,8 +284,8 @@ impl LspClient { if let Err(error) = result { let mut pending = pending_requests.lock().await; let drained = pending - .iter() - .map(|(id, _)| *id) + .keys() + .copied() .collect::>(); for id in drained { if let Some(sender) = pending.remove(&id) { @@ -294,7 +296,7 @@ impl LspClient { }); } - fn spawn_stderr_drain(&self, stderr: R) + fn spawn_stderr_drain(stderr: R) where R: AsyncRead + Unpin + Send + 'static, { diff --git a/crates/plugins/README.md b/crates/plugins/README.md new file mode 100644 index 0000000..ec01ef3 --- /dev/null +++ b/crates/plugins/README.md @@ -0,0 +1,59 @@ +# 插件模块 (plugins) + +本模块实现了 Claw 的插件系统,允许通过外部扩展来增强 AI 的功能,包括自定义工具、命令以及在工具执行前后运行的钩子 (Hooks)。 + +## 概览 + +`plugins` 模块的主要职责是: +- 定义插件的清单格式 (`plugin.json`) 和元数据结构。 +- 管理插件的完整生命周期:安装、加载、初始化、启用/禁用、更新和卸载。 +- 提供插件类型的抽象: + - **Builtin (内置)**:编译在程序内部的插件。 + - **Bundled (绑定)**:随应用程序分发但作为独立文件存在的插件。 + - **External (外部)**:用户自行安装或从远程仓库下载的插件。 +- 实现插件隔离与执行机制,支持插件定义的自定义工具。 + +## 关键特性 + +- **插件清单 (PluginManifest)**:每个插件必须包含一个 `plugin.json`,详细说明其名称、版本、所需权限、钩子、生命周期脚本以及它所暴露的工具。 +- **自定义工具 (PluginTool)**:插件可以定义全新的工具供 AI 调用。这些工具在执行时被作为独立的外部进程启动。 +- **钩子系统 (Hooks)**:支持 `PreToolUse` 和 `PostToolUse` 钩子,允许插件在 AI 调用任何工具之前或之后执行特定的逻辑。 +- **生命周期管理**:提供 `Init` 和 `Shutdown` 阶段,允许插件在加载时进行环境准备,在卸载或关闭时进行清理。 +- **权限模型**:强制要求插件声明权限(`read`, `write`, `execute`),并为定义的工具指定安全级别(`read-only`, `workspace-write`, `danger-full-access`)。 + +## 实现逻辑 + +### 核心模块 + +- **`lib.rs`**: 包含了插件定义的各种结构体(Manifest, Metadata, Tool, Permission 等)以及插件特性的定义。 +- **`manager.rs`**: 实现了 `PluginManager`,负责插件在磁盘上的组织、注册表的维护以及安装/更新逻辑。 +- **`hooks.rs`**: 实现了钩子执行器 (`HookRunner`),负责在正确的时机触发插件定义的脚本。 + +### 插件加载与执行流程 + +1. `PluginManager` 扫描指定的目录(内置、绑定及外部安装目录)。 +2. 读取并验证每个插件的 `plugin.json`。 +3. 如果插件被启用,则初始化该插件并将其定义的工具注册到系统的全局工具注册表中。 +4. 当 AI 调用插件工具时,系统根据清单中定义的命令行信息启动一个子进程,并通过标准输入/环境变量传递参数。 +5. 钩子逻辑会在工具执行的生命周期内被自动触发。 + +## 使用示例 (插件定义样例) + +```json +{ + "name": "my-custom-plugin", + "version": "1.0.0", + "description": "一个演示插件", + "permissions": ["read", "execute"], + "tools": [ + { + "name": "custom_search", + "description": "执行自定义搜索", + "inputSchema": { "type": "object", "properties": { "query": { "type": "string" } } }, + "command": "python3", + "args": ["search_script.py"], + "requiredPermission": "read-only" + } + ] +} +``` diff --git a/crates/plugins/src/hooks.rs b/crates/plugins/src/hooks.rs index fde23e8..165efc2 100644 --- a/crates/plugins/src/hooks.rs +++ b/crates/plugins/src/hooks.rs @@ -1,4 +1,5 @@ use std::ffi::OsStr; +#[cfg(not(windows))] use std::path::Path; use std::process::Command; diff --git a/crates/runtime/README.md b/crates/runtime/README.md new file mode 100644 index 0000000..670d297 --- /dev/null +++ b/crates/runtime/README.md @@ -0,0 +1,60 @@ +# 运行时模块 (runtime) + +本模块是 Claw 的核心引擎,负责协调 AI 模型、工具执行、会话管理和权限控制之间的所有交互。 + +## 概览 + +`runtime` 模块是整个系统的“中枢神经”,其主要职责包括: +- **对话驱动**:管理“用户-助手-工具”的循环迭代。 +- **会话持久化**:负责会话的加载、保存及历史记录的压缩 (Compaction)。 +- **MCP 客户端**:实现模型上下文协议 (Model Context Protocol),支持与外部 MCP 服务器通信。 +- **安全沙箱与权限**:实施基于策略的工具执行权限检查。 +- **上下文构建**:动态生成系统提示词 (System Prompt),集成工作区上下文。 +- **消耗统计**:精确跟踪 Token 使用情况和 Token 缓存状态。 + +## 关键特性 + +- **ConversationRuntime**:核心驱动类,支持流式响应处理和多轮工具调用迭代。 +- **权限引擎 (Permissions)**:提供多种模式(`ReadOnly`, `WorkspaceWrite`, `DangerFullAccess`),并支持交互式权限确认。 +- **会话压缩 (Compaction)**:当对话历史过长影响性能或成本时,自动将旧消息总结为摘要,保持上下文精简。 +- **钩子集成 (Hooks)**:在工具执行的前后触发插件定义的钩子,支持干预工具输入或处理执行结果。 +- **沙箱执行 (Sandbox)**:为 Bash 等敏感工具提供受限的执行环境。 + +## 实现逻辑 + +### 核心子模块 + +- **`conversation.rs`**: 定义了核心的 `ConversationRuntime` 和 `ApiClient`/`ToolExecutor` 特性。 +- **`mcp_stdio.rs` / `mcp_client.rs`**: 实现了完整的 MCP 规范,支持通过标准输入/输出与外部工具服务器交互。 +- **`session.rs`**: 定义了消息模型 (`ConversationMessage`)、内容块 (`ContentBlock`) 和会话序列化逻辑。 +- **`permissions.rs`**: 实现了权限审核逻辑和提示器接口。 +- **`compact.rs`**: 包含了基于 LLM 的会话摘要生成和历史裁剪算法。 +- **`config.rs`**: 负责加载和合并多层级的配置文件。 + +### 对话循环流程 (run_turn) + +1. 将用户输入推入 `Session`。 +2. 调用 `ApiClient` 发起流式请求。 +3. 监听 `AssistantEvent`,解析文本内容和工具调用请求。 +4. **工具权限审核**:针对每个 `ToolUse`,根据 `PermissionPolicy` 决定是允许、拒绝还是询问用户。 +5. **执行工具**:若允许,则通过 `ToolExecutor`(或 MCP 客户端)执行工具,并运行相关的 `Pre/Post Hooks`。 +6. 将工具结果反馈给 AI,进入下一轮迭代,直到 AI 给出最终回复。 + +## 使用示例 (内部) + +```rust +use runtime::{ConversationRuntime, Session, PermissionPolicy, PermissionMode}; + +// 初始化运行时 +let mut runtime = ConversationRuntime::new( + Session::new(), + api_client, + tool_executor, + PermissionPolicy::new(PermissionMode::WorkspaceWrite), + system_prompt, +); + +// 运行一轮对话 +let summary = runtime.run_turn("帮我重构 src/lib.rs", Some(&mut cli_prompter))?; +println!("共迭代 {} 次,消耗 {} tokens", summary.iterations, summary.usage.total_tokens()); +``` diff --git a/crates/runtime/src/compact.rs b/crates/runtime/src/compact.rs index a0792da..438ee10 100644 --- a/crates/runtime/src/compact.rs +++ b/crates/runtime/src/compact.rs @@ -160,7 +160,9 @@ fn summarize_messages(messages: &[ConversationMessage]) -> String { .filter_map(|block| match block { ContentBlock::ToolUse { name, .. } => Some(name.as_str()), ContentBlock::ToolResult { tool_name, .. } => Some(tool_name.as_str()), - ContentBlock::Text { .. } => None, + ContentBlock::Text { .. } + | ContentBlock::Thinking { .. } + | ContentBlock::RedactedThinking { .. } => None, }) .collect::>(); tool_names.sort_unstable(); @@ -275,6 +277,8 @@ fn summarize_block(block: &ContentBlock) -> String { "tool_result {tool_name}: {}{output}", if *is_error { "error " } else { "" } ), + ContentBlock::Thinking { thinking, .. } => format!("thinking: {thinking}"), + ContentBlock::RedactedThinking { .. } => "thinking: ".to_string(), }; truncate_summary(&raw, 160) } @@ -324,6 +328,8 @@ fn collect_key_files(messages: &[ConversationMessage]) -> Vec { .flat_map(|message| message.blocks.iter()) .map(|block| match block { ContentBlock::Text { text } => text.as_str(), + ContentBlock::Thinking { thinking, .. } => thinking.as_str(), + ContentBlock::RedactedThinking { .. } => "", ContentBlock::ToolUse { input, .. } => input.as_str(), ContentBlock::ToolResult { output, .. } => output.as_str(), }) @@ -348,7 +354,9 @@ fn first_text_block(message: &ConversationMessage) -> Option<&str> { ContentBlock::Text { text } if !text.trim().is_empty() => Some(text.as_str()), ContentBlock::ToolUse { .. } | ContentBlock::ToolResult { .. } - | ContentBlock::Text { .. } => None, + | ContentBlock::Text { .. } + | ContentBlock::Thinking { .. } + | ContentBlock::RedactedThinking { .. } => None, }) } @@ -394,6 +402,8 @@ fn estimate_message_tokens(message: &ConversationMessage) -> usize { .iter() .map(|block| match block { ContentBlock::Text { text } => text.len() / 4 + 1, + ContentBlock::Thinking { thinking, .. } => thinking.len() / 4 + 1, + ContentBlock::RedactedThinking { .. } => 1, ContentBlock::ToolUse { name, input, .. } => (name.len() + input.len()) / 4 + 1, ContentBlock::ToolResult { tool_name, output, .. diff --git a/crates/runtime/src/conversation.rs b/crates/runtime/src/conversation.rs index 8411b8d..0a5435b 100644 --- a/crates/runtime/src/conversation.rs +++ b/crates/runtime/src/conversation.rs @@ -19,6 +19,7 @@ pub struct ApiRequest { #[derive(Debug, Clone, PartialEq, Eq)] pub enum AssistantEvent { TextDelta(String), + ThinkingDelta(String), ToolUse { id: String, name: String, @@ -122,6 +123,7 @@ where ) } + #[allow(clippy::needless_pass_by_value)] #[must_use] pub fn new_with_features( session: Session, @@ -299,6 +301,16 @@ fn build_assistant_message( for event in events { match event { AssistantEvent::TextDelta(delta) => text.push_str(&delta), + AssistantEvent::ThinkingDelta(delta) => { + if let Some(ContentBlock::Thinking { thinking, .. }) = blocks.last_mut() { + thinking.push_str(&delta); + } else { + blocks.push(ContentBlock::Thinking { + thinking: delta, + signature: None, + }); + } + } AssistantEvent::ToolUse { id, name, input } => { flush_text_block(&mut text, &mut blocks); blocks.push(ContentBlock::ToolUse { id, name, input }); diff --git a/crates/runtime/src/hooks.rs b/crates/runtime/src/hooks.rs index 63ef9ff..eaa7f85 100644 --- a/crates/runtime/src/hooks.rs +++ b/crates/runtime/src/hooks.rs @@ -74,7 +74,7 @@ impl HookRunner { #[must_use] pub fn run_pre_tool_use(&self, tool_name: &str, tool_input: &str) -> HookRunResult { - self.run_commands( + Self::run_commands( HookEvent::PreToolUse, self.config.pre_tool_use(), tool_name, @@ -92,7 +92,7 @@ impl HookRunner { tool_output: &str, is_error: bool, ) -> HookRunResult { - self.run_commands( + Self::run_commands( HookEvent::PostToolUse, self.config.post_tool_use(), tool_name, @@ -103,7 +103,6 @@ impl HookRunner { } fn run_commands( - &self, event: HookEvent, commands: &[String], tool_name: &str, @@ -238,7 +237,7 @@ fn format_hook_warning(command: &str, code: i32, stdout: Option<&str>, stderr: & fn shell_command(command: &str) -> CommandWithStdin { #[cfg(windows)] - let mut command_builder = { + let command_builder = { let mut command_builder = Command::new("cmd"); command_builder.arg("/C").arg(command); CommandWithStdin::new(command_builder) diff --git a/crates/runtime/src/json.rs b/crates/runtime/src/json.rs index d829a15..2c89795 100644 --- a/crates/runtime/src/json.rs +++ b/crates/runtime/src/json.rs @@ -1,7 +1,8 @@ use std::collections::BTreeMap; use std::fmt::{Display, Formatter}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum JsonValue { Null, Bool(bool), diff --git a/crates/runtime/src/mcp_stdio.rs b/crates/runtime/src/mcp_stdio.rs index 56b15ed..f3f16b9 100644 --- a/crates/runtime/src/mcp_stdio.rs +++ b/crates/runtime/src/mcp_stdio.rs @@ -892,9 +892,12 @@ mod tests { ] .join("\n"); fs::write(&script_path, script).expect("write script"); - let mut permissions = fs::metadata(&script_path).expect("metadata").permissions(); - permissions.set_mode(0o755); - fs::set_permissions(&script_path, permissions).expect("chmod"); + #[cfg(unix)] + { + let mut permissions = fs::metadata(&script_path).expect("metadata").permissions(); + permissions.set_mode(0o755); + fs::set_permissions(&script_path, permissions).expect("chmod"); + } script_path } @@ -1018,9 +1021,12 @@ mod tests { ] .join("\n"); fs::write(&script_path, script).expect("write script"); - let mut permissions = fs::metadata(&script_path).expect("metadata").permissions(); - permissions.set_mode(0o755); - fs::set_permissions(&script_path, permissions).expect("chmod"); + #[cfg(unix)] + { + let mut permissions = fs::metadata(&script_path).expect("metadata").permissions(); + permissions.set_mode(0o755); + fs::set_permissions(&script_path, permissions).expect("chmod"); + } script_path } @@ -1122,9 +1128,12 @@ mod tests { ] .join("\n"); fs::write(&script_path, script).expect("write script"); - let mut permissions = fs::metadata(&script_path).expect("metadata").permissions(); - permissions.set_mode(0o755); - fs::set_permissions(&script_path, permissions).expect("chmod"); + #[cfg(unix)] + { + let mut permissions = fs::metadata(&script_path).expect("metadata").permissions(); + permissions.set_mode(0o755); + fs::set_permissions(&script_path, permissions).expect("chmod"); + } script_path } diff --git a/crates/runtime/src/session.rs b/crates/runtime/src/session.rs index ec37070..ad3e119 100644 --- a/crates/runtime/src/session.rs +++ b/crates/runtime/src/session.rs @@ -20,6 +20,14 @@ pub enum MessageRole { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ContentBlock { + Thinking { + thinking: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + signature: Option, + }, + RedactedThinking { + data: JsonValue, + }, Text { text: String, }, @@ -261,6 +269,26 @@ impl ContentBlock { object.insert("type".to_string(), JsonValue::String("text".to_string())); object.insert("text".to_string(), JsonValue::String(text.clone())); } + Self::Thinking { thinking, signature } => { + object.insert("type".to_string(), JsonValue::String("thinking".to_string())); + object.insert( + "thinking".to_string(), + JsonValue::String(thinking.clone()), + ); + if let Some(signature) = signature { + object.insert( + "signature".to_string(), + JsonValue::String(signature.clone()), + ); + } + } + Self::RedactedThinking { data } => { + object.insert( + "type".to_string(), + JsonValue::String("redacted_thinking".to_string()), + ); + object.insert("data".to_string(), data.clone()); + } Self::ToolUse { id, name, input } => { object.insert( "type".to_string(), @@ -312,6 +340,13 @@ impl ContentBlock { name: required_string(object, "name")?, input: required_string(object, "input")?, }), + "thinking" => Ok(Self::Thinking { + thinking: required_string(object, "thinking")?, + signature: object.get("signature").and_then(JsonValue::as_str).map(ToOwned::to_owned), + }), + "redacted_thinking" => Ok(Self::RedactedThinking { + data: object.get("data").cloned().ok_or_else(|| SessionError::Format("missing data".to_string()))?, + }), "tool_result" => Ok(Self::ToolResult { tool_use_id: required_string(object, "tool_use_id")?, tool_name: required_string(object, "tool_name")?, diff --git a/crates/rusty-claude-cli/README.md b/crates/rusty-claude-cli/README.md new file mode 100644 index 0000000..2ff1bf5 --- /dev/null +++ b/crates/rusty-claude-cli/README.md @@ -0,0 +1,64 @@ +# Rusty Claude CLI 模块 (rusty-claude-cli) + +本模块提供了 Claw 命令行界面的另一个功能完整的实现。它集成了对话、工具执行、插件扩展以及身份验证等核心功能。 + +## 概览 + +`rusty-claude-cli` 是一个全功能的 CLI 应用程序,其主要职责包括: +- **用户交互**:提供交互式 REPL 和非交互式命令执行(`prompt` 子命令)。 +- **环境初始化**:处理项目初始化 (`init`) 和配置加载。 +- **身份验证**:通过本地回环服务器处理 OAuth 登录流程。 +- **状态渲染**:实现丰富的终端 UI 效果,如 Markdown 渲染、语法高亮和动态加载动画 (Spinner)。 +- **会话管理**:支持从保存的文件中恢复会话并执行追加的斜杠命令。 + +## 与 `claw-cli` 的关系 + +虽然 `rusty-claude-cli` 和 `claw-cli` 都生成名为 `claw` 的二进制文件,但 `rusty-claude-cli` 包含更复杂的集成逻辑: +- 它直接引用了几乎所有的核心 crate(`runtime`, `api`, `tools`, `plugins`, `commands`)。 +- 它的 `main.rs` 实现非常庞大,包含了大量的业务编排逻辑。 +- 它可以作为一个独立的、集成度极高的 CLI 参考实现。 + +## 关键特性 + +- **多功能子命令**: + - `prompt`:快速运行单次推理。 + - `login`/`logout`:OAuth 身份验证管理。 + - `init`:项目环境自举。 + - `bootstrap-plan`:查看系统的启动阶段。 + - `dump-manifests`:从上游源码中提取并显示功能清单。 +- **增强的 REPL**: + - 支持多行输入和历史记录。 + - 集成了斜杠命令处理引擎。 + - 提供详细的消耗统计和权限模式切换报告。 +- **灵活的权限控制**:支持通过命令行参数 `--permission-mode` 或环境变量动态调整权限级别。 + +## 实现逻辑 + +### 核心子模块 + +- **`main.rs`**: 程序的入口,包含了复杂的参数解析逻辑和 REPL 循环。 +- **`render.rs`**: 封装了 `TerminalRenderer` 和 `Spinner`,负责所有的终端输出美化。 +- **`input.rs`**: 处理从标准输入读取数据及命令解析。 +- **`init.rs`**: 专注于仓库的初始化和 `.claw.md` 文件的生成。 +- **`app.rs`**: 可能包含应用程序级别的高层状态管理(取决于具体实现)。 + +### 工作流程 + +1. 程序启动,解析命令行参数。 +2. 根据参数决定是执行单次任务还是进入 REPL 模式。 +3. 在 REPL 模式下,初始化 `ConversationRuntime`。 +4. 进入循环:读取用户输入 -> 处理斜杠命令或发送给 AI -> 渲染响应 -> 执行工具 -> 循环。 +5. 会话数据根据需要保存或恢复。 + +## 使用示例 + +```bash +# 启动交互模式 +cargo run -p rusty-claude-cli --bin claw + +# 直接运行 Prompt +cargo run -p rusty-claude-cli --bin claw prompt "检查代码中的内存泄漏" + +# 恢复之前的会话并执行压缩 +cargo run -p rusty-claude-cli --bin claw --resume session.json /compact +``` diff --git a/crates/rusty-claude-cli/src/main.rs b/crates/rusty-claude-cli/src/main.rs index ecc5550..bf16026 100644 --- a/crates/rusty-claude-cli/src/main.rs +++ b/crates/rusty-claude-cli/src/main.rs @@ -35,7 +35,7 @@ use runtime::{ }; use serde_json::json; use tools::{execute_tool, mvp_tool_specs, ToolSpec}; -use plugins::{self, PluginManager, PluginManagerConfig}; +use plugins::{self}; const DEFAULT_MODEL: &str = "claude-opus-4-6"; const DEFAULT_MAX_TOKENS: u32 = 32; @@ -1870,6 +1870,12 @@ fn render_export_text(session: &Session) -> String { for block in &message.blocks { match block { ContentBlock::Text { text } => lines.push(text.clone()), + ContentBlock::Thinking { thinking, .. } => { + lines.push(format!("[thinking] {thinking}")); + } + ContentBlock::RedactedThinking { .. } => { + lines.push("[thinking] ".to_string()); + } ContentBlock::ToolUse { id, name, input } => { lines.push(format!("[tool_use id={id} name={name}] {input}")); } @@ -2352,6 +2358,16 @@ fn convert_messages(messages: &[ConversationMessage]) -> Vec { .iter() .map(|block| match block { ContentBlock::Text { text } => InputContentBlock::Text { text: text.clone() }, + ContentBlock::Thinking { + thinking, + signature, + } => InputContentBlock::Thinking { + thinking: thinking.clone(), + signature: signature.clone(), + }, + ContentBlock::RedactedThinking { data } => InputContentBlock::RedactedThinking { + data: serde_json::from_str(&data.render()).unwrap_or(serde_json::Value::Null), + }, ContentBlock::ToolUse { id, name, input } => InputContentBlock::ToolUse { id: id.clone(), name: name.clone(), diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 9151aef..c4fa9df 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -5,6 +5,10 @@ edition.workspace = true license.workspace = true publish.workspace = true +[[bin]] +name = "claw-server" +path = "src/main.rs" + [dependencies] async-stream = "0.3" axum = "0.8" @@ -12,6 +16,14 @@ runtime = { path = "../runtime" } serde = { version = "1", features = ["derive"] } serde_json.workspace = true tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "net", "time"] } +tower = "0.5" +tower-http = { version = "0.6", features = ["cors"] } +api = { path = "../api" } +tools = { path = "../tools" } +plugins = { path = "../plugins" } +commands = { path = "../commands" } +dotenvy = "0.15" +chrono = "0.4" [dev-dependencies] reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] } diff --git a/crates/server/README.md b/crates/server/README.md new file mode 100644 index 0000000..bbdef3e --- /dev/null +++ b/crates/server/README.md @@ -0,0 +1,57 @@ +# 服务模块 (server) + +本模块提供了一个基于 HTTP 的 RESTful API 和 Server-Sent Events (SSE) 流接口,允许通过网络远程管理和与 Claw 会话进行交互。 + +## 概览 + +`server` 模块将 `runtime` 的核心功能封装为 Web 服务,其主要职责包括: +- **会话管理**:提供创建、列出和获取会话详情的端点。 +- **消息分发**:接收用户消息并将其路由到相应的会话实例。 +- **实时流推送**:通过 SSE 接口实时推送会话事件(如 AI 响应消息、状态快照)。 +- **状态维护**:在内存中管理多个活跃会话的生命周期。 + +## 关键特性 + +- **RESTful API**:使用 `axum` 框架实现,遵循现代 Web 服务标准。 +- **事件流 (SSE)**:支持 `text/event-stream`,允许客户端实时订阅会话更新。 +- **并发处理**:利用 `tokio` 和 `broadcast` 频道,支持多个客户端同时监听同一会话的事件。 +- **快照机制**:在建立连接时发送当前会话的完整快照,确保客户端能够同步历史状态。 + +## 实现逻辑 + +### 核心接口 (API Routes) + +- `POST /sessions`: 创建一个新的对话会话。 +- `GET /sessions`: 列出所有活跃会话的简要信息。 +- `GET /sessions/{id}`: 获取指定会话的完整详细信息。 +- `POST /sessions/{id}/message`: 向指定会话发送一条新消息。 +- `GET /sessions/{id}/events`: 建立 SSE 连接,订阅该会话的实时事件流。 + +### 核心结构 + +- **`AppState`**: 存储全局状态,包括 `SessionStore` (由 `RwLock` 保护的哈希表) 和会话 ID 分配器。 +- **`Session`**: 封装了 `runtime::Session` 实例,并包含一个用于广播事件的 `broadcast::Sender`。 +- **`SessionEvent`**: 定义了流中传输的事件类型,包括 `Snapshot` (快照) 和 `Message` (新消息)。 + +### 工作流程 + +1. 启动服务并初始化 `AppState`。 +2. 客户端通过 `POST /sessions` 开启一个新会话。 +3. 客户端连接 `GET /sessions/{id}/events` 以监听响应。 +4. 客户端通过 `POST /sessions/{id}/message` 发送 Prompt。 +5. 服务端将消息存入 `runtime::Session`,并触发广播。SSE 流将该消息及后续的 AI 响应实时推送回客户端。 + +## 使用示例 (内部) + +```rust +use server::{app, AppState}; +use axum::Router; + +// 创建应用路由 +let state = AppState::new(); +let router = app(state); + +// 启动服务(示例) +let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); +axum::serve(listener, router).await.unwrap(); +``` diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index b3386ea..25a3b7c 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -1,7 +1,8 @@ -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::convert::Infallible; +use std::path::PathBuf; use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use async_stream::stream; @@ -11,77 +12,72 @@ use axum::response::sse::{Event, KeepAlive, Sse}; use axum::response::IntoResponse; use axum::routing::{get, post}; use axum::{Json, Router}; -use runtime::{ConversationMessage, Session as RuntimeSession}; +use plugins::{PluginManager, PluginManagerConfig}; +use runtime::{ + ApiClient, ApiRequest, AssistantEvent, CompactionConfig, ConfigLoader, ContentBlock, + ConversationMessage, ConversationRuntime, MessageRole, PermissionMode, PermissionPolicy, + RuntimeConfig, RuntimeFeatureConfig, RuntimeError, Session as RuntimeSession, TokenUsage, + ToolError, ToolExecutor, +}; +use api::{ + max_tokens_for_model, resolve_startup_auth_source, AuthSource, ClawApiClient, + ContentBlockDelta, InputContentBlock, InputMessage, MessageRequest, + MessageResponse, OutputContentBlock, + StreamEvent as ApiStreamEvent, ToolChoice, ToolResultContentBlock, +}; use serde::{Deserialize, Serialize}; use tokio::sync::{broadcast, RwLock}; +use tools::GlobalToolRegistry; +use tower_http::cors::{Any, CorsLayer}; pub type SessionId = String; pub type SessionStore = Arc>>; -const BROADCAST_CAPACITY: usize = 64; +const BROADCAST_CAPACITY: usize = 256; -#[derive(Clone)] -pub struct AppState { - sessions: SessionStore, - next_session_id: Arc, -} - -impl AppState { - #[must_use] - pub fn new() -> Self { - Self { - sessions: Arc::new(RwLock::new(HashMap::new())), - next_session_id: Arc::new(AtomicU64::new(1)), - } - } - - fn allocate_session_id(&self) -> SessionId { - let id = self.next_session_id.fetch_add(1, Ordering::Relaxed); - format!("session-{id}") - } -} - -impl Default for AppState { - fn default() -> Self { - Self::new() - } -} - -#[derive(Clone)] -pub struct Session { - pub id: SessionId, - pub created_at: u64, - pub conversation: RuntimeSession, - events: broadcast::Sender, -} - -impl Session { - fn new(id: SessionId) -> Self { - let (events, _) = broadcast::channel(BROADCAST_CAPACITY); - Self { - id, - created_at: unix_timestamp_millis(), - conversation: RuntimeSession::new(), - events, - } - } - - fn subscribe(&self) -> broadcast::Receiver { - self.events.subscribe() - } -} +// ── SSE 事件类型 ────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type", rename_all = "snake_case")] enum SessionEvent { Snapshot { session_id: SessionId, - session: RuntimeSession, + messages: Vec, }, Message { session_id: SessionId, message: ConversationMessage, }, + MessageDelta { + session_id: SessionId, + text: String, + }, + ToolUseStart { + session_id: SessionId, + tool_use_id: String, + tool_name: String, + input: String, + }, + ToolResult { + session_id: SessionId, + tool_use_id: String, + tool_name: String, + output: String, + is_error: bool, + }, + ThinkingDelta { + session_id: SessionId, + thinking: String, + }, + Usage { + session_id: SessionId, + usage: TokenUsage, + }, + TurnComplete { + session_id: SessionId, + usage: TokenUsage, + iterations: usize, + }, } impl SessionEvent { @@ -89,6 +85,12 @@ impl SessionEvent { match self { Self::Snapshot { .. } => "snapshot", Self::Message { .. } => "message", + Self::MessageDelta { .. } => "message_delta", + Self::ToolUseStart { .. } => "tool_use_start", + Self::ToolResult { .. } => "tool_result", + Self::ThinkingDelta { .. } => "thinking_delta", + Self::Usage { .. } => "usage", + Self::TurnComplete { .. } => "turn_complete", } } @@ -99,6 +101,598 @@ impl SessionEvent { } } +// ── ServerApiClient:实现 runtime::ApiClient trait ──────────────────── + +struct ServerApiClient { + client: ClawApiClient, + model: String, + tool_registry: GlobalToolRegistry, + allowed_tools: Option>, + enable_tools: bool, + broadcaster: broadcast::Sender, + session_id: SessionId, +} + +impl ApiClient for ServerApiClient { + #[allow(clippy::too_many_lines)] + fn stream(&mut self, request: ApiRequest) -> Result, RuntimeError> { + let message_request = MessageRequest { + model: self.model.clone(), + max_tokens: max_tokens_for_model(&self.model), + messages: convert_messages(&request.messages), + system: (!request.system_prompt.is_empty()) + .then(|| request.system_prompt.join("\n\n")), + tools: self + .enable_tools + .then(|| self.tool_registry.definitions(self.allowed_tools.as_ref())), + tool_choice: self.enable_tools.then_some(ToolChoice::Auto), + stream: true, + }; + + let rt = tokio::runtime::Runtime::new() + .map_err(|e| RuntimeError::new(format!("failed to create tokio runtime: {e}")))?; + + rt.block_on(async { + let mut stream = self + .client + .stream_message(&message_request) + .await + .map_err(|error| RuntimeError::new(error.to_string()))?; + + let mut events = Vec::new(); + let mut pending_tool: Option<(String, String, String)> = None; + let mut saw_stop = false; + + while let Some(event) = stream + .next_event() + .await + .map_err(|error| RuntimeError::new(error.to_string()))? + { + match event { + ApiStreamEvent::MessageStart(start) => { + for block in start.message.content { + push_output_block( + block, + &mut events, + &mut pending_tool, + &self.broadcaster, + &self.session_id, + true, + ); + } + } + ApiStreamEvent::ContentBlockStart(start) => { + push_output_block( + start.content_block, + &mut events, + &mut pending_tool, + &self.broadcaster, + &self.session_id, + true, + ); + } + ApiStreamEvent::ContentBlockDelta(delta) => match delta.delta { + ContentBlockDelta::TextDelta { text } => { + if !text.is_empty() { + let _ = self.broadcaster.send(SessionEvent::MessageDelta { + session_id: self.session_id.clone(), + text: text.clone(), + }); + events.push(AssistantEvent::TextDelta(text)); + } + } + ContentBlockDelta::InputJsonDelta { partial_json } => { + if let Some((_, _, input)) = &mut pending_tool { + input.push_str(&partial_json); + } + } + ContentBlockDelta::ThinkingDelta { thinking } => { + if !thinking.is_empty() { + let _ = self.broadcaster.send(SessionEvent::ThinkingDelta { + session_id: self.session_id.clone(), + thinking: thinking.clone(), + }); + events.push(AssistantEvent::ThinkingDelta(thinking)); + } + } + ContentBlockDelta::SignatureDelta { .. } => {} + }, + ApiStreamEvent::ContentBlockStop(_) => { + if let Some((id, name, input)) = pending_tool.take() { + let _ = self.broadcaster.send(SessionEvent::ToolUseStart { + session_id: self.session_id.clone(), + tool_use_id: id.clone(), + tool_name: name.clone(), + input: input.clone(), + }); + events.push(AssistantEvent::ToolUse { id, name, input }); + } + } + ApiStreamEvent::MessageDelta(delta) => { + let usage = TokenUsage { + input_tokens: delta.usage.input_tokens, + output_tokens: delta.usage.output_tokens, + cache_creation_input_tokens: delta + .usage + .cache_creation_input_tokens, + cache_read_input_tokens: delta.usage.cache_read_input_tokens, + }; + let _ = self.broadcaster.send(SessionEvent::Usage { + session_id: self.session_id.clone(), + usage, + }); + events.push(AssistantEvent::Usage(usage)); + } + ApiStreamEvent::MessageStop(_) => { + saw_stop = true; + events.push(AssistantEvent::MessageStop); + } + } + } + + if !saw_stop + && events.iter().any(|e| { + matches!(e, AssistantEvent::TextDelta(t) if !t.is_empty()) + || matches!(e, AssistantEvent::ToolUse { .. }) + }) + { + events.push(AssistantEvent::MessageStop); + } + + // 非流式回退:如果流式响应未产生 MessageStop,用非流式请求重试 + if !events + .iter() + .any(|e| matches!(e, AssistantEvent::MessageStop)) + { + let response = self + .client + .send_message(&MessageRequest { + stream: false, + ..message_request + }) + .await + .map_err(|error| RuntimeError::new(error.to_string()))?; + return Ok(response_to_events(response, &self.broadcaster, &self.session_id)); + } + + Ok(events) + }) + } +} + +/// 将 API 返回的 `OutputContentBlock` 推入事件流 +/// +/// 流式模式下(`streaming_tool_input=true`),`ContentBlockStart` 中的 `ToolUse` +/// input 通常为空 JSON `{}`,真正的 input 通过 `InputJsonDelta` 累积到 `pending_tool`。 +/// `ContentBlockStop` 时才产生最终的 `ToolUse` 事件。 +fn push_output_block( + block: OutputContentBlock, + events: &mut Vec, + pending_tool: &mut Option<(String, String, String)>, + broadcaster: &broadcast::Sender, + session_id: &str, + streaming_tool_input: bool, +) { + match block { + OutputContentBlock::Text { text } => { + if !text.is_empty() { + let _ = broadcaster.send(SessionEvent::MessageDelta { + session_id: session_id.to_string(), + text: text.clone(), + }); + events.push(AssistantEvent::TextDelta(text)); + } + } + OutputContentBlock::ToolUse { id, name, input } => { + // 流式模式下,初始 ContentBlockStart 的 input 为空 `{}`, + // 真正的 input 由 InputJsonDelta 事件累积,在 ContentBlockStop 时发射。 + // 非流式模式下直接使用完整的 input。 + let initial_input = if streaming_tool_input + && input.is_object() + && input.as_object().is_some_and(serde_json::Map::is_empty) + { + String::new() + } else { + input.to_string() + }; + *pending_tool = Some((id, name, initial_input)); + } + OutputContentBlock::Thinking { thinking, .. } => { + if !thinking.is_empty() { + let _ = broadcaster.send(SessionEvent::ThinkingDelta { + session_id: session_id.to_string(), + thinking: thinking.clone(), + }); + events.push(AssistantEvent::ThinkingDelta(thinking)); + } + } + OutputContentBlock::RedactedThinking { .. } => {} + } +} + +/// 将非流式 `MessageResponse` 转换为 `AssistantEvent` 序列 +fn response_to_events( + response: MessageResponse, + broadcaster: &broadcast::Sender, + session_id: &str, +) -> Vec { + let mut events = Vec::new(); + let mut pending_tool = None; + + for block in response.content { + push_output_block(block, &mut events, &mut pending_tool, broadcaster, session_id, false); + // 非流式模式下 ContentBlockStart 后立即发射 ToolUse + if let Some((id, name, input)) = pending_tool.take() { + let _ = broadcaster.send(SessionEvent::ToolUseStart { + session_id: session_id.to_string(), + tool_use_id: id.clone(), + tool_name: name.clone(), + input: input.clone(), + }); + events.push(AssistantEvent::ToolUse { id, name, input }); + } + } + + let usage = TokenUsage { + input_tokens: response.usage.input_tokens, + output_tokens: response.usage.output_tokens, + cache_creation_input_tokens: response.usage.cache_creation_input_tokens, + cache_read_input_tokens: response.usage.cache_read_input_tokens, + }; + let _ = broadcaster.send(SessionEvent::Usage { + session_id: session_id.to_string(), + usage, + }); + events.push(AssistantEvent::Usage(usage)); + events.push(AssistantEvent::MessageStop); + events +} + +// ── ServerToolExecutor:实现 runtime::ToolExecutor trait ────────────── + +struct ServerToolExecutor { + tool_registry: GlobalToolRegistry, + allowed_tools: Option>, + broadcaster: broadcast::Sender, + session_id: SessionId, +} + +impl ToolExecutor for ServerToolExecutor { + fn execute(&mut self, tool_name: &str, input: &str) -> Result { + if self + .allowed_tools + .as_ref() + .is_some_and(|allowed| !allowed.contains(tool_name)) + { + return Err(ToolError::new(format!( + "tool `{tool_name}` is not enabled by the current allowedTools setting" + ))); + } + let value = serde_json::from_str(input) + .map_err(|error| ToolError::new(format!("invalid tool input JSON: {error}")))?; + match self.tool_registry.execute(tool_name, &value) { + Ok(output) => { + let _ = self.broadcaster.send(SessionEvent::ToolResult { + session_id: self.session_id.clone(), + tool_use_id: String::new(), + tool_name: tool_name.to_string(), + output: output.clone(), + is_error: false, + }); + Ok(output) + } + Err(error) => { + let _ = self.broadcaster.send(SessionEvent::ToolResult { + session_id: self.session_id.clone(), + tool_use_id: String::new(), + tool_name: tool_name.to_string(), + output: error.clone(), + is_error: true, + }); + Err(ToolError::new(error)) + } + } + } +} + +// ── 消息格式转换 ───────────────────────────────────────────────────── + +fn convert_messages(messages: &[ConversationMessage]) -> Vec { + messages + .iter() + .filter_map(|message| { + let role = match message.role { + MessageRole::System | MessageRole::User | MessageRole::Tool => "user", + MessageRole::Assistant => "assistant", + }; + let content = message + .blocks + .iter() + .map(|block| match block { + ContentBlock::Text { text } => { + InputContentBlock::Text { text: text.clone() } + } + ContentBlock::Thinking { + thinking, + signature, + } => InputContentBlock::Thinking { + thinking: thinking.clone(), + signature: signature.clone(), + }, + ContentBlock::RedactedThinking { data } => InputContentBlock::RedactedThinking { + data: serde_json::from_str(&data.render()) + .unwrap_or(serde_json::Value::Null), + }, + ContentBlock::ToolUse { id, name, input } => InputContentBlock::ToolUse { + id: id.clone(), + name: name.clone(), + input: serde_json::from_str(input) + .unwrap_or_else(|_| serde_json::json!({ "raw": input })), + }, + ContentBlock::ToolResult { + tool_use_id, + output, + is_error, + .. + } => InputContentBlock::ToolResult { + tool_use_id: tool_use_id.clone(), + content: vec![ToolResultContentBlock::Text { + text: output.clone(), + }], + is_error: *is_error, + }, + }) + .collect::>(); + (!content.is_empty()).then_some(InputMessage { + role: role.to_string(), + content, + }) + }) + .collect() +} + +fn permission_policy(mode: PermissionMode, tool_registry: &GlobalToolRegistry) -> PermissionPolicy { + tool_registry + .permission_specs(None) + .into_iter() + .fold(PermissionPolicy::new(mode), |policy, (name, req)| { + policy.with_tool_requirement(name, req) + }) +} + +// ── AppState ────────────────────────────────────────────────────────── + +#[derive(Clone)] +pub struct AppState { + sessions: SessionStore, + next_session_id: Arc, + model: String, + system_prompt: Vec, + permission_mode: PermissionMode, + feature_config: RuntimeFeatureConfig, + tool_registry: GlobalToolRegistry, + cwd: PathBuf, + sessions_dir: PathBuf, +} + +impl AppState { + pub fn new( + model: String, + system_prompt: Vec, + permission_mode: PermissionMode, + cwd: PathBuf, + ) -> Result> { + let (feature_config, tool_registry) = build_plugin_state(&cwd)?; + let sessions_dir = ensure_sessions_dir(&cwd)?; + + // 从磁盘恢复已有会话 + let (restored_sessions, max_id) = restore_sessions( + &sessions_dir, + &model, + &system_prompt, + permission_mode, + &feature_config, + &tool_registry, + &cwd, + ); + + let next_id = max_id + 1; + + Ok(Self { + sessions: Arc::new(RwLock::new(restored_sessions)), + next_session_id: Arc::new(AtomicU64::new(next_id)), + model, + system_prompt, + permission_mode, + feature_config, + tool_registry, + cwd, + sessions_dir, + }) + } + + fn allocate_session_id(&self) -> SessionId { + let id = self.next_session_id.fetch_add(1, Ordering::Relaxed); + format!("session-{id}") + } + + /// 测试用 AppState(不需要真实 API 密钥) + #[cfg(test)] + fn new_test() -> Self { + let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + let sessions_dir = cwd.join(".claw").join("sessions"); + let _ = std::fs::create_dir_all(&sessions_dir); + Self { + sessions: Arc::new(RwLock::new(HashMap::new())), + next_session_id: Arc::new(AtomicU64::new(1)), + model: "claude-sonnet-4-6".to_string(), + system_prompt: vec!["You are a helpful assistant.".to_string()], + permission_mode: PermissionMode::WorkspaceWrite, + feature_config: RuntimeFeatureConfig::default(), + tool_registry: GlobalToolRegistry::builtin(), + cwd, + sessions_dir, + } + } +} + +/// 构建 `RuntimeFeatureConfig` 和 `GlobalToolRegistry`(与 claw-cli 对齐) +fn build_plugin_state( + cwd: &std::path::Path, +) -> Result<(RuntimeFeatureConfig, GlobalToolRegistry), Box> { + let loader = ConfigLoader::default_for(cwd); + let runtime_config = loader.load()?; + let plugin_manager = build_plugin_manager(cwd, &loader, &runtime_config); + let tool_registry = GlobalToolRegistry::with_plugin_tools(plugin_manager.aggregated_tools()?)?; + Ok((runtime_config.feature_config().clone(), tool_registry)) +} + +fn build_plugin_manager( + cwd: &std::path::Path, + loader: &ConfigLoader, + runtime_config: &RuntimeConfig, +) -> PluginManager { + let plugin_settings = runtime_config.plugins(); + let mut plugin_config = PluginManagerConfig::new(loader.config_home().to_path_buf()); + plugin_config.enabled_plugins = plugin_settings.enabled_plugins().clone(); + plugin_config.external_dirs = plugin_settings + .external_directories() + .iter() + .map(|path| resolve_plugin_path(cwd, loader.config_home(), path)) + .collect(); + plugin_config.install_root = plugin_settings + .install_root() + .map(|path| resolve_plugin_path(cwd, loader.config_home(), path)); + plugin_config.registry_path = plugin_settings + .registry_path() + .map(|path| resolve_plugin_path(cwd, loader.config_home(), path)); + plugin_config.bundled_root = plugin_settings + .bundled_root() + .map(|path| resolve_plugin_path(cwd, loader.config_home(), path)); + PluginManager::new(plugin_config) +} + +fn resolve_plugin_path( + cwd: &std::path::Path, + config_home: &std::path::Path, + value: &str, +) -> PathBuf { + let path = PathBuf::from(value); + if path.is_absolute() { + path + } else if value.starts_with('.') { + cwd.join(path) + } else { + config_home.join(path) + } +} + +// ── Session ─────────────────────────────────────────────────────────── + +pub struct Session { + pub id: SessionId, + pub created_at: u64, + runtime: Mutex>, + events: broadcast::Sender, +} + +impl Session { + fn new( + id: SessionId, + model: String, + system_prompt: Vec, + permission_mode: PermissionMode, + feature_config: &RuntimeFeatureConfig, + tool_registry: &GlobalToolRegistry, + cwd: &std::path::Path, + ) -> Result> { + Self::with_runtime_session( + id, + model, + system_prompt, + permission_mode, + feature_config, + tool_registry, + cwd, + RuntimeSession::new(), + ) + } + + #[allow(clippy::too_many_arguments)] + fn with_runtime_session( + id: SessionId, + model: String, + system_prompt: Vec, + permission_mode: PermissionMode, + feature_config: &RuntimeFeatureConfig, + tool_registry: &GlobalToolRegistry, + cwd: &std::path::Path, + runtime_session: RuntimeSession, + ) -> Result> { + let (events_tx, _) = broadcast::channel(BROADCAST_CAPACITY); + + let auth = resolve_server_auth_source(cwd)?; + let client = ClawApiClient::from_auth(auth).with_base_url(api::read_base_url()); + + let api_client = ServerApiClient { + client, + model, + tool_registry: tool_registry.clone(), + allowed_tools: None, + enable_tools: true, + broadcaster: events_tx.clone(), + session_id: id.clone(), + }; + + let tool_executor = ServerToolExecutor { + tool_registry: tool_registry.clone(), + allowed_tools: None, + broadcaster: events_tx.clone(), + session_id: id.clone(), + }; + + let policy = permission_policy(permission_mode, tool_registry); + let runtime = ConversationRuntime::new_with_features( + runtime_session, + api_client, + tool_executor, + policy, + system_prompt, + feature_config.clone(), + ); + + Ok(Self { + id, + created_at: unix_timestamp_millis(), + runtime: Mutex::new(runtime), + events: events_tx, + }) + } + + fn persist(&self, sessions_dir: &std::path::Path) { + let path = session_file_path(sessions_dir, &self.id); + if let Ok(rt) = self.runtime.lock() { + let _ = rt.session().save_to_path(path); + } + } + + fn subscribe(&self) -> broadcast::Receiver { + self.events.subscribe() + } +} + +fn resolve_server_auth_source(cwd: &std::path::Path) -> Result> { + let cwd_clone = cwd.to_path_buf(); + Ok(resolve_startup_auth_source(move || { + let config = ConfigLoader::default_for(&cwd_clone).load().map_err(|error| { + api::ApiError::Auth(format!("failed to load runtime OAuth config: {error}")) + })?; + Ok(config.oauth().cloned()) + })?) +} + +// ── 请求/响应类型 ───────────────────────────────────────────────────── + #[derive(Debug, Serialize)] struct ErrorResponse { error: String, @@ -128,7 +722,7 @@ pub struct ListSessionsResponse { pub struct SessionDetailsResponse { pub id: SessionId, pub created_at: u64, - pub session: RuntimeSession, + pub messages: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -136,21 +730,66 @@ pub struct SendMessageRequest { pub message: String, } -#[must_use] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct UsageResponse { + pub session_id: SessionId, + pub usage: TokenUsage, + pub turns: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct CompactResponse { + pub session_id: SessionId, + pub summary: String, + pub removed_message_count: usize, +} + +// ── 路由 ────────────────────────────────────────────────────────────── + +#[must_use = "the Router must be served to handle requests"] pub fn app(state: AppState) -> Router { Router::new() .route("/sessions", post(create_session).get(list_sessions)) - .route("/sessions/{id}", get(get_session)) + .route("/sessions/{id}", get(get_session).delete(delete_session)) .route("/sessions/{id}/events", get(stream_session_events)) .route("/sessions/{id}/message", post(send_message)) + .route("/sessions/{id}/compact", post(compact_session_handler)) + .route("/sessions/{id}/usage", get(get_usage)) + .layer( + CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any), + ) .with_state(state) } +// ── 处理器 ──────────────────────────────────────────────────────────── + async fn create_session( State(state): State, -) -> (StatusCode, Json) { +) -> ApiResult<(StatusCode, Json)> { let session_id = state.allocate_session_id(); - let session = Session::new(session_id.clone()); + let session = Session::new( + session_id.clone(), + state.model.clone(), + state.system_prompt.clone(), + state.permission_mode, + &state.feature_config, + &state.tool_registry, + &state.cwd, + ) + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse { + error: format!("failed to create session: {e}"), + }), + ) + })?; + + // 创建后立即持久化 + session.persist(&state.sessions_dir); state .sessions @@ -158,20 +797,26 @@ async fn create_session( .await .insert(session_id.clone(), session); - ( + Ok(( StatusCode::CREATED, Json(CreateSessionResponse { session_id }), - ) + )) } async fn list_sessions(State(state): State) -> Json { let sessions = state.sessions.read().await; let mut summaries = sessions .values() - .map(|session| SessionSummary { - id: session.id.clone(), - created_at: session.created_at, - message_count: session.conversation.messages.len(), + .map(|session| { + let msg_count = session + .runtime + .lock() + .map_or(0, |rt| rt.session().messages.len()); + SessionSummary { + id: session.id.clone(), + created_at: session.created_at, + message_count: msg_count, + } }) .collect::>(); summaries.sort_by(|left, right| left.id.cmp(&right.id)); @@ -189,35 +834,142 @@ async fn get_session( let session = sessions .get(&id) .ok_or_else(|| not_found(format!("session `{id}` not found")))?; + let messages = session + .runtime + .lock() + .map(|rt| rt.session().messages.clone()) + .unwrap_or_default(); Ok(Json(SessionDetailsResponse { id: session.id.clone(), created_at: session.created_at, - session: session.conversation.clone(), + messages, })) } +async fn delete_session( + State(state): State, + Path(id): Path, +) -> ApiResult { + let removed = state.sessions.write().await.remove(&id).is_some(); + if removed { + // 同时删除磁盘文件 + let path = session_file_path(&state.sessions_dir, &id); + let _ = std::fs::remove_file(path); + Ok(StatusCode::NO_CONTENT) + } else { + Err(not_found(format!("session `{id}` not found"))) + } +} + async fn send_message( State(state): State, Path(id): Path, Json(payload): Json, ) -> ApiResult { - let message = ConversationMessage::user_text(payload.message); + // 先获取 broadcaster 引用,然后释放 sessions 读锁 let broadcaster = { - let mut sessions = state.sessions.write().await; + let sessions = state.sessions.read().await; let session = sessions - .get_mut(&id) + .get(&id) .ok_or_else(|| not_found(format!("session `{id}` not found")))?; - session.conversation.messages.push(message.clone()); session.events.clone() }; - let _ = broadcaster.send(SessionEvent::Message { - session_id: id, - message, + // spawn_blocking 执行同步的 run_turn + let sessions = state.sessions.clone(); + let id_clone = id.clone(); + let input = payload.message; + let sessions_dir = state.sessions_dir.clone(); + + tokio::task::spawn_blocking(move || { + let sessions_guard = sessions.blocking_read(); + let Some(session) = sessions_guard.get(&id_clone) else { + return; + }; + let mut rt = match session.runtime.lock() { + Ok(guard) => guard, + Err(e) => { + eprintln!("runtime lock poisoned: {e}"); + return; + } + }; + + let result = rt.run_turn(input, None::<&mut dyn runtime::PermissionPrompter>); + + match result { + Ok(summary) => { + // turn 成功后持久化 + drop(rt); + session.persist(&sessions_dir); + let _ = broadcaster.send(SessionEvent::TurnComplete { + session_id: id_clone, + usage: summary.usage, + iterations: summary.iterations, + }); + } + Err(error) => { + let _ = broadcaster.send(SessionEvent::MessageDelta { + session_id: id_clone, + text: format!("[runtime error: {error}]"), + }); + } + } }); - Ok(StatusCode::NO_CONTENT) + Ok(StatusCode::ACCEPTED) +} + +async fn compact_session_handler( + State(state): State, + Path(id): Path, +) -> ApiResult> { + let sessions = state.sessions.read().await; + let session = sessions + .get(&id) + .ok_or_else(|| not_found(format!("session `{id}` not found")))?; + + let result = match session.runtime.lock() { + Ok(rt) => { + let result = rt.compact(CompactionConfig::default()); + drop(rt); + // compact 成功后持久化 + session.persist(&state.sessions_dir); + result + } + Err(_) => runtime::compact_session(&RuntimeSession::new(), CompactionConfig::default()), + }; + + Ok(Json(CompactResponse { + session_id: id, + summary: result.summary, + removed_message_count: result.removed_message_count, + })) +} + +async fn get_usage( + State(state): State, + Path(id): Path, +) -> ApiResult> { + let sessions = state.sessions.read().await; + let session = sessions + .get(&id) + .ok_or_else(|| not_found(format!("session `{id}` not found")))?; + + let (usage, turns) = session + .runtime + .lock() + .map(|rt| { + let u = rt.usage(); + (u.cumulative_usage(), u.turns()) + }) + .unwrap_or((TokenUsage::default(), 0)); + + Ok(Json(UsageResponse { + session_id: id, + usage, + turns, + })) } async fn stream_session_events( @@ -229,10 +981,15 @@ async fn stream_session_events( let session = sessions .get(&id) .ok_or_else(|| not_found(format!("session `{id}` not found")))?; + let messages = session + .runtime + .lock() + .map(|rt| rt.session().messages.clone()) + .unwrap_or_default(); ( SessionEvent::Snapshot { session_id: session.id.clone(), - session: session.conversation.clone(), + messages, }, session.subscribe(), ) @@ -250,7 +1007,7 @@ async fn stream_session_events( yield Ok::(sse_event); } } - Err(broadcast::error::RecvError::Lagged(_)) => continue, + Err(broadcast::error::RecvError::Lagged(_)) => {} Err(broadcast::error::RecvError::Closed) => break, } } @@ -259,11 +1016,91 @@ async fn stream_session_events( Ok(Sse::new(stream).keep_alive(KeepAlive::new().interval(Duration::from_secs(15)))) } +// ── 工具函数 ────────────────────────────────────────────────────────── + +fn ensure_sessions_dir(cwd: &std::path::Path) -> Result { + let path = cwd.join(".claw").join("sessions"); + std::fs::create_dir_all(&path)?; + Ok(path) +} + +fn session_file_path(sessions_dir: &std::path::Path, id: &str) -> PathBuf { + sessions_dir.join(format!("{id}.json")) +} + +/// 从磁盘恢复已有会话,返回 (sessions `HashMap`, 最大 session ID) +fn restore_sessions( + sessions_dir: &std::path::Path, + model: &str, + system_prompt: &[String], + permission_mode: PermissionMode, + feature_config: &RuntimeFeatureConfig, + tool_registry: &GlobalToolRegistry, + cwd: &std::path::Path, +) -> (HashMap, u64) { + let Ok(entries) = std::fs::read_dir(sessions_dir) else { + return (HashMap::new(), 0); + }; + + let mut sessions = HashMap::new(); + let mut max_id: u64 = 0; + + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().is_none_or(|ext| ext != "json") { + continue; + } + + // 从文件名提取 session ID: "session-1.json" -> "session-1" + let file_stem = path.file_stem().unwrap_or_default().to_string_lossy(); + let id = file_stem.to_string(); + + // 从 ID 解析数字部分以更新 max_id + if let Some(num_str) = id.strip_prefix("session-") { + if let Ok(num) = num_str.parse::() { + max_id = max_id.max(num); + } + } + + // 加载 runtime session,跳过损坏文件 + let runtime_session = match RuntimeSession::load_from_path(&path) { + Ok(s) => s, + Err(e) => { + eprintln!("跳过损坏的会话文件 {}: {e}", path.display()); + continue; + } + }; + + // 构建完整的 server Session + match Session::with_runtime_session( + id.clone(), + model.to_string(), + system_prompt.to_vec(), + permission_mode, + feature_config, + tool_registry, + cwd, + runtime_session, + ) { + Ok(session) => { + sessions.insert(id, session); + } + Err(e) => { + eprintln!("跳过无法重建的会话文件 {}: {e}", path.display()); + } + } + } + + (sessions, max_id) +} + fn unix_timestamp_millis() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system time should be after epoch") - .as_millis() as u64 + .as_millis() + .try_into() + .expect("timestamp should fit in u64") } fn not_found(message: String) -> ApiError { @@ -273,6 +1110,8 @@ fn not_found(message: String) -> ApiError { ) } +// ── 测试 ────────────────────────────────────────────────────────────── + #[cfg(test)] mod tests { use super::{ @@ -298,8 +1137,10 @@ mod tests { let address = listener .local_addr() .expect("listener should report local address"); + + let state = AppState::new_test(); let handle = tokio::spawn(async move { - axum::serve(listener, app(AppState::default())) + axum::serve(listener, app(state)) .await .expect("server should run"); }); @@ -354,10 +1195,8 @@ mod tests { let server = TestServer::spawn().await; let client = Client::new(); - // given let created = create_session(&client, &server).await; - // when let sessions = client .get(server.url("/sessions")) .send() @@ -379,21 +1218,19 @@ mod tests { .await .expect("details response should parse"); - // then assert_eq!(created.session_id, "session-1"); assert_eq!(sessions.sessions.len(), 1); assert_eq!(sessions.sessions[0].id, created.session_id); assert_eq!(sessions.sessions[0].message_count, 0); assert_eq!(details.id, "session-1"); - assert!(details.session.messages.is_empty()); + assert!(details.messages.is_empty()); } #[tokio::test] - async fn streams_message_events_and_persists_message_flow() { + async fn streams_snapshot_on_connect() { let server = TestServer::spawn().await; let client = Client::new(); - // given let created = create_session(&client, &server).await; let mut response = client .get(server.url(&format!("/sessions/{}/events", created.session_id))) @@ -405,38 +1242,30 @@ mod tests { let mut buffer = String::new(); let snapshot_frame = next_sse_frame(&mut response, &mut buffer).await; - // when - let send_status = client - .post(server.url(&format!("/sessions/{}/message", created.session_id))) - .json(&super::SendMessageRequest { - message: "hello from test".to_string(), - }) + assert!(snapshot_frame.contains("event: snapshot")); + assert!(snapshot_frame.contains("\"session_id\":\"session-1\"")); + } + + #[tokio::test] + async fn deletes_session() { + let server = TestServer::spawn().await; + let client = Client::new(); + + let created = create_session(&client, &server).await; + let status = client + .delete(server.url(&format!("/sessions/{}", created.session_id))) .send() .await - .expect("message request should succeed") + .expect("delete request should succeed") .status(); - let message_frame = next_sse_frame(&mut response, &mut buffer).await; - let details = client + assert_eq!(status, reqwest::StatusCode::NO_CONTENT); + + let status = client .get(server.url(&format!("/sessions/{}", created.session_id))) .send() .await - .expect("details request should succeed") - .error_for_status() - .expect("details request should return success") - .json::() - .await - .expect("details response should parse"); - - // then - assert_eq!(send_status, reqwest::StatusCode::NO_CONTENT); - assert!(snapshot_frame.contains("event: snapshot")); - assert!(snapshot_frame.contains("\"session_id\":\"session-1\"")); - assert!(message_frame.contains("event: message")); - assert!(message_frame.contains("hello from test")); - assert_eq!(details.session.messages.len(), 1); - assert_eq!( - details.session.messages[0], - runtime::ConversationMessage::user_text("hello from test") - ); + .expect("get request should succeed") + .status(); + assert_eq!(status, reqwest::StatusCode::NOT_FOUND); } } diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs new file mode 100644 index 0000000..6d089fc --- /dev/null +++ b/crates/server/src/main.rs @@ -0,0 +1,74 @@ +use std::env; +use std::net::SocketAddr; +use server::{app, AppState}; +use tokio::net::TcpListener; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // 尝试加载 .env + let _ = dotenvy::dotenv(); + + let host = env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); + let port = env::var("SERVER_PORT") + .unwrap_or_else(|_| "3000".to_string()) + .parse::()?; + let addr = format!("{host}:{port}").parse::()?; + + // 解析模型(支持别名,如 "opus" -> "claude-opus-4-6") + let raw_model = env::var("CLAW_MODEL").unwrap_or_else(|_| "claude-opus-4-6".to_string()); + let model = api::resolve_model_alias(&raw_model).to_string(); + + // 动态日期 + let today = chrono::Local::now().format("%Y-%m-%d").to_string(); + + // 构建系统提示词(从项目目录加载或使用默认值) + let cwd = env::current_dir()?; + let system_prompt = + runtime::load_system_prompt(&cwd, &today, env::consts::OS, "unknown") + .unwrap_or_else(|_| { + vec![ + "You are a helpful AI assistant running inside the Claw web interface." + .to_string(), + ] + }); + + // 解析权限模式(从环境变量或配置,默认 WorkspaceWrite) + let permission_mode = match env::var("CLAW_PERMISSION_MODE") + .unwrap_or_default() + .as_str() + { + "danger" | "DangerFullAccess" => runtime::PermissionMode::DangerFullAccess, + "readonly" | "ReadOnly" => runtime::PermissionMode::ReadOnly, + _ => { + // 尝试从 .claw.json 配置读取 + let loader = runtime::ConfigLoader::default_for(&cwd); + loader + .load() + .ok() + .and_then(|config| config.permission_mode()) + .map(|resolved| match resolved { + runtime::ResolvedPermissionMode::ReadOnly => runtime::PermissionMode::ReadOnly, + runtime::ResolvedPermissionMode::WorkspaceWrite => runtime::PermissionMode::WorkspaceWrite, + runtime::ResolvedPermissionMode::DangerFullAccess => runtime::PermissionMode::DangerFullAccess, + }) + .unwrap_or(runtime::PermissionMode::WorkspaceWrite) + } + }; + + // 初始化应用状态 + let state = AppState::new(model.clone(), system_prompt, permission_mode, cwd)?; + + // 构建路由 + let router = app(state); + + println!("Claw Server started"); + println!(" address: http://{addr}"); + println!(" pid: {}", std::process::id()); + println!(" model: {model}"); + println!(" tip: curl -X POST http://{addr}/sessions"); + + let listener = TcpListener::bind(addr).await?; + axum::serve(listener, router).await?; + + Ok(()) +} diff --git a/crates/tools/README.md b/crates/tools/README.md new file mode 100644 index 0000000..043be36 --- /dev/null +++ b/crates/tools/README.md @@ -0,0 +1,51 @@ +# 工具规范模块 (tools) + +本模块定义了 AI 代理可以使用的所有内置工具的规范 (Schema)、权限要求以及分发逻辑。 + +## 概览 + +`tools` 模块充当了 AI 认知能力与物理操作之间的桥梁,其主要职责包括: +- **工具定义**:使用 JSON Schema 定义每个工具的输入参数结构,以便 AI 正确调用。 +- **权限映射**:为每个工具分配安全等级(如只读、工作区写入、完全访问)。 +- **工具注册表 (GlobalToolRegistry)**:统一管理内置工具和由插件提供的动态工具。 +- **分发执行**:将 AI 生成的 JSON 调用分发到 `runtime` 模块中的具体实现。 + +## 关键特性 + +- **内置工具集 (MVP Tools)**: + - **系统交互**:`bash`, `PowerShell`, `REPL`。 + - **文件操作**:`read_file`, `write_file`, `edit_file`。 + - **搜索与发现**:`glob_search`, `grep_search`, `ToolSearch`。 + - **网络与辅助**:`WebSearch`, `WebFetch`, `Sleep`。 + - **高级调度**:`Agent`(启动子代理), `Skill`(加载专用技能), `TodoWrite`(任务管理)。 +- **名称归一化**:支持工具别名(例如将 `grep` 映射为 `grep_search`),提高 AI 调用的稳健性。 +- **插件集成**:允许 `plugins` 模块注册自定义工具,并确保它们与内置工具不发生命名冲突。 + +## 实现逻辑 + +### 核心结构 + +- **`ToolSpec`**: 核心配置结构,存储工具的元数据(名称、描述、Schema、权限)。 +- **`GlobalToolRegistry`**: 负责维护工具列表,并提供 `definitions` 方法生成供 LLM 使用的工具 API 声明。 +- **`execute_tool`**: 顶级分发函数,负责将反序列化后的输入传递给底层的执行函数。 + +### 工作流程 + +1. 系统初始化时,根据用户配置和加载的插件,构建 `GlobalToolRegistry`。 +2. 将工具定义转换为 AI 模型可理解的格式(由 `api` 模块处理)。 +3. 当接收到 AI 的工具调用请求时,`runtime::ConversationRuntime` 调用 `ToolExecutor`。 +4. `ToolExecutor` 委托给本模块的 `execute_tool` 函数。 +5. 本模块验证输入格式,并调用 `runtime` 提供的底层文件或进程操作 API。 + +## 使用示例 (工具定义) + +```rust +use tools::{ToolSpec, mvp_tool_specs}; +use serde_json::json; + +// 获取所有 MVP 工具的规范 +let specs = mvp_tool_specs(); +for spec in specs { + println!("工具: {}, 权限级别: {:?}", spec.name, spec.required_permission); +} +``` diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs index 4b42572..5d6c37c 100644 --- a/crates/tools/src/lib.rs +++ b/crates/tools/src/lib.rs @@ -1628,7 +1628,7 @@ fn build_agent_runtime( .clone() .unwrap_or_else(|| DEFAULT_AGENT_MODEL.to_string()); let allowed_tools = job.allowed_tools.clone(); - let api_client = ProviderRuntimeClient::new(model, allowed_tools.clone())?; + let api_client = ProviderRuntimeClient::new(&model, allowed_tools.clone())?; let tool_executor = SubagentToolExecutor::new(allowed_tools); Ok(ConversationRuntime::new( Session::new(), @@ -1805,8 +1805,8 @@ struct ProviderRuntimeClient { } impl ProviderRuntimeClient { - fn new(model: String, allowed_tools: BTreeSet) -> Result { - let model = resolve_model_alias(&model).to_string(); + fn new(model: &str, allowed_tools: BTreeSet) -> Result { + let model = resolve_model_alias(model).clone(); let client = ProviderClient::from_model(&model).map_err(|error| error.to_string())?; Ok(Self { runtime: tokio::runtime::Runtime::new().map_err(|error| error.to_string())?, @@ -1818,6 +1818,7 @@ impl ProviderRuntimeClient { } impl ApiClient for ProviderRuntimeClient { + #[allow(clippy::too_many_lines)] fn stream(&mut self, request: ApiRequest) -> Result, RuntimeError> { let tools = tool_specs_for_allowed_tools(Some(&self.allowed_tools)) .into_iter() @@ -1991,6 +1992,13 @@ fn convert_messages(messages: &[ConversationMessage]) -> Vec { }], is_error: *is_error, }, + ContentBlock::Thinking { thinking, signature } => InputContentBlock::Thinking { + thinking: thinking.clone(), + signature: signature.clone(), + }, + ContentBlock::RedactedThinking { data } => InputContentBlock::RedactedThinking { + data: serde_json::from_str(&data.render()).unwrap_or(serde_json::Value::Null), + }, }) .collect::>(); (!content.is_empty()).then(|| InputMessage { @@ -2061,7 +2069,10 @@ fn final_assistant_text(summary: &runtime::TurnSummary) -> String { .iter() .filter_map(|block| match block { ContentBlock::Text { text } => Some(text.as_str()), - _ => None, + ContentBlock::Thinking { .. } + | ContentBlock::RedactedThinking { .. } + | ContentBlock::ToolUse { .. } + | ContentBlock::ToolResult { .. } => None, }) .collect::>() .join("") diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..804bd4a --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +*.local diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..6018cdf --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,50 @@ +# Claw Code Frontend + +Claw Code 的 Web 前端界面,使用 [Ant Design X](https://x.ant.design/) 构建。 + +## 技术栈 + +- React 19 + TypeScript +- Ant Design X 2.5(Bubble / Sender / Conversations / Think / ThoughtChain / XMarkdown) +- Vite 6 + +## 开发 + +```bash +npm install +npm run dev +``` + +前端通过 Vite 代理连接后端,默认代理到 `http://localhost:3000`。 + +先启动后端: + +```bash +# 项目根目录 +cargo run --bin claw-server +``` + +## 构建 + +```bash +npm run build +``` + +产出目录:`dist/`。 + +## 项目结构 + +``` +src/ + main.tsx # 入口 + App.tsx # XProvider 主题 + 布局 + 状态管理 + api.ts # REST API 客户端 + types.ts # 类型定义 + hooks/ + useSSE.ts # SSE 事件流 + components/ + ChatView.tsx # 聊天区(Bubble.List + Sender + XMarkdown) + SessionSidebar.tsx # 会话侧边栏(Conversations) + ToolChain.tsx # 工具调用链(ThoughtChain) + WelcomeScreen.tsx # 欢迎页 +``` diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..e611262 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + Claw Code + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..72f7925 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4910 @@ +{ + "name": "claw-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "claw-frontend", + "version": "0.1.0", + "dependencies": { + "@ant-design/icons": "^6.0.0", + "@ant-design/x": "^2.5.0", + "@ant-design/x-markdown": "^2.5.0", + "@ant-design/x-sdk": "^2.5.0", + "@antv/infographic": "^0.2.16", + "antd": "^6.1.1", + "marked-emoji": "^2.0.3", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^4.4.1", + "typescript": "~5.8.3", + "vite": "^6.3.2" + } + }, + "node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", + "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz", + "integrity": "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.1.2", + "@babel/runtime": "^7.23.2", + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.1.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-6.1.1.tgz", + "integrity": "sha512-AMT4N2y++TZETNHiM77fs4a0uPVCJGuL5MTonk13Pvv7UN7sID1cNEZOc1qNqx6zLKAOilTEFAdAoAFKa0U//Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-2.0.0.tgz", + "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "clsx": "^2.1.1", + "json2mq": "^0.2.0", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@ant-design/x": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@ant-design/x/-/x-2.5.0.tgz", + "integrity": "sha512-B4FGlYz++MHelu5+PHbdKCXASAz7n+W8bzpIDzFbK45Dx9mL6uR3jOpCN2UcuWE0w2hQ8wtuKbRb/AfGS+KNeA==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.0", + "@ant-design/cssinjs": "^2.0.1", + "@ant-design/cssinjs-utils": "^2.0.2", + "@ant-design/fast-color": "^3.0.0", + "@ant-design/icons": "^6.0.0", + "@babel/runtime": "^7.25.6", + "@rc-component/motion": "^1.1.6", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "lodash.throttle": "^4.1.1", + "mermaid": "^11.12.1", + "react-syntax-highlighter": "^16.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "antd": "^6.1.1", + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@ant-design/x-markdown": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@ant-design/x-markdown/-/x-markdown-2.5.0.tgz", + "integrity": "sha512-Xuhhus8yB0eza9Cs5/G51gG+wUJAgNzyjwGx4G4ivsKTcEzcLJWomE6zpzmlmd4t3lAWsDzcyMDYB6wKjDMbyw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "dompurify": "^3.2.6", + "html-react-parser": "^5.2.13", + "katex": "^0.16.22", + "marked": "^15.0.12" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@ant-design/x-sdk": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@ant-design/x-sdk/-/x-sdk-2.5.0.tgz", + "integrity": "sha512-exUKKoJCYK2y81FH5ofZtw8ItrSERTnwkMYz+vY5S8Oie2MN4j5RYf9HmbktBc3Lct9blbZVXaotHcXqKuJvGg==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antv/event-emitter": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/@antv/event-emitter/-/event-emitter-0.1.3.tgz", + "integrity": "sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg==", + "license": "MIT" + }, + "node_modules/@antv/expr": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@antv/expr/-/expr-1.0.2.tgz", + "integrity": "sha512-vrfdmPHkTuiS5voVutKl2l06w1ihBh9A8SFdQPEE+2KMVpkymzGOF1eWpfkbGZ7tiFE15GodVdhhHomD/hdIwg==", + "license": "MIT" + }, + "node_modules/@antv/graphlib": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/@antv/graphlib/-/graphlib-2.0.4.tgz", + "integrity": "sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3" + } + }, + "node_modules/@antv/hierarchy": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/@antv/hierarchy/-/hierarchy-0.7.1.tgz", + "integrity": "sha512-7r22r+HxfcRZp79ZjGmsn97zgC1Iajrv0Mm9DIgx3lPfk+Kme2MG/+EKdZj1iEBsN0rJRzjWVPGL5YrBdVHchw==", + "license": "MIT" + }, + "node_modules/@antv/infographic": { + "version": "0.2.16", + "resolved": "https://registry.npmmirror.com/@antv/infographic/-/infographic-0.2.16.tgz", + "integrity": "sha512-PmrQ4Wb1MOlr4wxYzBfWhtKKWwXMS6r/JNEjoH6ZYzjchZII9IAw3K1w1S/ZfuUsS7X9rFt7xbY8/tzqHtaJ6A==", + "license": "MIT", + "workspaces": [ + "dev", + "site" + ], + "dependencies": { + "@antv/hierarchy": "^0.7.0", + "@antv/layout": "^2.0.0-beta.0", + "culori": "^4.0.2", + "d3": "^7.9.0", + "eventemitter3": "^5.0.1", + "flru": "^1.0.2", + "linkedom": "^0.18.12", + "lodash-es": "^4.17.21", + "measury": "^0.1.5", + "postcss": "^8.5.6", + "roughjs": "^4.6.6", + "round-polygon": "^0.6.7", + "tinycolor2": "^1.6.0" + } + }, + "node_modules/@antv/layout": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@antv/layout/-/layout-2.0.0.tgz", + "integrity": "sha512-aCZ3UdNc40SfT7meFV7QTADY2HCnc0DShVw56CJNTI6oExUIVU736grPuL5Dhb8/JrVaU4Y83QPN/P7KafBzlw==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3", + "@antv/expr": "^1.0.2", + "@antv/graphlib": "^2.0.0", + "@antv/util": "^3.3.2", + "comlink": "^4.4.1", + "d3-force": "^3.0.0", + "d3-force-3d": "^3.0.5", + "d3-octree": "^1.0.2", + "d3-quadtree": "^3.0.1", + "dagre": "^0.8.5", + "ml-matrix": "^6.10.4", + "tslib": "^2.8.1" + } + }, + "node_modules/@antv/util": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/@antv/util/-/util-3.3.11.tgz", + "integrity": "sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "gl-matrix": "^3.3.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-12.0.0.tgz", + "integrity": "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "12.0.0", + "@chevrotain/types": "12.0.0" + } + }, + "node_modules/@chevrotain/gast": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@chevrotain/gast/-/gast-12.0.0.tgz", + "integrity": "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "12.0.0" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@chevrotain/regexp-to-ast/-/regexp-to-ast-12.0.0.tgz", + "integrity": "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@chevrotain/types/-/types-12.0.0.tgz", + "integrity": "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@chevrotain/utils/-/utils-12.0.0.tgz", + "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==", + "license": "Apache-2.0" + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@mermaid-js/parser/-/parser-1.1.0.tgz", + "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==", + "license": "MIT", + "dependencies": { + "langium": "^4.0.0" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.1.0.tgz", + "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/cascader": { + "version": "1.14.0", + "resolved": "https://registry.npmmirror.com/@rc-component/cascader/-/cascader-1.14.0.tgz", + "integrity": "sha512-Ip9356xwZUR2nbW5PRVGif4B/bDve4pLa/N+PGbvBaTnjbvmN4PFMBGQSmlDlzKP1ovxaYMvwF/dI9lXNLT4iQ==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.6.0", + "@rc-component/tree": "~1.2.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/checkbox": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@rc-component/checkbox/-/checkbox-2.0.0.tgz", + "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/collapse": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rc-component/collapse/-/collapse-1.2.0.tgz", + "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-3.1.1.tgz", + "integrity": "sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/context/-/context-2.0.1.tgz", + "integrity": "sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/dialog": { + "version": "1.8.4", + "resolved": "https://registry.npmmirror.com/@rc-component/dialog/-/dialog-1.8.4.tgz", + "integrity": "sha512-Ay6PM7phkTkquplG8fWfUGFZ2GTLx9diTl4f0d8Eqxd7W1u1KjE9AQooFQHOHnhZf0Ya3z51+5EKCWHmt/dNEw==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.3", + "@rc-component/portal": "^2.1.0", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/drawer": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/@rc-component/drawer/-/drawer-1.4.2.tgz", + "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.1.3", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dropdown": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@rc-component/dropdown/-/dropdown-1.0.2.tgz", + "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/@rc-component/form": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@rc-component/form/-/form-1.8.0.tgz", + "integrity": "sha512-eUD5KKYnIZWmJwRA0vnyO/ovYUfHGU1svydY1OrqU5fw8Oz9Tdqvxvrlh0wl6xI/EW69dT7II49xpgOWzK3T5A==", + "license": "MIT", + "dependencies": { + "@rc-component/async-validator": "^5.1.0", + "@rc-component/util": "^1.6.2", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/image": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/@rc-component/image/-/image-1.8.1.tgz", + "integrity": "sha512-JfPCijmMl+EaMvbftsEs/4VHmTyJKsZBh5ujFowSA45i9NTVYS1vuHtgpVV/QrGa27kXwbVOZriffCe/PNKuMw==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/portal": "^2.1.2", + "@rc-component/util": "^1.10.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/input": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/input/-/input-1.1.2.tgz", + "integrity": "sha512-Q61IMR47piUBudgixJ30CciKIy9b1H95qe7GgEKOmSJVJXvFRWJllJfQry9tif+MX2cWFXWJf/RXz4kaCeq/Fg==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/input-number": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/@rc-component/input-number/-/input-number-1.6.2.tgz", + "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==", + "license": "MIT", + "dependencies": { + "@rc-component/mini-decimal": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mentions": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mentions/-/mentions-1.6.0.tgz", + "integrity": "sha512-KIkQNP6habNuTsLhUv0UGEOwG67tlmE7KNIJoQZZNggEZl5lQJTytFDb69sl5CK3TDdISCTjKP3nGEBKgT61CQ==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.1.0", + "@rc-component/menu": "~1.2.0", + "@rc-component/textarea": "~1.1.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/menu": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rc-component/menu/-/menu-1.2.0.tgz", + "integrity": "sha512-VWwDuhvYHSnTGj4n6bV3ISrLACcPAzdPOq3d0BzkeiM5cve8BEYfvkEhNoM0PLzv51jpcejeyrLXeMVIJ+QJlg==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.3.tgz", + "integrity": "sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/motion": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@rc-component/motion/-/motion-1.3.2.tgz", + "integrity": "sha512-itfd+GztzJYAb04Z4RkEub1TbJAfZc2Iuy8p44U44xD1F5+fNYFKI3897ijlbIyfvXkTmMm+KGcjkQQGMHywEQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", + "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/notification": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rc-component/notification/-/notification-1.2.0.tgz", + "integrity": "sha512-OX3J+zVU7rvoJCikjrfW7qOUp7zlDeFBK2eA3SFbGSkDqo63Sl4Ss8A04kFP+fxHSxMDIS9jYVEZtU1FNCFuBA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/overflow": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@rc-component/overflow/-/overflow-1.0.0.tgz", + "integrity": "sha512-GSlBeoE0XTBi5cf3zl8Qh7Uqhn7v8RrlJ8ajeVpEkNe94HWy5l5BQ0Mwn2TVUq9gdgbfEMUmTX7tJFAg7mz0Rw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/pagination": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rc-component/pagination/-/pagination-1.2.0.tgz", + "integrity": "sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/picker": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/@rc-component/picker/-/picker-1.9.1.tgz", + "integrity": "sha512-9FBYYsvH3HMLICaPDA/1Th5FLaDkFa7qAtangIdlhKb3ZALaR745e9PsOhheJb6asS4QXc12ffiAcjdkZ4C5/g==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/trigger": "^3.6.15", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/@rc-component/portal": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@rc-component/portal/-/portal-2.2.0.tgz", + "integrity": "sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/progress": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@rc-component/progress/-/progress-1.0.2.tgz", + "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/rate": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/rate/-/rate-1.0.1.tgz", + "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/resize-observer": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz", + "integrity": "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/segmented": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@rc-component/segmented/-/segmented-1.3.0.tgz", + "integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/select": { + "version": "1.6.15", + "resolved": "https://registry.npmmirror.com/@rc-component/select/-/select-1.6.15.tgz", + "integrity": "sha512-SyVCWnqxCQZZcQvQJ/CxSjx2bGma6ds/HtnpkIfZVnt6RoEgbqUmHgD6vrzNarNXwbLXerwVzWwq8F3d1sst7g==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/slider": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/slider/-/slider-1.0.1.tgz", + "integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/steps": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@rc-component/steps/-/steps-1.2.2.tgz", + "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/switch": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@rc-component/switch/-/switch-1.0.3.tgz", + "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/table": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/@rc-component/table/-/table-1.9.1.tgz", + "integrity": "sha512-FVI5ZS/GdB3BcgexfCYKi3iHhZS3Fr59EtsxORszYGrfpH1eWr33eDNSYkVfLI6tfJ7vftJDd9D5apfFWqkdJg==", + "license": "MIT", + "dependencies": { + "@rc-component/context": "^2.0.1", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.1.0", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tabs": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/@rc-component/tabs/-/tabs-1.7.0.tgz", + "integrity": "sha512-J48cs2iBi7Ho3nptBxxIqizEliUC+ExE23faspUQKGQ550vaBlv3aGF8Epv/UB1vFWeoJDTW/dNzgIU0Qj5i/w==", + "license": "MIT", + "dependencies": { + "@rc-component/dropdown": "~1.0.0", + "@rc-component/menu": "~1.2.0", + "@rc-component/motion": "^1.1.3", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/textarea": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/textarea/-/textarea-1.1.2.tgz", + "integrity": "sha512-9rMUEODWZDMovfScIEHXWlVZuPljZ2pd1LKNjslJVitn4SldEzq5vO1CL3yy3Dnib6zZal2r2DPtjy84VVpF6A==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.1.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tooltip": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/tooltip/-/tooltip-1.4.0.tgz", + "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.7.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@rc-component/tour/-/tour-2.3.0.tgz", + "integrity": "sha512-K04K9r32kUC+auBSQfr+Fss4SpSIS9JGe56oq/ALAX0p+i2ylYOI1MgR83yBY7v96eO6ZFXcM/igCQmubps0Ow==", + "license": "MIT", + "dependencies": { + "@rc-component/portal": "^2.2.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.7.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tree": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/@rc-component/tree/-/tree-1.2.4.tgz", + "integrity": "sha512-5Gli43+m4R7NhpYYz3Z61I6LOw9yI6CNChxgVtvrO6xB1qML7iE6QMLVMB3+FTjo2yF6uFdAHtqWPECz/zbX5w==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/util": "^1.8.1", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/tree-select": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@rc-component/tree-select/-/tree-select-1.8.0.tgz", + "integrity": "sha512-iYsPq3nuLYvGqdvFAW+l+I9ASRIOVbMXyA8FGZg2lGym/GwkaWeJGzI4eJ7c9IOEhRj0oyfIN4S92Fl3J05mjQ==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.6.0", + "@rc-component/tree": "~1.2.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/trigger": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-3.9.0.tgz", + "integrity": "sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.2.0", + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/upload": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/upload/-/upload-1.1.0.tgz", + "integrity": "sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.10.1", + "resolved": "https://registry.npmmirror.com/@rc-component/util/-/util-1.10.1.tgz", + "integrity": "sha512-q++9S6rUa5Idb/xIBNz6jtvumw5+O5YV5V0g4iK9mn9jWs4oGJheE3ZN1kAnE723AXyaD8v95yeOASmdk8Jnng==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@rc-component/virtual-list/-/virtual-list-1.0.2.tgz", + "integrity": "sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmmirror.com/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmmirror.com/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/antd": { + "version": "6.3.5", + "resolved": "https://registry.npmmirror.com/antd/-/antd-6.3.5.tgz", + "integrity": "sha512-8BPz9lpZWQm42PTx7yL4KxWAotVuqINiKcoYRcLtdd5BFmAcAZicVyFTnBJyRDlzGZFZeRW3foGu6jXYFnej6Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/cssinjs": "^2.1.2", + "@ant-design/cssinjs-utils": "^2.1.2", + "@ant-design/fast-color": "^3.0.1", + "@ant-design/icons": "^6.1.1", + "@ant-design/react-slick": "~2.0.0", + "@babel/runtime": "^7.28.4", + "@rc-component/cascader": "~1.14.0", + "@rc-component/checkbox": "~2.0.0", + "@rc-component/collapse": "~1.2.0", + "@rc-component/color-picker": "~3.1.1", + "@rc-component/dialog": "~1.8.4", + "@rc-component/drawer": "~1.4.2", + "@rc-component/dropdown": "~1.0.2", + "@rc-component/form": "~1.8.0", + "@rc-component/image": "~1.8.0", + "@rc-component/input": "~1.1.2", + "@rc-component/input-number": "~1.6.2", + "@rc-component/mentions": "~1.6.0", + "@rc-component/menu": "~1.2.0", + "@rc-component/motion": "^1.3.2", + "@rc-component/mutate-observer": "^2.0.1", + "@rc-component/notification": "~1.2.0", + "@rc-component/pagination": "~1.2.0", + "@rc-component/picker": "~1.9.1", + "@rc-component/progress": "~1.0.2", + "@rc-component/qrcode": "~1.1.1", + "@rc-component/rate": "~1.0.1", + "@rc-component/resize-observer": "^1.1.2", + "@rc-component/segmented": "~1.3.0", + "@rc-component/select": "~1.6.15", + "@rc-component/slider": "~1.0.1", + "@rc-component/steps": "~1.2.2", + "@rc-component/switch": "~1.0.3", + "@rc-component/table": "~1.9.1", + "@rc-component/tabs": "~1.7.0", + "@rc-component/textarea": "~1.1.2", + "@rc-component/tooltip": "~1.4.0", + "@rc-component/tour": "~2.3.0", + "@rc-component/tree": "~1.2.4", + "@rc-component/tree-select": "~1.8.0", + "@rc-component/trigger": "^3.9.0", + "@rc-component/upload": "~1.1.0", + "@rc-component/util": "^1.10.0", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.16", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", + "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001787", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", + "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chevrotain": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/chevrotain/-/chevrotain-12.0.0.tgz", + "integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "12.0.0", + "@chevrotain/gast": "12.0.0", + "@chevrotain/regexp-to-ast": "12.0.0", + "@chevrotain/types": "12.0.0", + "@chevrotain/utils": "12.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/chevrotain-allstar/-/chevrotain-allstar-0.4.1.tgz", + "integrity": "sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^12.0.0" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmmirror.com/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/culori": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/culori/-/culori-4.0.2.tgz", + "integrity": "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/cytoscape": { + "version": "3.33.2", + "resolved": "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.33.2.tgz", + "integrity": "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmmirror.com/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmmirror.com/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmmirror.com/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.334", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz", + "integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flru": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/flru/-/flru-1.0.2.tgz", + "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmmirror.com/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, + "node_modules/html-dom-parser": { + "version": "5.1.8", + "resolved": "https://registry.npmmirror.com/html-dom-parser/-/html-dom-parser-5.1.8.tgz", + "integrity": "sha512-MCIUng//mF2qTtGHXJWr6OLfHWmg3Pm8ezpfiltF83tizPWY17JxT4dRLE8lykJ5bChJELoY3onQKPbufJHxYA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/remarkablemark" + } + ], + "license": "MIT", + "dependencies": { + "domhandler": "5.0.3", + "htmlparser2": "10.1.0" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-react-parser": { + "version": "5.2.17", + "resolved": "https://registry.npmmirror.com/html-react-parser/-/html-react-parser-5.2.17.tgz", + "integrity": "sha512-m+K/7Moq1jodAB4VL0RXSOmtwLUYoAsikZhwd+hGQe5Vtw2dbWfpFd60poxojMU0Tsh9w59mN1QLEcoHz0Dx9w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/remarkablemark" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/html-react-parser" + } + ], + "license": "MIT", + "dependencies": { + "domhandler": "5.0.3", + "html-dom-parser": "5.1.8", + "react-property": "2.0.2", + "style-to-js": "1.1.21" + }, + "peerDependencies": { + "@types/react": "0.14 || 15 || 16 || 17 || 18 || 19", + "react": "0.14 || 15 || 16 || 17 || 18 || 19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-any-array": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-any-array/-/is-any-array-2.0.1.tgz", + "integrity": "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==", + "license": "MIT" + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/katex": { + "version": "0.16.45", + "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.45.tgz", + "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/langium": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/langium/-/langium-4.2.2.tgz", + "integrity": "sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==", + "license": "MIT", + "dependencies": { + "@chevrotain/regexp-to-ast": "~12.0.0", + "chevrotain": "~12.0.0", + "chevrotain-allstar": "~0.4.1", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.1.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/linkedom": { + "version": "0.18.12", + "resolved": "https://registry.npmmirror.com/linkedom/-/linkedom-0.18.12.tgz", + "integrity": "sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==", + "license": "ISC", + "dependencies": { + "css-select": "^5.1.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^10.0.0", + "uhyphen": "^0.2.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": ">= 2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmmirror.com/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmmirror.com/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-emoji": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/marked-emoji/-/marked-emoji-2.0.3.tgz", + "integrity": "sha512-fChW/AfUqCHgoEC1nFDgiw3OR/qsi71/QXH/HTo05yd6B5+T+VHh1SqCpn/HpeGLDxkA+MK4+hr4eULB2/A8Jw==", + "license": "MIT", + "peerDependencies": { + "marked": ">=4 <19" + } + }, + "node_modules/measury": { + "version": "0.1.5", + "resolved": "https://registry.npmmirror.com/measury/-/measury-0.1.5.tgz", + "integrity": "sha512-YS9nhEbFECQzM9V7kyYylASFwpMyajeRvVnpmfRnhZZwbl9AfpBW+C/o7YwmIQLjK8XFzPHt0OGR7GwqoDuWjA==", + "license": "MIT" + }, + "node_modules/mermaid": { + "version": "11.14.0", + "resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-11.14.0.tgz", + "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.1.0", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "lodash-es": "^4.17.23", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/ml-array-max": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/ml-array-max/-/ml-array-max-1.2.4.tgz", + "integrity": "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0" + } + }, + "node_modules/ml-array-min": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/ml-array-min/-/ml-array-min-1.2.3.tgz", + "integrity": "sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0" + } + }, + "node_modules/ml-array-rescale": { + "version": "1.3.7", + "resolved": "https://registry.npmmirror.com/ml-array-rescale/-/ml-array-rescale-1.3.7.tgz", + "integrity": "sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0", + "ml-array-max": "^1.2.4", + "ml-array-min": "^1.2.3" + } + }, + "node_modules/ml-matrix": { + "version": "6.12.1", + "resolved": "https://registry.npmmirror.com/ml-matrix/-/ml-matrix-6.12.1.tgz", + "integrity": "sha512-TJ+8eOFdp+INvzR4zAuwBQJznDUfktMtOB6g/hUcGh3rcyjxbz4Te57Pgri8Q9bhSQ7Zys4IYOGhFdnlgeB6Lw==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.1", + "ml-array-rescale": "^1.3.7" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmmirror.com/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.5" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-property": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/react-property/-/react-property-2.0.2.tgz", + "integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-syntax-highlighter": { + "version": "16.1.1", + "resolved": "https://registry.npmmirror.com/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz", + "integrity": "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.30.0", + "refractor": "^5.0.0" + }, + "engines": { + "node": ">= 16.20.2" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/refractor": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/refractor/-/refractor-5.0.0.tgz", + "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^9.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmmirror.com/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/round-polygon": { + "version": "0.6.7", + "resolved": "https://registry.npmmirror.com/round-polygon/-/round-polygon-0.6.7.tgz", + "integrity": "sha512-h5rNwOEP6+EHeRuZAGI2/HsNdl6UCKIFvQXzQfv7nTICZ4/sIURe51vOmQJgk/I6KW5LjQ87HJFoASKjaIEySQ==", + "license": "MIT" + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "license": "ISC" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..ca3a3a2 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,29 @@ +{ + "name": "claw-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons": "^6.0.0", + "@ant-design/x": "^2.5.0", + "@ant-design/x-markdown": "^2.5.0", + "@ant-design/x-sdk": "^2.5.0", + "@antv/infographic": "^0.2.16", + "antd": "^6.1.1", + "marked-emoji": "^2.0.3", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^4.4.1", + "typescript": "~5.8.3", + "vite": "^6.3.2" + } +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..0baff63 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ +🐾 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..f1ff9f0 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,343 @@ +import React, { useState, useCallback, useRef } from 'react'; +import { XProvider } from '@ant-design/x'; +import zhCN_X from '@ant-design/x/locale/zh_CN'; +import { theme } from 'antd'; +import zhCN from 'antd/locale/zh_CN'; +import SessionSidebar from './components/SessionSidebar'; +import ChatView from './components/ChatView'; +import type { ChatDisplayMessage } from './components/ChatView'; +import type { ContentBlock, ConversationMessage, SessionEvent, TokenUsage } from './types'; +import { useSSE } from './hooks/useSSE'; +import * as api from './api'; + +// 将服务端消息格式(tool 消息独立)合并为前端展示格式 +// 服务端: user → assistant(text+tool_use) → tool(tool_result) → tool(tool_result) → assistant(text+tool_use) → ... +// 前端: user → assistant(text+tool_use+tool_result) → assistant(text+tool_use+tool_result) → ... +function mergeMessages(raw: ConversationMessage[]): ChatDisplayMessage[] { + const result: ChatDisplayMessage[] = []; + let assistantIdx = -1; // 上一个 assistant 消息在 result 中的索引 + + for (let i = 0; i < raw.length; i++) { + const m = raw[i]; + if (m.role === 'assistant') { + result.push({ + key: `msg-${i}`, + role: 'assistant', + blocks: [...m.blocks], + streaming: false, + }); + assistantIdx = result.length - 1; + } else if (m.role === 'user') { + result.push({ + key: `msg-${i}`, + role: 'user', + blocks: [...m.blocks], + streaming: false, + }); + assistantIdx = -1; + } else if (m.role === 'tool') { + // 将 tool_result blocks 合并到上一个 assistant 消息 + if (assistantIdx >= 0) { + result[assistantIdx].blocks = [ + ...result[assistantIdx].blocks, + ...m.blocks, + ]; + } + } + } + return result; +} + +// 助手消息的累积缓冲区 +interface AssistantBuffer { + text: string; + thinking: string; + toolCalls: Map; +} + +function blocksFromBuffer(buffer: AssistantBuffer, _streaming: boolean): ContentBlock[] { + const blocks: ContentBlock[] = []; + if (buffer.thinking) { + blocks.push({ type: 'thinking', thinking: buffer.thinking }); + } + if (buffer.text) { + blocks.push({ type: 'text', text: buffer.text }); + } + for (const tool of buffer.toolCalls.values()) { + blocks.push({ type: 'tool_use', id: tool.id, name: tool.name, input: tool.input }); + if (tool.output !== undefined) { + blocks.push({ type: 'tool_result', tool_use_id: tool.id, tool_name: tool.name, output: tool.output, is_error: tool.isError ?? false }); + } + } + return blocks; +} + +const App: React.FC = () => { + const [isDark, setIsDark] = useState(() => { + const saved = localStorage.getItem('claw-theme'); + if (saved) return saved === 'dark'; + return window.matchMedia('(prefers-color-scheme: dark)').matches; + }); + + const [activeSessionId, setActiveSessionId] = useState(null); + const [messages, setMessages] = useState([]); + const [isStreaming, setIsStreaming] = useState(false); + const [_usage, setUsage] = useState(null); + + // 助手消息缓冲区 + const bufferRef = useRef(null); + const msgCounterRef = useRef(0); + + const toggleTheme = useCallback(() => { + setIsDark((prev) => { + const next = !prev; + localStorage.setItem('claw-theme', next ? 'dark' : 'light'); + return next; + }); + }, []); + + // 处理 SSE 事件 + const handleEvent = useCallback((event: SessionEvent) => { + switch (event.type) { + case 'snapshot': { + // 初始化完整消息状态(合并 tool 消息到 assistant) + setMessages(mergeMessages(event.messages)); + setIsStreaming(false); + bufferRef.current = null; + break; + } + + case 'message_delta': { + // 累积文本 delta + if (!bufferRef.current) return; + bufferRef.current.text += event.text; + setMessages((prev) => + updateLastAssistant(prev, bufferRef.current!) + ); + break; + } + + case 'thinking_delta': { + if (!bufferRef.current) return; + bufferRef.current.thinking += event.thinking; + setMessages((prev) => + updateLastAssistant(prev, bufferRef.current!) + ); + break; + } + + case 'tool_use_start': { + if (!bufferRef.current) return; + bufferRef.current.toolCalls.set(event.tool_use_id, { + id: event.tool_use_id, + name: event.tool_name, + input: event.input, + }); + setMessages((prev) => + updateLastAssistant(prev, bufferRef.current!) + ); + break; + } + + case 'tool_result': { + if (!bufferRef.current) return; + const existing = bufferRef.current.toolCalls.get(event.tool_use_id); + if (existing) { + existing.output = event.output; + existing.isError = event.is_error; + } else { + bufferRef.current.toolCalls.set(event.tool_use_id, { + id: event.tool_use_id, + name: event.tool_name, + input: '', + output: event.output, + isError: event.is_error, + }); + } + setMessages((prev) => + updateLastAssistant(prev, bufferRef.current!) + ); + break; + } + + case 'usage': { + setUsage(event.usage); + break; + } + + case 'turn_complete': { + setIsStreaming(false); + setUsage(event.usage); + // 标记最后一条助手消息为非流式 + setMessages((prev) => { + if (prev.length === 0) return prev; + const last = prev[prev.length - 1]; + if (last.role !== 'assistant') return prev; + return [ + ...prev.slice(0, -1), + { ...last, streaming: false }, + ]; + }); + bufferRef.current = null; + break; + } + + case 'message': { + // 忽略完整 message 事件,因为 delta 已经处理了流式组装 + break; + } + } + }, []); + + // SSE 连接 + useSSE(activeSessionId, handleEvent); + + // 新建会话 + const handleNewSession = useCallback(async () => { + try { + const res = await api.createSession(); + setActiveSessionId(res.session_id); + setMessages([]); + setUsage(null); + setIsStreaming(false); + bufferRef.current = null; + } catch (err) { + console.error('创建会话失败:', err); + } + }, []); + + // 切换会话 + const handleSessionChange = useCallback(async (id: string) => { + try { + const details = await api.getSession(id); + setActiveSessionId(id); + setMessages(mergeMessages(details.messages)); + setUsage(null); + setIsStreaming(false); + bufferRef.current = null; + } catch (err) { + console.error('加载会话失败:', err); + } + }, []); + + // 删除会话 + const handleDeleteSession = useCallback(async (id: string) => { + try { + await api.deleteSession(id); + if (activeSessionId === id) { + setActiveSessionId(null); + setMessages([]); + setUsage(null); + } + } catch (err) { + console.error('删除会话失败:', err); + } + }, [activeSessionId]); + + // 发送消息 + const handleSend = useCallback(async (message: string) => { + if (!activeSessionId || isStreaming) return; + + // 添加用户消息 + const userKey = `user-${++msgCounterRef.current}`; + const assistantKey = `assistant-${msgCounterRef.current}`; + + // 初始化助手消息缓冲区 + bufferRef.current = { + text: '', + thinking: '', + toolCalls: new Map(), + }; + + const userMsg: ChatDisplayMessage = { + key: userKey, + role: 'user', + blocks: [{ type: 'text', text: message }], + }; + + const assistantMsg: ChatDisplayMessage = { + key: assistantKey, + role: 'assistant', + blocks: [], + streaming: true, + }; + + setMessages((prev) => [...prev, userMsg, assistantMsg]); + setIsStreaming(true); + + try { + await api.sendMessage(activeSessionId, message); + } catch (err) { + console.error('发送消息失败:', err); + setIsStreaming(false); + } + }, [activeSessionId, isStreaming]); + + // 取消(中止) + const handleCancel = useCallback(() => { + setIsStreaming(false); + setMessages((prev) => { + if (prev.length === 0) return prev; + const last = prev[prev.length - 1]; + if (last.role !== 'assistant') return prev; + return [ + ...prev.slice(0, -1), + { ...last, streaming: false }, + ]; + }); + bufferRef.current = null; + }, []); + + return ( + +
+ + +
+
+ ); +}; + +// 更新最后一条助手消息 +function updateLastAssistant( + prev: ChatDisplayMessage[], + buffer: AssistantBuffer, +): ChatDisplayMessage[] { + if (prev.length === 0) return prev; + const last = prev[prev.length - 1]; + if (last.role !== 'assistant') return prev; + return [ + ...prev.slice(0, -1), + { + ...last, + blocks: blocksFromBuffer(buffer, true), + }, + ]; +} + +export default App; diff --git a/frontend/src/api.ts b/frontend/src/api.ts new file mode 100644 index 0000000..e706b1d --- /dev/null +++ b/frontend/src/api.ts @@ -0,0 +1,57 @@ +import type { + CreateSessionResponse, + ListSessionsResponse, + SessionDetailsResponse, + UsageResponse, + CompactResponse, +} from './types'; + +const BASE = '/sessions'; + +async function request(url: string, init?: RequestInit): Promise { + const res = await fetch(url, { + headers: { 'Content-Type': 'application/json' }, + ...init, + }); + if (!res.ok) { + const body = await res.json().catch(() => ({ error: res.statusText })); + throw new Error(body.error || res.statusText); + } + if (res.status === 202 || res.status === 204) return undefined as T; + const contentLength = res.headers.get('content-length'); + if (contentLength === '0') return undefined as T; + const text = await res.text(); + if (!text.trim()) return undefined as T; + return JSON.parse(text) as T; +} + +export async function createSession(): Promise { + return request(BASE, { method: 'POST' }); +} + +export async function listSessions(): Promise { + return request(BASE); +} + +export async function getSession(id: string): Promise { + return request(`${BASE}/${id}`); +} + +export async function deleteSession(id: string): Promise { + return request(`${BASE}/${id}`, { method: 'DELETE' }); +} + +export async function sendMessage(sessionId: string, message: string): Promise { + return request(`${BASE}/${sessionId}/message`, { + method: 'POST', + body: JSON.stringify({ message }), + }); +} + +export async function compactSession(sessionId: string): Promise { + return request(`${BASE}/${sessionId}/compact`, { method: 'POST' }); +} + +export async function getUsage(sessionId: string): Promise { + return request(`${BASE}/${sessionId}/usage`); +} diff --git a/frontend/src/components/ChatView.tsx b/frontend/src/components/ChatView.tsx new file mode 100644 index 0000000..d45ac57 --- /dev/null +++ b/frontend/src/components/ChatView.tsx @@ -0,0 +1,475 @@ +import React, { useCallback } from 'react'; +import { Bubble, Sender, Think, ThoughtChain, Actions, CodeHighlighter, Mermaid, Sources } from '@ant-design/x'; +import { UserOutlined, RobotOutlined, GlobalOutlined } from '@ant-design/icons'; +import { theme, Skeleton, Spin, Popover } from 'antd'; +import { XMarkdown } from '@ant-design/x-markdown'; + +// 助手气泡 body 撑满可用宽度,避免 Mermaid 等内容宽度受文本行长度影响 +const bubbleStyle = document.createElement('style'); +bubbleStyle.textContent = '.ant-bubble-start > .ant-bubble-body { width: 80%; }'; +document.head.appendChild(bubbleStyle); +import type { ComponentProps, Token } from '@ant-design/x-markdown'; +import Latex from '@ant-design/x-markdown/plugins/latex'; +import '@ant-design/x-markdown/themes/light.css'; +import '@ant-design/x-markdown/themes/dark.css'; +import type { ContentBlock } from '../types'; +import ToolChain from './ToolChain'; +import WelcomeScreen from './WelcomeScreen'; + +// ── XMarkdown 插件配置 ──────────────────────────────────────────────── + +// LaTeX 数学公式插件:解析 $...$ / $$...$$ / \(...\) / \[...\] +// 自定义脚注插件:解析 [^1] 语法 → 标签 +const footnoteExtension = { + name: 'footnote', + level: 'inline' as const, + start(src: string) { + const idx = src.indexOf('[^'); + return idx !== -1 ? idx : undefined; + }, + tokenizer(src: string) { + const match = src.match(/^\[\^(\d+)\]/); + if (!match) return; + return { + type: 'footnote', + raw: match[0], + text: match[1], + renderType: 'component' as const, + }; + }, + renderer(token: Token) { + return `${token.text}`; + }, +}; + +const xMarkdownConfig = { extensions: [...Latex(), footnoteExtension] }; + +// ── XMarkdown components 映射 ───────────────────────────────────────── + +// Infographic 渲染器(动态加载 @antv/infographic) +const InfographicBlock: React.FC<{ content: string }> = ({ content }) => { + const containerRef = React.useRef(null); + const instanceRef = React.useRef<{ render: (spec: string) => void; destroy: () => void } | null>(null); + const [loading, setLoading] = React.useState(true); + const [error, setError] = React.useState(false); + + React.useEffect(() => { + if (!containerRef.current) return; + let mounted = true; + + import('@antv/infographic') + .then(({ Infographic }) => { + if (!mounted || !containerRef.current) return; + instanceRef.current = new Infographic({ container: containerRef.current }); + instanceRef.current.render(content); + setLoading(false); + }) + .catch(() => { + if (mounted) { setLoading(false); setError(true); } + }); + + return () => { mounted = false; instanceRef.current?.destroy(); }; + }, [content]); + + if (error) { + return ( +
+ Infographic 渲染失败(缺少 @antv/infographic) +
+ ); + } + + return ( +
+ {loading && ( +
+ +
+ )} +
+
+ ); +}; + +// 完整的代码块渲染器 +const CodeBlock: React.FC = ({ children, lang, block, streamStatus, ...rest }) => { + // 行内 code + if (!block) { + return {children}; + } + + const content = String(children).replace(/\n$/, ''); + + // Mermaid 图表:直接渲染,让 Mermaid 组件展示内置的渲染动画 + if (lang === 'mermaid') { + if (!content) return null; + return {content}; + } + + // Infographic 信息图 + if (lang === 'infographic') { + if (!content) return null; + return ; + } + + // 普通代码块:语法高亮 + return ( + + {content} + + ); +}; + +// 流式渲染:图片未闭合 → 骨架屏 +const IncompleteImage = () => ; + +// 流式渲染:链接未闭合 → 显示已有文本 +const IncompleteLink: React.FC = (props) => { + const text = decodeURIComponent(String(props['data-raw'] || '')); + const match = text.match(/^\[([^\]]*)\]/); + const displayText = match ? match[1] : text.slice(1); + return {displayText}; +}; + +// 流式渲染:表格未闭合 → 骨架屏 +const IncompleteTable = () => ; + +// 流式渲染:HTML 未闭合 → 骨架屏 +const IncompleteHtml = () => ; + +// 流式渲染:强调语法未闭合 → 显示已有文本 +const IncompleteEmphasis: React.FC = (props) => { + const text = decodeURIComponent(String(props['data-raw'] || '')); + const match = text.match(/^([*_]{1,3})([^*_]*)/); + if (!match || !match[2]) return null; + const [, symbols, content] = match; + const level = symbols.length; + if (level === 1) return {content}; + if (level === 2) return {content}; + return {content}; +}; + +// 流式渲染:行内代码未闭合 → 显示已有文本 +const IncompleteInlineCode: React.FC = (props) => { + const rawData = String(props['data-raw'] || ''); + if (!rawData) return null; + return {decodeURIComponent(rawData).slice(1)}; +}; + +// Markdown 中嵌入的 标签渲染(根据 streamStatus 自动切换状态) +const ThinkInMarkdown: React.FC = React.memo((props) => { + const isDone = props.streamStatus === 'done'; + return ( + + {props.children} + + ); +}); + +// 引用 → Sources 内联组件(搜索增强场景) +const SupComponent: React.FC = React.memo((props) => { + const key = parseInt(String(props.children) || '0', 10); + return ( + + ); +}); + +// 自定义脚注 [^1] → 可点击引用标记 +const FootnoteComponent: React.FC = React.memo((props) => { + const key = String(props['data-key'] || props.children); + return ( + + + {key} + + + ); +}); + +const xMarkdownComponents = { + code: CodeBlock, + think: ThinkInMarkdown, + sup: SupComponent, + footnote: FootnoteComponent, + 'incomplete-image': IncompleteImage, + 'incomplete-link': IncompleteLink, + 'incomplete-table': IncompleteTable, + 'incomplete-html': IncompleteHtml, + 'incomplete-emphasis': IncompleteEmphasis, + 'incomplete-inline-code': IncompleteInlineCode, +}; + +interface ChatViewProps { + messages: ChatDisplayMessage[]; + isStreaming: boolean; + hasActiveSession: boolean; + onSend: (message: string) => void; + onCancel: () => void; +} + +export interface ChatDisplayMessage { + key: string; + role: 'user' | 'assistant'; + blocks: ContentBlock[]; + streaming?: boolean; +} + +const ChatView: React.FC = ({ + messages, + isStreaming, + hasActiveSession, + onSend, + onCancel, +}) => { + const [inputValue, setInputValue] = React.useState(''); + + const handleSubmit = useCallback((msg: string) => { + const trimmed = msg.trim(); + if (!trimmed) return; + onSend(trimmed); + setInputValue(''); + }, [onSend]); + + if (!hasActiveSession) { + return ; + } + + // 用 key 索引消息,供 contentRender 查找 + const msgMap = new Map(messages.map((m) => [m.key, m])); + + const items = messages.map((msg) => { + const textContent = msg.blocks + .filter((b): b is Extract => b.type === 'text') + .map((b) => b.text) + .join('\n'); + + return { + key: msg.key, + role: msg.role, + content: msg.role === 'user' ? textContent : '', + loading: msg.role === 'assistant' && msg.streaming && !textContent, + }; + }); + + // 使用 v2 的 role(单数)配置 + const role = { + user: { + placement: 'end' as const, + variant: 'filled' as const, + shape: 'round' as const, + avatar: , + // styles: { content: { width: '80%' } }, + }, + assistant: { + placement: 'start' as const, + variant: 'borderless' as const, + avatar: , + streaming: true, + styles: { content: { width: '80%' } }, + header: (_content: unknown, { status }: { status?: string }) => { + if (status === 'loading' || status === 'updating') { + return ( + } + title="模型运行中" + /> + ); + } + if (status === 'success') { + return ( + } + title="执行完成" + /> + ); + } + return null; + }, + footer: (_content: string, { key, status }: { key?: string | number; status?: string }) => { + if (status === 'updating' || status === 'loading') return null; + const msg = msgMap.get(String(key)); + if (!msg || msg.role !== 'assistant') return null; + const textBlocks = msg.blocks + .filter((b): b is Extract => b.type === 'text') + .map((b) => b.text) + .join('\n'); + return ( +
+ }, + ]} + /> +
+ ); + }, + contentRender: (_content: unknown, { key }: { key?: string | number }) => { + const msg = msgMap.get(String(key)); + if (!msg) return ''; + return ; + }, + }, + }; + + return ( +
+
+ +
+ +
+ +
+
+ ); +}; + +// ── 助手消息内容渲染 ────────────────────────────────────────────────── + +const AssistantContent: React.FC<{ blocks: ContentBlock[]; streaming?: boolean }> = ({ + blocks, + streaming, +}) => { + const { theme: antdTheme } = theme.useToken(); + const mdClassName = antdTheme.id === 0 ? 'x-markdown-light' : 'x-markdown-dark'; + const elements: React.ReactNode[] = []; + + // 收集工具调用 + const toolCalls = new Map(); + let firstToolIndex = -1; + + for (let i = 0; i < blocks.length; i++) { + const block = blocks[i]; + if (block.type === 'tool_use') { + if (firstToolIndex === -1) firstToolIndex = i; + toolCalls.set(block.id, { id: block.id, name: block.name, input: block.input }); + } else if (block.type === 'tool_result') { + if (firstToolIndex === -1) firstToolIndex = i; + const existing = toolCalls.get(block.tool_use_id); + if (existing) { + existing.output = block.output; + existing.isError = block.is_error; + } else { + toolCalls.set(block.tool_use_id, { + id: block.tool_use_id, name: block.tool_name, input: '', + output: block.output, isError: block.is_error, + }); + } + } + } + + for (let i = 0; i < blocks.length; i++) { + const block = blocks[i]; + switch (block.type) { + case 'thinking': + elements.push( + +
+ {block.thinking} +
+
+ ); + break; + + case 'text': + elements.push( + + {block.text} + + ); + break; + + case 'redacted_thinking': + elements.push( + + [内容已隐藏] + + ); + break; + + case 'tool_use': + if (i === firstToolIndex) { + elements.push( + + ); + } + break; + + case 'tool_result': + // 由 ToolChain 统一渲染 + break; + } + } + + return
{elements}
; +}; + +export default ChatView; diff --git a/frontend/src/components/SessionSidebar.tsx b/frontend/src/components/SessionSidebar.tsx new file mode 100644 index 0000000..4843ba6 --- /dev/null +++ b/frontend/src/components/SessionSidebar.tsx @@ -0,0 +1,116 @@ +import React, { useEffect, useState } from 'react'; +import { Conversations } from '@ant-design/x'; +import { DeleteOutlined, PlusOutlined, BulbOutlined, BulbFilled } from '@ant-design/icons'; +import type { SessionSummary } from '../types'; +import * as api from '../api'; + +interface SessionSidebarProps { + activeSessionId: string | null; + onSessionChange: (id: string) => void; + onNewSession: () => void; + onDeleteSession: (id: string) => void; + isDark: boolean; + onToggleTheme: () => void; +} + +const SessionSidebar: React.FC = ({ + activeSessionId, + onSessionChange, + onNewSession, + onDeleteSession, + isDark, + onToggleTheme, +}) => { + const [sessions, setSessions] = useState([]); + + const fetchSessions = async () => { + try { + const res = await api.listSessions(); + setSessions(res.sessions); + } catch { + // 忽略 + } + }; + + useEffect(() => { + fetchSessions(); + }, [activeSessionId]); + + const items = sessions.map((s) => ({ + key: s.id, + label: `会话 ${s.id.replace('session-', '')}`, + timestamp: s.created_at, + })); + + return ( +
+ {/* Logo */} +
+ 🐾 + Claw Code +
+ + {/* 会话列表 */} +
+ onSessionChange(key)} + menu={(conversation) => ({ + items: [ + { + key: 'delete', + label: '删除', + icon: , + danger: true, + }, + ], + onClick: (info) => { + if (info.key === 'delete') { + onDeleteSession(conversation.key); + } + }, + })} + creation={{ + onClick: onNewSession, + label: '新建会话', + icon: , + }} + /> +
+ + {/* 主题切换 */} +
+ {isDark ? : } + {isDark ? '浅色模式' : '深色模式'} +
+
+ ); +}; + +export default SessionSidebar; diff --git a/frontend/src/components/ToolChain.tsx b/frontend/src/components/ToolChain.tsx new file mode 100644 index 0000000..1c6fbeb --- /dev/null +++ b/frontend/src/components/ToolChain.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { ThoughtChain } from '@ant-design/x'; + +interface ToolCall { + id: string; + name: string; + input: string; + output?: string; + isError?: boolean; +} + +interface ToolChainProps { + tools: ToolCall[]; +} + +const ToolChain: React.FC = ({ tools }) => { + const items = tools.map((tool) => { + const hasResult = tool.output !== undefined; + let status: 'loading' | 'success' | 'error' = 'loading'; + if (hasResult) { + status = tool.isError ? 'error' : 'success'; + } + + return { + key: tool.id, + status, + title: tool.name, + description: hasResult ? (tool.isError ? '执行出错' : '执行完成') : '执行中...', + collapsible: true, + content: ( +
+ {tool.input && ( +
+
输入
+
+                {tryFormatJSON(tool.input)}
+              
+
+ )} + {tool.output !== undefined && ( +
+
+ {tool.isError ? '错误' : '输出'} +
+
+                {tool.output}
+              
+
+ )} +
+ ), + }; + }); + + if (items.length === 0) return null; + + return ; +}; + +function tryFormatJSON(str: string): string { + try { + return JSON.stringify(JSON.parse(str), null, 2); + } catch { + return str; + } +} + +export default ToolChain; +export type { ToolCall }; diff --git a/frontend/src/components/WelcomeScreen.tsx b/frontend/src/components/WelcomeScreen.tsx new file mode 100644 index 0000000..b600813 --- /dev/null +++ b/frontend/src/components/WelcomeScreen.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Welcome, Prompts } from '@ant-design/x'; + +const examplePrompts = [ + { key: '1', label: '总结当前工作区', description: '分析项目结构和代码' }, + { key: '2', label: '帮我写一个测试', description: '为指定模块生成测试用例' }, + { key: '3', label: '查找 Bug', description: '检查代码中的潜在问题' }, +]; + +interface WelcomeScreenProps { + onSelect: (prompt: string) => void; +} + +const WelcomeScreen: React.FC = ({ onSelect }) => { + return ( +
+ + { + onSelect(String(info.data.label)); + }} + /> +
+ ); +}; + +export default WelcomeScreen; diff --git a/frontend/src/hooks/useSSE.ts b/frontend/src/hooks/useSSE.ts new file mode 100644 index 0000000..6b8b54f --- /dev/null +++ b/frontend/src/hooks/useSSE.ts @@ -0,0 +1,50 @@ +import { useEffect, useRef, useCallback } from 'react'; +import type { SessionEvent } from '../types'; + +export function useSSE( + sessionId: string | null, + onEvent: (event: SessionEvent) => void, +): void { + const onEventRef = useRef(onEvent); + onEventRef.current = onEvent; + + const stableCallback = useCallback((event: SessionEvent) => { + onEventRef.current(event); + }, []); + + useEffect(() => { + if (!sessionId) return; + + const es = new EventSource(`/sessions/${sessionId}/events`); + + const eventTypes: SessionEvent['type'][] = [ + 'snapshot', + 'message', + 'message_delta', + 'thinking_delta', + 'tool_use_start', + 'tool_result', + 'usage', + 'turn_complete', + ]; + + for (const type of eventTypes) { + es.addEventListener(type, (e: MessageEvent) => { + try { + const data = JSON.parse(e.data) as SessionEvent; + stableCallback(data); + } catch { + // 忽略解析错误 + } + }); + } + + es.onerror = () => { + // EventSource 会自动重连 + }; + + return () => { + es.close(); + }; + }, [sessionId, stableCallback]); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..3b33104 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +// 全局样式:消除默认 margin/padding,防止 100vh 溢出产生页面滚动条 +document.body.style.margin = '0'; +document.body.style.padding = '0'; +document.body.style.overflow = 'hidden'; +document.documentElement.style.overflow = 'hidden'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/frontend/src/types.ts b/frontend/src/types.ts new file mode 100644 index 0000000..6b6b28f --- /dev/null +++ b/frontend/src/types.ts @@ -0,0 +1,160 @@ +// 镜像 crates/runtime/src/session.rs 和 crates/server/src/lib.rs 的类型 + +// ── ContentBlock ────────────────────────────────────────────────────── + +export interface TextBlock { + type: 'text'; + text: string; +} + +export interface ThinkingBlock { + type: 'thinking'; + thinking: string; + signature?: string; +} + +export interface RedactedThinkingBlock { + type: 'redacted_thinking'; + data: unknown; +} + +export interface ToolUseBlock { + type: 'tool_use'; + id: string; + name: string; + input: string; +} + +export interface ToolResultBlock { + type: 'tool_result'; + tool_use_id: string; + tool_name: string; + output: string; + is_error: boolean; +} + +export type ContentBlock = + | TextBlock + | ThinkingBlock + | RedactedThinkingBlock + | ToolUseBlock + | ToolResultBlock; + +// ── MessageRole ─────────────────────────────────────────────────────── + +export type MessageRole = 'system' | 'user' | 'assistant' | 'tool'; + +// ── ConversationMessage ─────────────────────────────────────────────── + +export interface TokenUsage { + input_tokens: number; + output_tokens: number; + cache_creation_input_tokens: number; + cache_read_input_tokens: number; +} + +export interface ConversationMessage { + role: MessageRole; + blocks: ContentBlock[]; + usage?: TokenUsage; +} + +// ── SSE SessionEvent ────────────────────────────────────────────────── + +export interface SnapshotEvent { + type: 'snapshot'; + session_id: string; + messages: ConversationMessage[]; +} + +export interface MessageEvent { + type: 'message'; + session_id: string; + message: ConversationMessage; +} + +export interface MessageDeltaEvent { + type: 'message_delta'; + session_id: string; + text: string; +} + +export interface ToolUseStartEvent { + type: 'tool_use_start'; + session_id: string; + tool_use_id: string; + tool_name: string; + input: string; +} + +export interface ToolResultEvent { + type: 'tool_result'; + session_id: string; + tool_use_id: string; + tool_name: string; + output: string; + is_error: boolean; +} + +export interface ThinkingDeltaEvent { + type: 'thinking_delta'; + session_id: string; + thinking: string; +} + +export interface UsageEvent { + type: 'usage'; + session_id: string; + usage: TokenUsage; +} + +export interface TurnCompleteEvent { + type: 'turn_complete'; + session_id: string; + usage: TokenUsage; + iterations: number; +} + +export type SessionEvent = + | SnapshotEvent + | MessageEvent + | MessageDeltaEvent + | ToolUseStartEvent + | ToolResultEvent + | ThinkingDeltaEvent + | UsageEvent + | TurnCompleteEvent; + +// ── REST API 响应类型 ───────────────────────────────────────────────── + +export interface SessionSummary { + id: string; + created_at: number; + message_count: number; +} + +export interface CreateSessionResponse { + session_id: string; +} + +export interface SessionDetailsResponse { + id: string; + created_at: number; + messages: ConversationMessage[]; +} + +export interface UsageResponse { + session_id: string; + usage: TokenUsage; + turns: number; +} + +export interface CompactResponse { + session_id: string; + summary: string; + removed_message_count: number; +} + +export interface ListSessionsResponse { + sessions: SessionSummary[]; +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..0085339 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "moduleResolution": "bundler", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "isolatedModules": true, + "moduleDetection": "force", + "jsx": "react-jsx", + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..e46810f --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { + proxy: { + '/sessions': { + target: 'http://localhost:3000', + changeOrigin: true, + }, + }, + }, +});