From bda32d243555d9def2b88be92dfd926fda8a1fe4 Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sun, 22 Feb 2026 13:46:18 -0800 Subject: [PATCH 01/12] Add multi-agent communication graph Directed links between agents with relationship semantics (peer/superior/subordinate), a shared instance-level database, and a send_agent_message tool that routes through the existing MessagingManager pipeline. Foundation for the 0.2.0 release. - Instance database (instance.db) with separate migration path for cross-agent data - AgentLink model with direction (one_way/two_way) and relationship policies - LinkStore CRUD backed by instance.db - [[links]] config section with startup sync - send_agent_message tool: resolves target, validates link, injects InboundMessage - ROLE.md identity file for operational responsibilities - Org context prompt fragment (superiors/subordinates/peers hierarchy) - Link context prompt fragment for agent-to-agent channels - AgentMessageSent/Received process events with SSE forwarding - Link CRUD API endpoints + topology snapshot endpoint --- .../20260222000001_agent_links.sql | 14 + prompts/en/channel.md.j2 | 8 + prompts/en/fragments/link_context.md.j2 | 12 + prompts/en/fragments/org_context.md.j2 | 28 ++ .../send_agent_message_description.md.j2 | 1 + src/agent/channel.rs | 144 +++++++- src/api.rs | 1 + src/api/agents.rs | 1 + src/api/links.rs | 330 ++++++++++++++++++ src/api/server.rs | 16 +- src/api/state.rs | 54 +++ src/api/system.rs | 2 + src/config.rs | 44 +++ src/db.rs | 33 +- src/identity/files.rs | 7 + src/lib.rs | 14 + src/links.rs | 7 + src/links/store.rs | 208 +++++++++++ src/links/types.rs | 119 +++++++ src/main.rs | 44 +++ src/prompts/engine.rs | 84 ++++- src/prompts/text.rs | 11 + src/tools.rs | 11 +- src/tools/send_agent_message.rs | 256 ++++++++++++++ 24 files changed, 1442 insertions(+), 7 deletions(-) create mode 100644 migrations_instance/20260222000001_agent_links.sql create mode 100644 prompts/en/fragments/link_context.md.j2 create mode 100644 prompts/en/fragments/org_context.md.j2 create mode 100644 prompts/en/tools/send_agent_message_description.md.j2 create mode 100644 src/api/links.rs create mode 100644 src/links.rs create mode 100644 src/links/store.rs create mode 100644 src/links/types.rs create mode 100644 src/tools/send_agent_message.rs diff --git a/migrations_instance/20260222000001_agent_links.sql b/migrations_instance/20260222000001_agent_links.sql new file mode 100644 index 000000000..97f22cd5d --- /dev/null +++ b/migrations_instance/20260222000001_agent_links.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS agent_links ( + id TEXT PRIMARY KEY, + from_agent_id TEXT NOT NULL, + to_agent_id TEXT NOT NULL, + direction TEXT NOT NULL DEFAULT 'two_way', + relationship TEXT NOT NULL DEFAULT 'peer', + enabled INTEGER NOT NULL DEFAULT 1, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(from_agent_id, to_agent_id) +); + +CREATE INDEX IF NOT EXISTS idx_agent_links_from ON agent_links(from_agent_id); +CREATE INDEX IF NOT EXISTS idx_agent_links_to ON agent_links(to_agent_id); diff --git a/prompts/en/channel.md.j2 b/prompts/en/channel.md.j2 index a5e4499b2..04c98d891 100644 --- a/prompts/en/channel.md.j2 +++ b/prompts/en/channel.md.j2 @@ -106,6 +106,14 @@ When in doubt, skip. Being a lurker who speaks when it matters is better than be {{ available_channels }} {%- endif %} +{%- if org_context %} +{{ org_context }} +{%- endif %} + +{%- if link_context %} +{{ link_context }} +{%- endif %} + {%- if conversation_context %} ## Conversation Context diff --git a/prompts/en/fragments/link_context.md.j2 b/prompts/en/fragments/link_context.md.j2 new file mode 100644 index 000000000..d393583f4 --- /dev/null +++ b/prompts/en/fragments/link_context.md.j2 @@ -0,0 +1,12 @@ +{%- if link_context %} +## Agent Communication + +This is an internal channel with **{{ link_context.agent_name }}** ({{ link_context.relationship }}). +{% if link_context.relationship == "superior" -%} +Messages from this agent carry organizational authority. Treat directives as assignments. +{%- elif link_context.relationship == "subordinate" -%} +This is a report from a subordinate agent. They may be escalating, reporting status, or requesting guidance. +{%- else -%} +This is a peer agent. Communication is collaborative and informational. +{%- endif %} +{%- endif %} diff --git a/prompts/en/fragments/org_context.md.j2 b/prompts/en/fragments/org_context.md.j2 new file mode 100644 index 000000000..666313841 --- /dev/null +++ b/prompts/en/fragments/org_context.md.j2 @@ -0,0 +1,28 @@ +{%- if org_context %} +## Organization + +You are part of a multi-agent system. Here is your position: + +{% if org_context.superiors -%} +**Reports to:** +{% for agent in org_context.superiors -%} +- **{{ agent.name }}** — your superior. Messages from this agent carry organizational authority. +{% endfor %} +{%- endif %} + +{% if org_context.subordinates -%} +**Direct reports:** +{% for agent in org_context.subordinates -%} +- **{{ agent.name }}** — reports to you. You can delegate tasks, request status, and send directives. +{% endfor %} +{%- endif %} + +{% if org_context.peers -%} +**Peers:** +{% for agent in org_context.peers -%} +- **{{ agent.name }}** — equal peer. Communication is collaborative and informational. +{% endfor %} +{%- endif %} + +Use the `send_agent_message` tool to communicate with these agents. Each link has a dedicated internal channel with full conversation history. +{%- endif %} diff --git a/prompts/en/tools/send_agent_message_description.md.j2 b/prompts/en/tools/send_agent_message_description.md.j2 new file mode 100644 index 000000000..8b312e67a --- /dev/null +++ b/prompts/en/tools/send_agent_message_description.md.j2 @@ -0,0 +1 @@ +Send a message to another agent through the agent communication graph. The message is delivered to a dedicated internal channel between you and the target agent. Use this to coordinate, delegate, escalate, or share information with linked agents. diff --git a/src/agent/channel.rs b/src/agent/channel.rs index 158564718..5054fa6ff 100644 --- a/src/agent/channel.rs +++ b/src/agent/channel.rs @@ -145,6 +145,8 @@ pub struct Channel { pending_retrigger_metadata: HashMap, /// Deadline for firing the pending retrigger (debounce timer). retrigger_deadline: Option, + /// Optional send_agent_message tool (only when agent has active links). + send_agent_message_tool: Option, } impl Channel { @@ -226,6 +228,7 @@ impl Channel { pending_retrigger: false, pending_retrigger_metadata: HashMap::new(), retrigger_deadline: None, + send_agent_message_tool: None, }; (channel, message_tx) @@ -623,10 +626,13 @@ impl Channel { let available_channels = self.build_available_channels().await; + let org_context = self.build_org_context(&prompt_engine).await; + let link_context = self.build_link_context(&prompt_engine).await; + let empty_to_none = |s: String| if s.is_empty() { None } else { Some(s) }; prompt_engine - .render_channel_prompt( + .render_channel_prompt_with_links( empty_to_none(identity_context), empty_to_none(memory_bulletin.to_string()), empty_to_none(skills_prompt), @@ -635,6 +641,8 @@ impl Channel { empty_to_none(status_text), coalesce_hint, available_channels, + org_context, + link_context, ) .expect("failed to render channel prompt") } @@ -674,6 +682,20 @@ impl Channel { Vec::new() }; + // Emit AgentMessageReceived event for internal agent-to-agent messages + if message.source == "internal" { + if let Some(from_agent_id) = message.metadata.get("from_agent_id").and_then(|v| v.as_str()) { + if let Some(link_id) = message.metadata.get("link_id").and_then(|v| v.as_str()) { + self.deps.event_tx.send(ProcessEvent::AgentMessageReceived { + from_agent_id: Arc::from(from_agent_id), + to_agent_id: self.deps.agent_id.clone(), + link_id: link_id.to_string(), + channel_id: self.id.clone(), + }).ok(); + } + } + } + // Persist user messages (skip system re-triggers) if message.source != "system" { let sender_name = message @@ -794,6 +816,118 @@ impl Channel { prompt_engine.render_available_channels(entries).ok() } + /// Build org context showing the agent's position in the communication hierarchy. + async fn build_org_context( + &self, + prompt_engine: &crate::prompts::PromptEngine, + ) -> Option { + let link_store = self.deps.link_store.as_ref()?; + let agent_id = self.deps.agent_id.as_ref(); + + let links = match link_store.get_links_for_agent(agent_id).await { + Ok(links) => links, + Err(error) => { + tracing::warn!(%error, "failed to fetch links for org context"); + return None; + } + }; + + if links.is_empty() { + return None; + } + + let mut superiors = Vec::new(); + let mut subordinates = Vec::new(); + let mut peers = Vec::new(); + + for link in &links { + if !link.enabled { + continue; + } + + let is_from = link.from_agent_id == agent_id; + let other_agent_id = if is_from { + &link.to_agent_id + } else { + &link.from_agent_id + }; + + // Determine relationship from this agent's perspective + let relationship = if is_from { + link.relationship + } else { + link.relationship.inverse() + }; + + // For one-way links where this agent is the to_agent (can't initiate), + // still show them in the org context for awareness + let agent_info = crate::prompts::engine::LinkedAgent { + name: other_agent_id.clone(), + id: other_agent_id.clone(), + }; + + match relationship { + crate::links::LinkRelationship::Superior => superiors.push(agent_info), + crate::links::LinkRelationship::Subordinate => subordinates.push(agent_info), + crate::links::LinkRelationship::Peer => peers.push(agent_info), + } + } + + if superiors.is_empty() && subordinates.is_empty() && peers.is_empty() { + return None; + } + + let org_context = crate::prompts::engine::OrgContext { + superiors, + subordinates, + peers, + }; + + prompt_engine.render_org_context(org_context).ok() + } + + /// Build link context for the current channel if it's an internal agent-to-agent channel. + async fn build_link_context( + &self, + prompt_engine: &crate::prompts::PromptEngine, + ) -> Option { + // Link channels have conversation IDs starting with "link:" + let conversation_id = self.conversation_id.as_deref()?; + let link_id = conversation_id.strip_prefix("link:")?; + + let link_store = self.deps.link_store.as_ref()?; + let link = match link_store.get(link_id).await { + Ok(Some(link)) => link, + Ok(None) => return None, + Err(error) => { + tracing::warn!(%error, "failed to fetch link for channel context"); + return None; + } + }; + + let agent_id = self.deps.agent_id.as_ref(); + let is_from = link.from_agent_id == agent_id; + let other_agent_id = if is_from { + &link.to_agent_id + } else { + &link.from_agent_id + }; + + // Relationship from the other agent's perspective toward this agent + let relationship = if is_from { + link.relationship + } else { + link.relationship.inverse() + }; + + let link_context = crate::prompts::engine::LinkContext { + agent_name: other_agent_id.clone(), + relationship: relationship.as_str().to_string(), + }; + + prompt_engine.render_link_context(link_context).ok() + } + /// Assemble the full system prompt using the PromptEngine. async fn build_system_prompt(&self) -> String { let rc = &self.deps.runtime_config; @@ -818,10 +952,13 @@ impl Channel { let available_channels = self.build_available_channels().await; + let org_context = self.build_org_context(&prompt_engine).await; + let link_context = self.build_link_context(&prompt_engine).await; + let empty_to_none = |s: String| if s.is_empty() { None } else { Some(s) }; prompt_engine - .render_channel_prompt( + .render_channel_prompt_with_links( empty_to_none(identity_context), empty_to_none(memory_bulletin.to_string()), empty_to_none(skills_prompt), @@ -830,6 +967,8 @@ impl Channel { empty_to_none(status_text), None, // coalesce_hint - only set for batched messages available_channels, + org_context, + link_context, ) .expect("failed to render channel prompt") } @@ -860,6 +999,7 @@ impl Channel { skip_flag.clone(), replied_flag.clone(), self.deps.cron_tool.clone(), + self.send_agent_message_tool.clone(), ) .await { diff --git a/src/api.rs b/src/api.rs index dc5057297..e8a13c91a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -11,6 +11,7 @@ mod config; mod cortex; mod cron; mod ingest; +mod links; mod mcp; mod memories; mod messaging; diff --git a/src/api/agents.rs b/src/api/agents.rs index d655e4142..ba87893aa 100644 --- a/src/api/agents.rs +++ b/src/api/agents.rs @@ -432,6 +432,7 @@ pub(super) async fn create_agent( let guard = state.messaging_manager.read().await; guard.as_ref().cloned() }, + link_store: state.link_store.load().as_ref().clone(), }; let event_rx = event_tx.subscribe(); diff --git a/src/api/links.rs b/src/api/links.rs new file mode 100644 index 000000000..29e190911 --- /dev/null +++ b/src/api/links.rs @@ -0,0 +1,330 @@ +//! API handlers for agent link CRUD and topology. + +use crate::api::state::ApiState; +use crate::links::types::{AgentLink, LinkDirection, LinkRelationship}; + +use axum::Json; +use axum::extract::{Path, State}; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use chrono::Utc; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +/// List all links in the instance. +pub async fn list_links(State(state): State>) -> impl IntoResponse { + let Some(link_store) = get_link_store(&state) else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(serde_json::json!({"error": "link store not initialized"})), + ) + .into_response(); + }; + + match link_store.get_all().await { + Ok(links) => Json(serde_json::json!({ "links": links })).into_response(), + Err(error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": error.to_string()})), + ) + .into_response(), + } +} + +/// Get a specific link by ID. +pub async fn get_link( + State(state): State>, + Path(link_id): Path, +) -> impl IntoResponse { + let Some(link_store) = get_link_store(&state) else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(serde_json::json!({"error": "link store not initialized"})), + ) + .into_response(); + }; + + match link_store.get(&link_id).await { + Ok(Some(link)) => Json(link).into_response(), + Ok(None) => ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"error": "link not found"})), + ) + .into_response(), + Err(error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": error.to_string()})), + ) + .into_response(), + } +} + +/// Request body for creating a link. +#[derive(Debug, Deserialize)] +pub struct CreateLinkRequest { + pub from_agent_id: String, + pub to_agent_id: String, + #[serde(default = "default_direction")] + pub direction: String, + #[serde(default = "default_relationship")] + pub relationship: String, +} + +fn default_direction() -> String { + "two_way".into() +} +fn default_relationship() -> String { + "peer".into() +} + +/// Create a new link between agents. +pub async fn create_link( + State(state): State>, + Json(body): Json, +) -> impl IntoResponse { + let Some(link_store) = get_link_store(&state) else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(serde_json::json!({"error": "link store not initialized"})), + ) + .into_response(); + }; + + let direction: LinkDirection = match body.direction.parse() { + Ok(d) => d, + Err(error) => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({"error": error})), + ) + .into_response(); + } + }; + let relationship: LinkRelationship = match body.relationship.parse() { + Ok(r) => r, + Err(error) => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({"error": error})), + ) + .into_response(); + } + }; + + let now = Utc::now(); + let link = AgentLink { + id: uuid::Uuid::new_v4().to_string(), + from_agent_id: body.from_agent_id, + to_agent_id: body.to_agent_id, + direction, + relationship, + enabled: true, + created_at: now, + updated_at: now, + }; + + match link_store.create(&link).await { + Ok(()) => (StatusCode::CREATED, Json(link)).into_response(), + Err(error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": error.to_string()})), + ) + .into_response(), + } +} + +/// Request body for updating a link. +#[derive(Debug, Deserialize)] +pub struct UpdateLinkRequest { + pub direction: Option, + pub relationship: Option, + pub enabled: Option, +} + +/// Update a link's properties. +pub async fn update_link( + State(state): State>, + Path(link_id): Path, + Json(body): Json, +) -> impl IntoResponse { + let Some(link_store) = get_link_store(&state) else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(serde_json::json!({"error": "link store not initialized"})), + ) + .into_response(); + }; + + let mut link = match link_store.get(&link_id).await { + Ok(Some(link)) => link, + Ok(None) => { + return ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"error": "link not found"})), + ) + .into_response(); + } + Err(error) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": error.to_string()})), + ) + .into_response(); + } + }; + + if let Some(direction) = body.direction { + link.direction = match direction.parse() { + Ok(d) => d, + Err(error) => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({"error": error})), + ) + .into_response(); + } + }; + } + if let Some(relationship) = body.relationship { + link.relationship = match relationship.parse() { + Ok(r) => r, + Err(error) => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({"error": error})), + ) + .into_response(); + } + }; + } + if let Some(enabled) = body.enabled { + link.enabled = enabled; + } + + match link_store.update(&link).await { + Ok(()) => Json(link).into_response(), + Err(error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": error.to_string()})), + ) + .into_response(), + } +} + +/// Delete a link by ID. +pub async fn delete_link( + State(state): State>, + Path(link_id): Path, +) -> impl IntoResponse { + let Some(link_store) = get_link_store(&state) else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(serde_json::json!({"error": "link store not initialized"})), + ) + .into_response(); + }; + + match link_store.delete(&link_id).await { + Ok(()) => Json(serde_json::json!({"deleted": true})).into_response(), + Err(error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": error.to_string()})), + ) + .into_response(), + } +} + +/// Get links for a specific agent. +pub async fn agent_links( + State(state): State>, + Path(agent_id): Path, +) -> impl IntoResponse { + let Some(link_store) = get_link_store(&state) else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(serde_json::json!({"error": "link store not initialized"})), + ) + .into_response(); + }; + + match link_store.get_links_for_agent(&agent_id).await { + Ok(links) => Json(serde_json::json!({ "links": links })).into_response(), + Err(error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": error.to_string()})), + ) + .into_response(), + } +} + +/// Topology response for graph rendering. +#[derive(Debug, Serialize)] +struct TopologyResponse { + agents: Vec, + links: Vec, +} + +#[derive(Debug, Serialize)] +struct TopologyAgent { + id: String, + name: String, +} + +#[derive(Debug, Serialize)] +struct TopologyLink { + id: String, + from: String, + to: String, + direction: String, + relationship: String, + enabled: bool, +} + +/// Get the full agent topology for graph rendering. +pub async fn topology(State(state): State>) -> impl IntoResponse { + let Some(link_store) = get_link_store(&state) else { + return ( + StatusCode::SERVICE_UNAVAILABLE, + Json(serde_json::json!({"error": "link store not initialized"})), + ) + .into_response(); + }; + + let agent_configs = state.agent_configs.load(); + let agents: Vec = agent_configs + .iter() + .map(|config| TopologyAgent { + id: config.id.clone(), + name: config.id.clone(), + }) + .collect(); + + let links = match link_store.get_all().await { + Ok(links) => links + .into_iter() + .map(|link| TopologyLink { + id: link.id, + from: link.from_agent_id, + to: link.to_agent_id, + direction: link.direction.as_str().to_string(), + relationship: link.relationship.as_str().to_string(), + enabled: link.enabled, + }) + .collect(), + Err(error) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": error.to_string()})), + ) + .into_response(); + } + }; + + Json(TopologyResponse { agents, links }).into_response() +} + +/// Extract the link store from API state. +fn get_link_store(state: &ApiState) -> Option> { + let guard = state.link_store.load(); + guard.as_ref().clone() +} diff --git a/src/api/server.rs b/src/api/server.rs index ca4c59972..45e69ad5b 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -2,8 +2,8 @@ use super::state::ApiState; use super::{ - agents, bindings, channels, config, cortex, cron, ingest, mcp, memories, messaging, models, - providers, settings, skills, system, webchat, + agents, bindings, channels, config, cortex, cron, ingest, links, mcp, memories, messaging, + models, providers, settings, skills, system, webchat, }; use axum::Json; @@ -148,6 +148,18 @@ pub async fn start_http_server( .route("/update/apply", post(settings::update_apply)) .route("/webchat/send", post(webchat::webchat_send)) .route("/webchat/history", get(webchat::webchat_history)) + .route( + "/links", + get(links::list_links).post(links::create_link), + ) + .route( + "/links/{id}", + get(links::get_link) + .put(links::update_link) + .delete(links::delete_link), + ) + .route("/agents/{id}/links", get(links::agent_links)) + .route("/topology", get(links::topology)) .layer(middleware::from_fn_with_state( state.clone(), api_auth_middleware, diff --git a/src/api/state.rs b/src/api/state.rs index d3e3d6288..b9b6c5a17 100644 --- a/src/api/state.rs +++ b/src/api/state.rs @@ -93,6 +93,8 @@ pub struct ApiState { pub agent_remove_tx: mpsc::Sender, /// Shared webchat adapter for session management from API handlers. pub webchat_adapter: ArcSwap>>, + /// Instance-level link store for agent communication graph. + pub link_store: ArcSwap>>, } /// Events sent to SSE clients. Wraps ProcessEvents with agent context. @@ -172,6 +174,20 @@ pub enum ApiEvent { }, /// Configuration was reloaded (skills, identity, etc.). ConfigReloaded, + /// A message was sent from one agent to another. + AgentMessageSent { + from_agent_id: String, + to_agent_id: String, + link_id: String, + channel_id: String, + }, + /// A message was received by an agent from another agent. + AgentMessageReceived { + from_agent_id: String, + to_agent_id: String, + link_id: String, + channel_id: String, + }, } impl ApiState { @@ -211,6 +227,7 @@ impl ApiState { agent_tx, agent_remove_tx, webchat_adapter: ArcSwap::from_pointee(None), + link_store: ArcSwap::from_pointee(None), } } @@ -364,6 +381,38 @@ impl ApiState { }) .ok(); } + ProcessEvent::AgentMessageSent { + from_agent_id, + to_agent_id, + link_id, + channel_id, + .. + } => { + api_tx + .send(ApiEvent::AgentMessageSent { + from_agent_id: from_agent_id.to_string(), + to_agent_id: to_agent_id.to_string(), + link_id: link_id.clone(), + channel_id: channel_id.to_string(), + }) + .ok(); + } + ProcessEvent::AgentMessageReceived { + from_agent_id, + to_agent_id, + link_id, + channel_id, + .. + } => { + api_tx + .send(ApiEvent::AgentMessageReceived { + from_agent_id: from_agent_id.to_string(), + to_agent_id: to_agent_id.to_string(), + link_id: link_id.clone(), + channel_id: channel_id.to_string(), + }) + .ok(); + } _ => {} } } @@ -477,6 +526,11 @@ impl ApiState { self.webchat_adapter.store(Arc::new(Some(adapter))); } + /// Set the link store for the agent communication graph. + pub fn set_link_store(&self, store: Arc) { + self.link_store.store(Arc::new(Some(store))); + } + /// Send an event to all SSE subscribers. pub fn send_event(&self, event: ApiEvent) { let _ = self.event_tx.send(event); diff --git a/src/api/system.rs b/src/api/system.rs index da99f248c..d56f2ae51 100644 --- a/src/api/system.rs +++ b/src/api/system.rs @@ -93,6 +93,8 @@ pub(super) async fn events_sse( ApiEvent::ToolStarted { .. } => "tool_started", ApiEvent::ToolCompleted { .. } => "tool_completed", ApiEvent::ConfigReloaded => "config_reloaded", + ApiEvent::AgentMessageSent { .. } => "agent_message_sent", + ApiEvent::AgentMessageReceived { .. } => "agent_message_received", }; yield Ok(axum::response::sse::Event::default() .event(event_type) diff --git a/src/config.rs b/src/config.rs index 3ddb349b9..78f3b24c0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -39,6 +39,8 @@ pub struct Config { pub defaults: DefaultsConfig, /// Agent definitions. pub agents: Vec, + /// Agent communication graph links. + pub links: Vec, /// Messaging platform credentials. pub messaging: MessagingConfig, /// Routing bindings (maps platform conversations to agents). @@ -51,6 +53,15 @@ pub struct Config { pub telemetry: TelemetryConfig, } +/// A link definition from config, connecting two agents. +#[derive(Debug, Clone)] +pub struct LinkDef { + pub from: String, + pub to: String, + pub direction: String, + pub relationship: String, +} + /// HTTP API server configuration. #[derive(Debug, Clone)] pub struct ApiConfig { @@ -1090,6 +1101,8 @@ struct TomlConfig { #[serde(default)] agents: Vec, #[serde(default)] + links: Vec, + #[serde(default)] messaging: TomlMessagingConfig, #[serde(default)] bindings: Vec, @@ -1101,6 +1114,24 @@ struct TomlConfig { telemetry: TomlTelemetryConfig, } +#[derive(Deserialize)] +struct TomlLinkDef { + from: String, + to: String, + #[serde(default = "default_link_direction")] + direction: String, + #[serde(default = "default_link_relationship")] + relationship: String, +} + +fn default_link_direction() -> String { + "two_way".into() +} + +fn default_link_relationship() -> String { + "peer".into() +} + #[derive(Deserialize, Default)] struct TomlTelemetryConfig { otlp_endpoint: Option, @@ -1991,6 +2022,7 @@ impl Config { llm, defaults: DefaultsConfig::default(), agents, + links: Vec::new(), messaging: MessagingConfig::default(), bindings: Vec::new(), api, @@ -2775,11 +2807,23 @@ impl Config { } }; + let links = toml + .links + .into_iter() + .map(|l| LinkDef { + from: l.from, + to: l.to, + direction: l.direction, + relationship: l.relationship, + }) + .collect(); + Ok(Config { instance_dir, llm, defaults, agents, + links, messaging, bindings, api, diff --git a/src/db.rs b/src/db.rs index 5a1a3c54e..306690dff 100644 --- a/src/db.rs +++ b/src/db.rs @@ -5,7 +5,7 @@ use anyhow::Context as _; use sqlx::SqlitePool; use std::path::Path; -/// Database connections bundle. +/// Database connections bundle for per-agent databases. pub struct Db { /// SQLite pool for relational data. pub sqlite: SqlitePool, @@ -66,3 +66,34 @@ impl Db { // LanceDB and redb close automatically when dropped } } + +/// Instance-level database for cross-agent data (agent links, shared notes). +/// +/// Separate from per-agent databases because links span agents and need +/// a shared home that both agents can reference. +pub struct InstanceDb { + pub sqlite: SqlitePool, +} + +impl InstanceDb { + /// Connect to the instance database and run instance-level migrations. + pub async fn connect(instance_dir: &Path) -> Result { + let db_path = instance_dir.join("instance.db"); + let sqlite_url = format!("sqlite:{}?mode=rwc", db_path.display()); + let sqlite = SqlitePool::connect(&sqlite_url) + .await + .with_context(|| format!("failed to connect to instance.db at {}", db_path.display()))?; + + sqlx::migrate!("./migrations_instance") + .run(&sqlite) + .await + .with_context(|| "failed to run instance database migrations")?; + + Ok(Self { sqlite }) + } + + /// Close the instance database connection. + pub async fn close(self) { + self.sqlite.close().await; + } +} diff --git a/src/identity/files.rs b/src/identity/files.rs index e5b5c8d2e..0b4498a46 100644 --- a/src/identity/files.rs +++ b/src/identity/files.rs @@ -9,6 +9,7 @@ pub struct Identity { pub soul: Option, pub identity: Option, pub user: Option, + pub role: Option, } impl Identity { @@ -18,6 +19,7 @@ impl Identity { soul: load_optional_file(&workspace.join("SOUL.md")).await, identity: load_optional_file(&workspace.join("IDENTITY.md")).await, user: load_optional_file(&workspace.join("USER.md")).await, + role: load_optional_file(&workspace.join("ROLE.md")).await, } } @@ -40,6 +42,11 @@ impl Identity { output.push_str(user); output.push_str("\n\n"); } + if let Some(role) = &self.role { + output.push_str("## Role\n\n"); + output.push_str(role); + output.push_str("\n\n"); + } output } diff --git a/src/lib.rs b/src/lib.rs index 98b4eac3f..9bd50f55e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod db; pub mod error; pub mod hooks; pub mod identity; +pub mod links; pub mod llm; pub mod mcp; pub mod memory; @@ -170,6 +171,18 @@ pub enum ProcessEvent { question_id: String, questions: Vec, }, + AgentMessageSent { + from_agent_id: AgentId, + to_agent_id: AgentId, + link_id: String, + channel_id: ChannelId, + }, + AgentMessageReceived { + from_agent_id: AgentId, + to_agent_id: AgentId, + link_id: String, + channel_id: ChannelId, + }, } /// Shared dependency bundle for agent processes. @@ -184,6 +197,7 @@ pub struct AgentDeps { pub event_tx: tokio::sync::broadcast::Sender, pub sqlite_pool: sqlx::SqlitePool, pub messaging_manager: Option>, + pub link_store: Option>, } impl AgentDeps { diff --git a/src/links.rs b/src/links.rs new file mode 100644 index 000000000..803e41939 --- /dev/null +++ b/src/links.rs @@ -0,0 +1,7 @@ +//! Agent communication graph: links between agents with direction and relationship policies. + +pub mod store; +pub mod types; + +pub use store::LinkStore; +pub use types::{AgentLink, LinkDirection, LinkRelationship}; diff --git a/src/links/store.rs b/src/links/store.rs new file mode 100644 index 000000000..e795f7b08 --- /dev/null +++ b/src/links/store.rs @@ -0,0 +1,208 @@ +//! LinkStore: CRUD operations for the agent communication graph. + +use crate::error::Result; +use crate::links::types::{AgentLink, LinkDirection, LinkRelationship}; + +use anyhow::Context as _; +use chrono::Utc; +use sqlx::SqlitePool; + +/// Persistent store for agent links, backed by the instance-level SQLite database. +#[derive(Clone)] +pub struct LinkStore { + pool: SqlitePool, +} + +impl LinkStore { + pub fn new(pool: SqlitePool) -> Self { + Self { pool } + } + + /// Get all links involving this agent (as source or target). + pub async fn get_links_for_agent(&self, agent_id: &str) -> Result> { + let rows = sqlx::query_as::<_, LinkRow>( + "SELECT id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at + FROM agent_links + WHERE from_agent_id = ? OR to_agent_id = ? + ORDER BY created_at ASC", + ) + .bind(agent_id) + .bind(agent_id) + .fetch_all(&self.pool) + .await + .context("failed to fetch links for agent")?; + + rows.into_iter().map(LinkRow::into_link).collect() + } + + /// Get a specific link by ID. + pub async fn get(&self, link_id: &str) -> Result> { + let row = sqlx::query_as::<_, LinkRow>( + "SELECT id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at + FROM agent_links + WHERE id = ?", + ) + .bind(link_id) + .fetch_optional(&self.pool) + .await + .context("failed to fetch link by id")?; + + row.map(LinkRow::into_link).transpose() + } + + /// Get all links in the instance. + pub async fn get_all(&self) -> Result> { + let rows = sqlx::query_as::<_, LinkRow>( + "SELECT id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at + FROM agent_links + ORDER BY created_at ASC", + ) + .fetch_all(&self.pool) + .await + .context("failed to fetch all links")?; + + rows.into_iter().map(LinkRow::into_link).collect() + } + + /// Get the link between two specific agents (if any). + /// + /// Checks both directions: from_agent_id→to_agent_id and the reverse. + pub async fn get_between( + &self, + from_agent_id: &str, + to_agent_id: &str, + ) -> Result> { + let row = sqlx::query_as::<_, LinkRow>( + "SELECT id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at + FROM agent_links + WHERE (from_agent_id = ? AND to_agent_id = ?) + OR (from_agent_id = ? AND to_agent_id = ?) + LIMIT 1", + ) + .bind(from_agent_id) + .bind(to_agent_id) + .bind(to_agent_id) + .bind(from_agent_id) + .fetch_optional(&self.pool) + .await + .context("failed to fetch link between agents")?; + + row.map(LinkRow::into_link).transpose() + } + + /// Create a new link. Returns error if a link already exists between these agents. + pub async fn create(&self, link: &AgentLink) -> Result<()> { + sqlx::query( + "INSERT INTO agent_links (id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + ) + .bind(&link.id) + .bind(&link.from_agent_id) + .bind(&link.to_agent_id) + .bind(link.direction.as_str()) + .bind(link.relationship.as_str()) + .bind(link.enabled) + .bind(link.created_at) + .bind(link.updated_at) + .execute(&self.pool) + .await + .context("failed to create agent link")?; + + Ok(()) + } + + /// Update link properties (direction, relationship, enabled). + pub async fn update(&self, link: &AgentLink) -> Result<()> { + let now = Utc::now(); + sqlx::query( + "UPDATE agent_links + SET direction = ?, relationship = ?, enabled = ?, updated_at = ? + WHERE id = ?", + ) + .bind(link.direction.as_str()) + .bind(link.relationship.as_str()) + .bind(link.enabled) + .bind(now) + .bind(&link.id) + .execute(&self.pool) + .await + .context("failed to update agent link")?; + + Ok(()) + } + + /// Delete a link by ID. + pub async fn delete(&self, link_id: &str) -> Result<()> { + sqlx::query("DELETE FROM agent_links WHERE id = ?") + .bind(link_id) + .execute(&self.pool) + .await + .context("failed to delete agent link")?; + + Ok(()) + } + + /// Upsert a link from config. If a link between the same agents already exists, + /// update its direction and relationship to match config. + pub async fn upsert_from_config(&self, link: &AgentLink) -> Result<()> { + let now = Utc::now(); + sqlx::query( + "INSERT INTO agent_links (id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(from_agent_id, to_agent_id) + DO UPDATE SET direction = excluded.direction, + relationship = excluded.relationship, + enabled = excluded.enabled, + updated_at = ?", + ) + .bind(&link.id) + .bind(&link.from_agent_id) + .bind(&link.to_agent_id) + .bind(link.direction.as_str()) + .bind(link.relationship.as_str()) + .bind(link.enabled) + .bind(link.created_at) + .bind(link.updated_at) + .bind(now) + .execute(&self.pool) + .await + .context("failed to upsert agent link from config")?; + + Ok(()) + } +} + +/// Internal row type for sqlx deserialization. +#[derive(sqlx::FromRow)] +struct LinkRow { + id: String, + from_agent_id: String, + to_agent_id: String, + direction: String, + relationship: String, + enabled: bool, + created_at: chrono::DateTime, + updated_at: chrono::DateTime, +} + +impl LinkRow { + fn into_link(self) -> Result { + let direction: LinkDirection = self.direction.parse().map_err(|e: String| { + anyhow::anyhow!("invalid link direction in database: {e}") + })?; + let relationship: LinkRelationship = self.relationship.parse().map_err(|e: String| { + anyhow::anyhow!("invalid link relationship in database: {e}") + })?; + + Ok(AgentLink { + id: self.id, + from_agent_id: self.from_agent_id, + to_agent_id: self.to_agent_id, + direction, + relationship, + enabled: self.enabled, + created_at: self.created_at, + updated_at: self.updated_at, + }) + } +} diff --git a/src/links/types.rs b/src/links/types.rs new file mode 100644 index 000000000..73448ef06 --- /dev/null +++ b/src/links/types.rs @@ -0,0 +1,119 @@ +//! Types for the agent communication graph. + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// A directed edge in the agent communication graph. +/// +/// Represents a persistent, policy-governed communication channel between two agents. +/// When agent A has a link to agent B, agent A can send messages to agent B. +/// The link carries direction and relationship flags that define the nature +/// of the communication. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AgentLink { + pub id: String, + pub from_agent_id: String, + pub to_agent_id: String, + pub direction: LinkDirection, + pub relationship: LinkRelationship, + pub enabled: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// Direction policy for an agent link. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum LinkDirection { + /// from_agent can message to_agent, but not vice versa. + OneWay, + /// Both agents can message each other through this link. + TwoWay, +} + +impl LinkDirection { + pub fn as_str(&self) -> &'static str { + match self { + LinkDirection::OneWay => "one_way", + LinkDirection::TwoWay => "two_way", + } + } +} + +impl std::fmt::Display for LinkDirection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +impl std::str::FromStr for LinkDirection { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + match s { + "one_way" => Ok(LinkDirection::OneWay), + "two_way" => Ok(LinkDirection::TwoWay), + other => Err(format!( + "invalid link direction: '{other}', expected 'one_way' or 'two_way'" + )), + } + } +} + +/// Relationship semantics for an agent link. +/// +/// Affects the receiving agent's system prompt context. A superior can delegate tasks, +/// a subordinate reports status and escalates. Peers communicate collaboratively. +/// The relationship doesn't restrict message delivery — it frames context. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum LinkRelationship { + /// Equal peers — neither agent has authority over the other. + Peer, + /// from_agent is superior to to_agent. Can delegate tasks, + /// request status, and override decisions. + Superior, + /// from_agent is subordinate to to_agent. Reports status, + /// escalates issues, requests approval. + Subordinate, +} + +impl LinkRelationship { + pub fn as_str(&self) -> &'static str { + match self { + LinkRelationship::Peer => "peer", + LinkRelationship::Superior => "superior", + LinkRelationship::Subordinate => "subordinate", + } + } + + /// Get the inverse relationship from the other agent's perspective. + pub fn inverse(&self) -> Self { + match self { + LinkRelationship::Peer => LinkRelationship::Peer, + LinkRelationship::Superior => LinkRelationship::Subordinate, + LinkRelationship::Subordinate => LinkRelationship::Superior, + } + } +} + +impl std::fmt::Display for LinkRelationship { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +impl std::str::FromStr for LinkRelationship { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + match s { + "peer" => Ok(LinkRelationship::Peer), + "superior" => Ok(LinkRelationship::Superior), + "subordinate" => Ok(LinkRelationship::Subordinate), + other => Err(format!( + "invalid link relationship: '{other}', expected 'peer', 'superior', or 'subordinate'" + )), + } + } +} diff --git a/src/main.rs b/src/main.rs index 0b7fc6b4e..5cb1ecd9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -684,6 +684,43 @@ async fn run( let prompt_engine = spacebot::prompts::PromptEngine::new("en") .with_context(|| "failed to initialize prompt engine")?; + // Instance-level database for cross-agent data (agent links) + let instance_db = spacebot::db::InstanceDb::connect(&config.instance_dir) + .await + .with_context(|| "failed to connect instance database")?; + let link_store = Arc::new(spacebot::links::LinkStore::new(instance_db.sqlite.clone())); + + // Sync config-defined links to database + for link_def in &config.links { + use spacebot::links::types::{AgentLink, LinkDirection, LinkRelationship}; + let direction: LinkDirection = link_def.direction.parse().map_err(|e: String| { + anyhow::anyhow!("{e} (link {} → {})", link_def.from, link_def.to) + })?; + let relationship: LinkRelationship = + link_def.relationship.parse().map_err(|e: String| { + anyhow::anyhow!("{e} (link {} → {})", link_def.from, link_def.to) + })?; + let link = AgentLink { + id: uuid::Uuid::new_v4().to_string(), + from_agent_id: link_def.from.clone(), + to_agent_id: link_def.to.clone(), + direction, + relationship, + enabled: true, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + }; + link_store.upsert_from_config(&link).await.with_context(|| { + format!( + "failed to sync link {} → {} from config", + link_def.from, link_def.to + ) + })?; + } + if !config.links.is_empty() { + tracing::info!(count = config.links.len(), "synced config links to instance database"); + } + // These hold the initialized subsystems. Empty until agents are initialized. let mut agents: HashMap = HashMap::new(); let mut messaging_manager: Arc = @@ -707,6 +744,7 @@ async fn run( api_state.set_embedding_model(embedding_model.clone()).await; api_state.set_prompt_engine(prompt_engine.clone()).await; api_state.set_defaults_config(config.defaults.clone()).await; + api_state.set_link_store(link_store.clone()); // Track whether agents have been initialized let mut agents_initialized = false; @@ -738,6 +776,7 @@ async fn run( &mut slack_permissions, &mut telegram_permissions, &mut twitch_permissions, + Some(link_store.clone()), ) .await?; agents_initialized = true; @@ -1065,6 +1104,7 @@ async fn run( &mut new_slack_permissions, &mut new_telegram_permissions, &mut new_twitch_permissions, + Some(link_store.clone()), ).await { Ok(()) => { agents_initialized = true; @@ -1128,6 +1168,8 @@ async fn run( agent.db.close().await; } + instance_db.close().await; + tracing::info!("spacebot stopped"); // Flush buffered OTLP spans before the process exits. Without this the @@ -1172,6 +1214,7 @@ async fn initialize_agents( slack_permissions: &mut Option>>, telegram_permissions: &mut Option>>, twitch_permissions: &mut Option>>, + link_store: Option>, ) -> anyhow::Result<()> { let resolved_agents = config.resolve_agents(); @@ -1306,6 +1349,7 @@ async fn initialize_agents( event_tx, sqlite_pool: db.sqlite.clone(), messaging_manager: None, + link_store: link_store.clone(), }; let agent = spacebot::Agent { diff --git a/src/prompts/engine.rs b/src/prompts/engine.rs index 718e0ab57..d38926a2b 100644 --- a/src/prompts/engine.rs +++ b/src/prompts/engine.rs @@ -1,6 +1,6 @@ use crate::error::Result; use anyhow::Context; -use minijinja::{Environment, Value, context}; +use minijinja::{context, Environment, Value}; use std::collections::HashMap; use std::sync::Arc; @@ -76,6 +76,14 @@ impl PromptEngine { "fragments/available_channels", crate::prompts::text::get("fragments/available_channels"), )?; + env.add_template( + "fragments/org_context", + crate::prompts::text::get("fragments/org_context"), + )?; + env.add_template( + "fragments/link_context", + crate::prompts::text::get("fragments/link_context"), + )?; // System message fragments env.add_template( @@ -417,12 +425,86 @@ impl PromptEngine { ) } + /// Render the org context fragment showing the agent's position in the hierarchy. + pub fn render_org_context(&self, org_context: OrgContext) -> Result { + self.render( + "fragments/org_context", + context! { + org_context => org_context, + }, + ) + } + + /// Render the link context fragment for an internal agent-to-agent channel. + pub fn render_link_context(&self, link_context: LinkContext) -> Result { + self.render( + "fragments/link_context", + context! { + link_context => link_context, + }, + ) + } + + /// Render the channel system prompt with all dynamic components including org/link context. + #[allow(clippy::too_many_arguments)] + pub fn render_channel_prompt_with_links( + &self, + identity_context: Option, + memory_bulletin: Option, + skills_prompt: Option, + worker_capabilities: String, + conversation_context: Option, + status_text: Option, + coalesce_hint: Option, + available_channels: Option, + org_context: Option, + link_context: Option, + ) -> Result { + self.render( + "channel", + context! { + identity_context => identity_context, + memory_bulletin => memory_bulletin, + skills_prompt => skills_prompt, + worker_capabilities => worker_capabilities, + conversation_context => conversation_context, + status_text => status_text, + coalesce_hint => coalesce_hint, + available_channels => available_channels, + org_context => org_context, + link_context => link_context, + }, + ) + } + /// Get the configured language code. pub fn language(&self) -> &str { &self.language } } +/// Organizational context for an agent — grouped by relationship. +#[derive(Debug, Clone, serde::Serialize)] +pub struct OrgContext { + pub superiors: Vec, + pub subordinates: Vec, + pub peers: Vec, +} + +/// Information about a linked agent for prompt rendering. +#[derive(Debug, Clone, serde::Serialize)] +pub struct LinkedAgent { + pub name: String, + pub id: String, +} + +/// Context for the current link channel. +#[derive(Debug, Clone, serde::Serialize)] +pub struct LinkContext { + pub agent_name: String, + pub relationship: String, +} + /// Information about a skill for template rendering. #[derive(Debug, Clone, serde::Serialize)] pub struct SkillInfo { diff --git a/src/prompts/text.rs b/src/prompts/text.rs index d9614adae..ec1df307e 100644 --- a/src/prompts/text.rs +++ b/src/prompts/text.rs @@ -114,6 +114,14 @@ fn lookup(lang: &str, key: &str) -> &'static str { include_str!("../../prompts/en/fragments/system/tool_syntax_correction.md.j2") } + // Agent Communication Fragments + ("en", "fragments/org_context") => { + include_str!("../../prompts/en/fragments/org_context.md.j2") + } + ("en", "fragments/link_context") => { + include_str!("../../prompts/en/fragments/link_context.md.j2") + } + // Coalesce Hint ("en", "fragments/coalesce_hint") => { include_str!("../../prompts/en/fragments/coalesce_hint.md.j2") @@ -158,6 +166,9 @@ fn lookup(lang: &str, key: &str) -> &'static str { ("en", "tools/send_message_to_another_channel") => { include_str!("../../prompts/en/tools/send_message_description.md.j2") } + ("en", "tools/send_agent_message") => { + include_str!("../../prompts/en/tools/send_agent_message_description.md.j2") + } // Fallback: unknown language or key -> try English (lang, key) if lang != "en" => { diff --git a/src/tools.rs b/src/tools.rs index 0f8a8a777..968e5ab4b 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -35,6 +35,7 @@ pub mod memory_save; pub mod react; pub mod reply; pub mod route; +pub mod send_agent_message; pub mod send_file; pub mod send_message_to_another_channel; pub mod set_status; @@ -68,6 +69,9 @@ pub use memory_save::{ pub use react::{ReactArgs, ReactError, ReactOutput, ReactTool}; pub use reply::{RepliedFlag, ReplyArgs, ReplyError, ReplyOutput, ReplyTool, new_replied_flag}; pub use route::{RouteArgs, RouteError, RouteOutput, RouteTool}; +pub use send_agent_message::{ + SendAgentMessageArgs, SendAgentMessageError, SendAgentMessageOutput, SendAgentMessageTool, +}; pub use send_file::{SendFileArgs, SendFileError, SendFileOutput, SendFileTool}; pub use send_message_to_another_channel::{ SendMessageArgs, SendMessageError, SendMessageOutput, SendMessageTool, @@ -133,6 +137,7 @@ pub async fn add_channel_tools( skip_flag: SkipFlag, replied_flag: RepliedFlag, cron_tool: Option, + send_agent_message_tool: Option, ) -> Result<(), rig::tool::server::ToolServerError> { handle .add_tool(ReplyTool::new( @@ -165,6 +170,9 @@ pub async fn add_channel_tools( if let Some(cron) = cron_tool { handle.add_tool(cron).await?; } + if let Some(agent_msg) = send_agent_message_tool { + handle.add_tool(agent_msg).await?; + } Ok(()) } @@ -183,9 +191,10 @@ pub async fn remove_channel_tools( handle.remove_tool(SkipTool::NAME).await?; handle.remove_tool(SendFileTool::NAME).await?; handle.remove_tool(ReactTool::NAME).await?; - // Cron and send_message removal is best-effort since not all channels have them + // Cron, send_message, and send_agent_message removal is best-effort since not all channels have them let _ = handle.remove_tool(CronTool::NAME).await; let _ = handle.remove_tool(SendMessageTool::NAME).await; + let _ = handle.remove_tool(SendAgentMessageTool::NAME).await; Ok(()) } diff --git a/src/tools/send_agent_message.rs b/src/tools/send_agent_message.rs new file mode 100644 index 000000000..8a01617a0 --- /dev/null +++ b/src/tools/send_agent_message.rs @@ -0,0 +1,256 @@ +//! Send message to another agent through the communication graph. + +use crate::links::LinkStore; +use crate::messaging::MessagingManager; +use crate::{AgentId, InboundMessage, MessageContent, ProcessEvent}; + +use chrono::Utc; +use rig::completion::ToolDefinition; +use rig::tool::Tool; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::broadcast; + +/// Tool for sending messages to other agents through the agent communication graph. +/// +/// Resolves the target agent by ID or name, validates the link exists and permits +/// messaging in this direction, constructs an `InboundMessage` with source "internal", +/// and delivers via `MessagingManager::inject_message()`. +#[derive(Clone)] +pub struct SendAgentMessageTool { + agent_id: AgentId, + agent_name: String, + link_store: Arc, + messaging_manager: Arc, + event_tx: broadcast::Sender, + /// Map of known agent IDs to display names, for resolving targets. + agent_names: Arc>, +} + +impl std::fmt::Debug for SendAgentMessageTool { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SendAgentMessageTool") + .field("agent_id", &self.agent_id) + .finish_non_exhaustive() + } +} + +impl SendAgentMessageTool { + pub fn new( + agent_id: AgentId, + agent_name: String, + link_store: Arc, + messaging_manager: Arc, + event_tx: broadcast::Sender, + agent_names: Arc>, + ) -> Self { + Self { + agent_id, + agent_name, + link_store, + messaging_manager, + event_tx, + agent_names, + } + } +} + +/// Error type for send_agent_message tool. +#[derive(Debug, thiserror::Error)] +#[error("SendAgentMessage failed: {0}")] +pub struct SendAgentMessageError(String); + +/// Arguments for send_agent_message tool. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct SendAgentMessageArgs { + /// Target agent ID or name. + pub target: String, + /// The message content to send. + pub message: String, +} + +/// Output from send_agent_message tool. +#[derive(Debug, Serialize)] +pub struct SendAgentMessageOutput { + pub success: bool, + pub target_agent: String, + pub link_id: String, +} + +impl Tool for SendAgentMessageTool { + const NAME: &'static str = "send_agent_message"; + + type Error = SendAgentMessageError; + type Args = SendAgentMessageArgs; + type Output = SendAgentMessageOutput; + + async fn definition(&self, _prompt: String) -> ToolDefinition { + ToolDefinition { + name: Self::NAME.to_string(), + description: crate::prompts::text::get("tools/send_agent_message").to_string(), + parameters: serde_json::json!({ + "type": "object", + "properties": { + "target": { + "type": "string", + "description": "The target agent's ID or name." + }, + "message": { + "type": "string", + "description": "The message content to send to the target agent." + } + }, + "required": ["target", "message"] + }), + } + } + + async fn call(&self, args: Self::Args) -> Result { + tracing::info!( + from = %self.agent_id, + target = %args.target, + message_len = args.message.len(), + "send_agent_message tool called" + ); + + // Resolve target agent ID (could be name or ID) + let target_agent_id = self + .resolve_agent_id(&args.target) + .ok_or_else(|| { + SendAgentMessageError(format!( + "unknown agent '{}'. Check your organization context for available agents.", + args.target + )) + })?; + + // Look up the link between sending agent and target + let link = self + .link_store + .get_between(&self.agent_id, &target_agent_id) + .await + .map_err(|error| { + SendAgentMessageError(format!("failed to look up link: {error}")) + })? + .ok_or_else(|| { + SendAgentMessageError(format!( + "no communication link exists between you and agent '{}'.", + args.target + )) + })?; + + if !link.enabled { + return Err(SendAgentMessageError(format!( + "the link to agent '{}' is currently disabled.", + args.target + ))); + } + + // Check direction: if the link is one_way, only from_agent can initiate + let sending_agent_id = self.agent_id.as_ref(); + let is_from_agent = link.from_agent_id == sending_agent_id; + let is_to_agent = link.to_agent_id == sending_agent_id; + + if link.direction == crate::links::LinkDirection::OneWay && is_to_agent { + return Err(SendAgentMessageError(format!( + "the link to agent '{}' is one-way and you cannot initiate messages.", + args.target + ))); + } + + // Determine the receiving agent and the relationship from sender's perspective + let receiving_agent_id = if is_from_agent { + &link.to_agent_id + } else { + &link.from_agent_id + }; + + let relationship = if is_from_agent { + link.relationship + } else { + link.relationship.inverse() + }; + + let target_agent_arc: AgentId = Arc::from(receiving_agent_id.as_str()); + let conversation_id = format!("link:{}", link.id); + + // Construct the internal message + let message = InboundMessage { + id: uuid::Uuid::new_v4().to_string(), + source: "internal".into(), + conversation_id: conversation_id.clone(), + sender_id: sending_agent_id.to_string(), + agent_id: Some(target_agent_arc), + content: MessageContent::Text(args.message), + timestamp: Utc::now(), + metadata: HashMap::from([ + ("link_id".into(), serde_json::json!(link.id)), + ("from_agent_id".into(), serde_json::json!(sending_agent_id)), + ( + "relationship".into(), + serde_json::json!(relationship.as_str()), + ), + ]), + formatted_author: Some(format!("[{}]", self.agent_name)), + }; + + // Inject into the messaging pipeline + self.messaging_manager + .inject_message(message) + .await + .map_err(|error| { + SendAgentMessageError(format!("failed to deliver message: {error}")) + })?; + + // Emit process event for dashboard visibility + self.event_tx + .send(ProcessEvent::AgentMessageSent { + from_agent_id: self.agent_id.clone(), + to_agent_id: Arc::from(receiving_agent_id.as_str()), + link_id: link.id.clone(), + channel_id: Arc::from(conversation_id.as_str()), + }) + .ok(); + + let target_display = self + .agent_names + .get(receiving_agent_id) + .cloned() + .unwrap_or_else(|| receiving_agent_id.to_string()); + + tracing::info!( + from = %self.agent_id, + to = %receiving_agent_id, + link_id = %link.id, + "agent message sent" + ); + + Ok(SendAgentMessageOutput { + success: true, + target_agent: target_display, + link_id: link.id, + }) + } +} + +impl SendAgentMessageTool { + /// Resolve an agent target string to an agent ID. + /// Checks both IDs and display names. + fn resolve_agent_id(&self, target: &str) -> Option { + // Direct ID match + if self.agent_names.contains_key(target) { + return Some(target.to_string()); + } + + // Name match (case-insensitive) + let target_lower = target.to_lowercase(); + for (agent_id, name) in self.agent_names.iter() { + if name.to_lowercase() == target_lower { + return Some(agent_id.clone()); + } + } + + None + } +} From 326482a9e158fbc081a6b84468815b7a02c9b241 Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sun, 22 Feb 2026 13:58:32 -0800 Subject: [PATCH 02/12] Strip instance.db, back links with config Links are defined in [[links]] config sections and stored in a shared ArcSwap> that hot-reloads when config.toml changes. No separate database needed since config is already runtime-mutable through the API. - Remove InstanceDb, migrations_instance/, LinkStore - Links parsed from config at startup via AgentLink::from_config() - Shared via Arc>> passed to agents and API - File watcher reloads links on config.toml changes - API endpoints simplified to read-only (list, agent links, topology) - Deterministic link channel IDs from sorted agent ID pairs - ROLE.md added to file watcher identity change detection --- .../20260222000001_agent_links.sql | 14 - src/agent/channel.rs | 52 ++- src/api/agents.rs | 2 +- src/api/links.rs | 295 ++---------------- src/api/server.rs | 11 +- src/api/state.rs | 12 +- src/config.rs | 13 +- src/db.rs | 29 -- src/lib.rs | 2 +- src/links.rs | 27 +- src/links/store.rs | 208 ------------ src/links/types.rs | 43 ++- src/main.rs | 54 +--- src/tools/send_agent_message.rs | 53 ++-- tests/bulletin.rs | 1 + tests/context_dump.rs | 3 + 16 files changed, 155 insertions(+), 664 deletions(-) delete mode 100644 migrations_instance/20260222000001_agent_links.sql delete mode 100644 src/links/store.rs diff --git a/migrations_instance/20260222000001_agent_links.sql b/migrations_instance/20260222000001_agent_links.sql deleted file mode 100644 index 97f22cd5d..000000000 --- a/migrations_instance/20260222000001_agent_links.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE IF NOT EXISTS agent_links ( - id TEXT PRIMARY KEY, - from_agent_id TEXT NOT NULL, - to_agent_id TEXT NOT NULL, - direction TEXT NOT NULL DEFAULT 'two_way', - relationship TEXT NOT NULL DEFAULT 'peer', - enabled INTEGER NOT NULL DEFAULT 1, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - UNIQUE(from_agent_id, to_agent_id) -); - -CREATE INDEX IF NOT EXISTS idx_agent_links_from ON agent_links(from_agent_id); -CREATE INDEX IF NOT EXISTS idx_agent_links_to ON agent_links(to_agent_id); diff --git a/src/agent/channel.rs b/src/agent/channel.rs index 5054fa6ff..70b2f2f4e 100644 --- a/src/agent/channel.rs +++ b/src/agent/channel.rs @@ -626,8 +626,8 @@ impl Channel { let available_channels = self.build_available_channels().await; - let org_context = self.build_org_context(&prompt_engine).await; - let link_context = self.build_link_context(&prompt_engine).await; + let org_context = self.build_org_context(&prompt_engine); + let link_context = self.build_link_context(&prompt_engine); let empty_to_none = |s: String| if s.is_empty() { None } else { Some(s) }; @@ -817,20 +817,13 @@ impl Channel { } /// Build org context showing the agent's position in the communication hierarchy. - async fn build_org_context( + fn build_org_context( &self, prompt_engine: &crate::prompts::PromptEngine, ) -> Option { - let link_store = self.deps.link_store.as_ref()?; let agent_id = self.deps.agent_id.as_ref(); - - let links = match link_store.get_links_for_agent(agent_id).await { - Ok(links) => links, - Err(error) => { - tracing::warn!(%error, "failed to fetch links for org context"); - return None; - } - }; + let all_links = self.deps.links.load(); + let links = crate::links::links_for_agent(&all_links, agent_id); if links.is_empty() { return None; @@ -841,10 +834,6 @@ impl Channel { let mut peers = Vec::new(); for link in &links { - if !link.enabled { - continue; - } - let is_from = link.from_agent_id == agent_id; let other_agent_id = if is_from { &link.to_agent_id @@ -852,15 +841,12 @@ impl Channel { &link.from_agent_id }; - // Determine relationship from this agent's perspective let relationship = if is_from { link.relationship } else { link.relationship.inverse() }; - // For one-way links where this agent is the to_agent (can't initiate), - // still show them in the org context for awareness let agent_info = crate::prompts::engine::LinkedAgent { name: other_agent_id.clone(), id: other_agent_id.clone(), @@ -887,25 +873,24 @@ impl Channel { } /// Build link context for the current channel if it's an internal agent-to-agent channel. - async fn build_link_context( + fn build_link_context( &self, prompt_engine: &crate::prompts::PromptEngine, ) -> Option { // Link channels have conversation IDs starting with "link:" let conversation_id = self.conversation_id.as_deref()?; - let link_id = conversation_id.strip_prefix("link:")?; - - let link_store = self.deps.link_store.as_ref()?; - let link = match link_store.get(link_id).await { - Ok(Some(link)) => link, - Ok(None) => return None, - Err(error) => { - tracing::warn!(%error, "failed to fetch link for channel context"); - return None; - } - }; + if !conversation_id.starts_with("link:") { + return None; + } let agent_id = self.deps.agent_id.as_ref(); + let all_links = self.deps.links.load(); + + // Find the link that matches this channel's conversation ID + let link = all_links + .iter() + .find(|link| link.channel_id() == conversation_id)?; + let is_from = link.from_agent_id == agent_id; let other_agent_id = if is_from { &link.to_agent_id @@ -913,7 +898,6 @@ impl Channel { &link.from_agent_id }; - // Relationship from the other agent's perspective toward this agent let relationship = if is_from { link.relationship } else { @@ -952,8 +936,8 @@ impl Channel { let available_channels = self.build_available_channels().await; - let org_context = self.build_org_context(&prompt_engine).await; - let link_context = self.build_link_context(&prompt_engine).await; + let org_context = self.build_org_context(&prompt_engine); + let link_context = self.build_link_context(&prompt_engine); let empty_to_none = |s: String| if s.is_empty() { None } else { Some(s) }; diff --git a/src/api/agents.rs b/src/api/agents.rs index ba87893aa..3bbf40a1c 100644 --- a/src/api/agents.rs +++ b/src/api/agents.rs @@ -432,7 +432,7 @@ pub(super) async fn create_agent( let guard = state.messaging_manager.read().await; guard.as_ref().cloned() }, - link_store: state.link_store.load().as_ref().clone(), + links: Arc::new(arc_swap::ArcSwap::from_pointee((**state.agent_links.load()).clone())), }; let event_rx = event_tx.subscribe(); diff --git a/src/api/links.rs b/src/api/links.rs index 29e190911..e1d2a57bb 100644 --- a/src/api/links.rs +++ b/src/api/links.rs @@ -1,237 +1,17 @@ -//! API handlers for agent link CRUD and topology. +//! API handlers for agent links and topology. use crate::api::state::ApiState; -use crate::links::types::{AgentLink, LinkDirection, LinkRelationship}; use axum::Json; use axum::extract::{Path, State}; -use axum::http::StatusCode; use axum::response::IntoResponse; -use chrono::Utc; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use std::sync::Arc; /// List all links in the instance. pub async fn list_links(State(state): State>) -> impl IntoResponse { - let Some(link_store) = get_link_store(&state) else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(serde_json::json!({"error": "link store not initialized"})), - ) - .into_response(); - }; - - match link_store.get_all().await { - Ok(links) => Json(serde_json::json!({ "links": links })).into_response(), - Err(error) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({"error": error.to_string()})), - ) - .into_response(), - } -} - -/// Get a specific link by ID. -pub async fn get_link( - State(state): State>, - Path(link_id): Path, -) -> impl IntoResponse { - let Some(link_store) = get_link_store(&state) else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(serde_json::json!({"error": "link store not initialized"})), - ) - .into_response(); - }; - - match link_store.get(&link_id).await { - Ok(Some(link)) => Json(link).into_response(), - Ok(None) => ( - StatusCode::NOT_FOUND, - Json(serde_json::json!({"error": "link not found"})), - ) - .into_response(), - Err(error) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({"error": error.to_string()})), - ) - .into_response(), - } -} - -/// Request body for creating a link. -#[derive(Debug, Deserialize)] -pub struct CreateLinkRequest { - pub from_agent_id: String, - pub to_agent_id: String, - #[serde(default = "default_direction")] - pub direction: String, - #[serde(default = "default_relationship")] - pub relationship: String, -} - -fn default_direction() -> String { - "two_way".into() -} -fn default_relationship() -> String { - "peer".into() -} - -/// Create a new link between agents. -pub async fn create_link( - State(state): State>, - Json(body): Json, -) -> impl IntoResponse { - let Some(link_store) = get_link_store(&state) else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(serde_json::json!({"error": "link store not initialized"})), - ) - .into_response(); - }; - - let direction: LinkDirection = match body.direction.parse() { - Ok(d) => d, - Err(error) => { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({"error": error})), - ) - .into_response(); - } - }; - let relationship: LinkRelationship = match body.relationship.parse() { - Ok(r) => r, - Err(error) => { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({"error": error})), - ) - .into_response(); - } - }; - - let now = Utc::now(); - let link = AgentLink { - id: uuid::Uuid::new_v4().to_string(), - from_agent_id: body.from_agent_id, - to_agent_id: body.to_agent_id, - direction, - relationship, - enabled: true, - created_at: now, - updated_at: now, - }; - - match link_store.create(&link).await { - Ok(()) => (StatusCode::CREATED, Json(link)).into_response(), - Err(error) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({"error": error.to_string()})), - ) - .into_response(), - } -} - -/// Request body for updating a link. -#[derive(Debug, Deserialize)] -pub struct UpdateLinkRequest { - pub direction: Option, - pub relationship: Option, - pub enabled: Option, -} - -/// Update a link's properties. -pub async fn update_link( - State(state): State>, - Path(link_id): Path, - Json(body): Json, -) -> impl IntoResponse { - let Some(link_store) = get_link_store(&state) else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(serde_json::json!({"error": "link store not initialized"})), - ) - .into_response(); - }; - - let mut link = match link_store.get(&link_id).await { - Ok(Some(link)) => link, - Ok(None) => { - return ( - StatusCode::NOT_FOUND, - Json(serde_json::json!({"error": "link not found"})), - ) - .into_response(); - } - Err(error) => { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({"error": error.to_string()})), - ) - .into_response(); - } - }; - - if let Some(direction) = body.direction { - link.direction = match direction.parse() { - Ok(d) => d, - Err(error) => { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({"error": error})), - ) - .into_response(); - } - }; - } - if let Some(relationship) = body.relationship { - link.relationship = match relationship.parse() { - Ok(r) => r, - Err(error) => { - return ( - StatusCode::BAD_REQUEST, - Json(serde_json::json!({"error": error})), - ) - .into_response(); - } - }; - } - if let Some(enabled) = body.enabled { - link.enabled = enabled; - } - - match link_store.update(&link).await { - Ok(()) => Json(link).into_response(), - Err(error) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({"error": error.to_string()})), - ) - .into_response(), - } -} - -/// Delete a link by ID. -pub async fn delete_link( - State(state): State>, - Path(link_id): Path, -) -> impl IntoResponse { - let Some(link_store) = get_link_store(&state) else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(serde_json::json!({"error": "link store not initialized"})), - ) - .into_response(); - }; - - match link_store.delete(&link_id).await { - Ok(()) => Json(serde_json::json!({"deleted": true})).into_response(), - Err(error) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({"error": error.to_string()})), - ) - .into_response(), - } + let links = state.agent_links.load(); + Json(serde_json::json!({ "links": &**links })) } /// Get links for a specific agent. @@ -239,22 +19,9 @@ pub async fn agent_links( State(state): State>, Path(agent_id): Path, ) -> impl IntoResponse { - let Some(link_store) = get_link_store(&state) else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(serde_json::json!({"error": "link store not initialized"})), - ) - .into_response(); - }; - - match link_store.get_links_for_agent(&agent_id).await { - Ok(links) => Json(serde_json::json!({ "links": links })).into_response(), - Err(error) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({"error": error.to_string()})), - ) - .into_response(), - } + let all_links = state.agent_links.load(); + let links: Vec<_> = crate::links::links_for_agent(&all_links, &agent_id); + Json(serde_json::json!({ "links": links })) } /// Topology response for graph rendering. @@ -272,24 +39,14 @@ struct TopologyAgent { #[derive(Debug, Serialize)] struct TopologyLink { - id: String, from: String, to: String, direction: String, relationship: String, - enabled: bool, } /// Get the full agent topology for graph rendering. pub async fn topology(State(state): State>) -> impl IntoResponse { - let Some(link_store) = get_link_store(&state) else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(serde_json::json!({"error": "link store not initialized"})), - ) - .into_response(); - }; - let agent_configs = state.agent_configs.load(); let agents: Vec = agent_configs .iter() @@ -299,32 +56,16 @@ pub async fn topology(State(state): State>) -> impl IntoResponse { }) .collect(); - let links = match link_store.get_all().await { - Ok(links) => links - .into_iter() - .map(|link| TopologyLink { - id: link.id, - from: link.from_agent_id, - to: link.to_agent_id, - direction: link.direction.as_str().to_string(), - relationship: link.relationship.as_str().to_string(), - enabled: link.enabled, - }) - .collect(), - Err(error) => { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(serde_json::json!({"error": error.to_string()})), - ) - .into_response(); - } - }; - - Json(TopologyResponse { agents, links }).into_response() -} + let all_links = state.agent_links.load(); + let links: Vec = all_links + .iter() + .map(|link| TopologyLink { + from: link.from_agent_id.clone(), + to: link.to_agent_id.clone(), + direction: link.direction.as_str().to_string(), + relationship: link.relationship.as_str().to_string(), + }) + .collect(); -/// Extract the link store from API state. -fn get_link_store(state: &ApiState) -> Option> { - let guard = state.link_store.load(); - guard.as_ref().clone() + Json(TopologyResponse { agents, links }) } diff --git a/src/api/server.rs b/src/api/server.rs index 45e69ad5b..ce3b3db54 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -148,16 +148,7 @@ pub async fn start_http_server( .route("/update/apply", post(settings::update_apply)) .route("/webchat/send", post(webchat::webchat_send)) .route("/webchat/history", get(webchat::webchat_history)) - .route( - "/links", - get(links::list_links).post(links::create_link), - ) - .route( - "/links/{id}", - get(links::get_link) - .put(links::update_link) - .delete(links::delete_link), - ) + .route("/links", get(links::list_links)) .route("/agents/{id}/links", get(links::agent_links)) .route("/topology", get(links::topology)) .layer(middleware::from_fn_with_state( diff --git a/src/api/state.rs b/src/api/state.rs index b9b6c5a17..e3b5d7505 100644 --- a/src/api/state.rs +++ b/src/api/state.rs @@ -93,8 +93,8 @@ pub struct ApiState { pub agent_remove_tx: mpsc::Sender, /// Shared webchat adapter for session management from API handlers. pub webchat_adapter: ArcSwap>>, - /// Instance-level link store for agent communication graph. - pub link_store: ArcSwap>>, + /// Instance-level agent links for the communication graph. + pub agent_links: ArcSwap>, } /// Events sent to SSE clients. Wraps ProcessEvents with agent context. @@ -227,7 +227,7 @@ impl ApiState { agent_tx, agent_remove_tx, webchat_adapter: ArcSwap::from_pointee(None), - link_store: ArcSwap::from_pointee(None), + agent_links: ArcSwap::from_pointee(Vec::new()), } } @@ -526,9 +526,9 @@ impl ApiState { self.webchat_adapter.store(Arc::new(Some(adapter))); } - /// Set the link store for the agent communication graph. - pub fn set_link_store(&self, store: Arc) { - self.link_store.store(Arc::new(Some(store))); + /// Set the agent links for the communication graph. + pub fn set_agent_links(&self, links: Vec) { + self.agent_links.store(Arc::new(links)); } /// Send an event to all SSE subscribers. diff --git a/src/config.rs b/src/config.rs index 78f3b24c0..6c79cdbae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3050,6 +3050,7 @@ pub fn spawn_file_watcher( bindings: Arc>>, messaging_manager: Option>, llm_manager: Arc, + agent_links: Arc>>, ) -> tokio::task::JoinHandle<()> { use notify::{Event, RecursiveMode, Watcher}; use std::time::Duration; @@ -3139,7 +3140,7 @@ pub fn spawn_file_watcher( let mut config_changed = changed_paths.iter().any(|p| p.ends_with("config.toml")); let identity_changed = changed_paths.iter().any(|p| { let name = p.file_name().and_then(|n| n.to_str()).unwrap_or(""); - matches!(name, "SOUL.md" | "IDENTITY.md" | "USER.md") + matches!(name, "SOUL.md" | "IDENTITY.md" | "USER.md" | "ROLE.md") }); let skills_changed = changed_paths .iter() @@ -3205,6 +3206,16 @@ pub fn spawn_file_watcher( bindings.store(Arc::new(config.bindings.clone())); tracing::info!("bindings reloaded ({} entries)", config.bindings.len()); + match crate::links::AgentLink::from_config(&config.links) { + Ok(links) => { + agent_links.store(Arc::new(links)); + tracing::info!("agent links reloaded ({} entries)", config.links.len()); + } + Err(error) => { + tracing::error!(%error, "failed to parse links from reloaded config"); + } + } + if let Some(ref perms) = discord_permissions && let Some(discord_config) = &config.messaging.discord { diff --git a/src/db.rs b/src/db.rs index 306690dff..67011e7dd 100644 --- a/src/db.rs +++ b/src/db.rs @@ -67,33 +67,4 @@ impl Db { } } -/// Instance-level database for cross-agent data (agent links, shared notes). -/// -/// Separate from per-agent databases because links span agents and need -/// a shared home that both agents can reference. -pub struct InstanceDb { - pub sqlite: SqlitePool, -} - -impl InstanceDb { - /// Connect to the instance database and run instance-level migrations. - pub async fn connect(instance_dir: &Path) -> Result { - let db_path = instance_dir.join("instance.db"); - let sqlite_url = format!("sqlite:{}?mode=rwc", db_path.display()); - let sqlite = SqlitePool::connect(&sqlite_url) - .await - .with_context(|| format!("failed to connect to instance.db at {}", db_path.display()))?; - - sqlx::migrate!("./migrations_instance") - .run(&sqlite) - .await - .with_context(|| "failed to run instance database migrations")?; - - Ok(Self { sqlite }) - } - /// Close the instance database connection. - pub async fn close(self) { - self.sqlite.close().await; - } -} diff --git a/src/lib.rs b/src/lib.rs index 9bd50f55e..0ae295fb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -197,7 +197,7 @@ pub struct AgentDeps { pub event_tx: tokio::sync::broadcast::Sender, pub sqlite_pool: sqlx::SqlitePool, pub messaging_manager: Option>, - pub link_store: Option>, + pub links: Arc>>, } impl AgentDeps { diff --git a/src/links.rs b/src/links.rs index 803e41939..77c6940c4 100644 --- a/src/links.rs +++ b/src/links.rs @@ -1,7 +1,28 @@ -//! Agent communication graph: links between agents with direction and relationship policies. +//! Agent communication graph: directed links between agents with relationship policies. +//! +//! Links are defined in config via `[[links]]` sections and stored as a shared +//! `ArcSwap>` that's hot-reloadable when config changes. -pub mod store; pub mod types; -pub use store::LinkStore; pub use types::{AgentLink, LinkDirection, LinkRelationship}; + +/// Find the link between two agents (checking both directions). +pub fn find_link_between<'a>( + links: &'a [AgentLink], + agent_a: &str, + agent_b: &str, +) -> Option<&'a AgentLink> { + links.iter().find(|link| { + (link.from_agent_id == agent_a && link.to_agent_id == agent_b) + || (link.from_agent_id == agent_b && link.to_agent_id == agent_a) + }) +} + +/// Get all links involving a specific agent. +pub fn links_for_agent<'a>(links: &'a [AgentLink], agent_id: &str) -> Vec<&'a AgentLink> { + links + .iter() + .filter(|link| link.from_agent_id == agent_id || link.to_agent_id == agent_id) + .collect() +} diff --git a/src/links/store.rs b/src/links/store.rs deleted file mode 100644 index e795f7b08..000000000 --- a/src/links/store.rs +++ /dev/null @@ -1,208 +0,0 @@ -//! LinkStore: CRUD operations for the agent communication graph. - -use crate::error::Result; -use crate::links::types::{AgentLink, LinkDirection, LinkRelationship}; - -use anyhow::Context as _; -use chrono::Utc; -use sqlx::SqlitePool; - -/// Persistent store for agent links, backed by the instance-level SQLite database. -#[derive(Clone)] -pub struct LinkStore { - pool: SqlitePool, -} - -impl LinkStore { - pub fn new(pool: SqlitePool) -> Self { - Self { pool } - } - - /// Get all links involving this agent (as source or target). - pub async fn get_links_for_agent(&self, agent_id: &str) -> Result> { - let rows = sqlx::query_as::<_, LinkRow>( - "SELECT id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at - FROM agent_links - WHERE from_agent_id = ? OR to_agent_id = ? - ORDER BY created_at ASC", - ) - .bind(agent_id) - .bind(agent_id) - .fetch_all(&self.pool) - .await - .context("failed to fetch links for agent")?; - - rows.into_iter().map(LinkRow::into_link).collect() - } - - /// Get a specific link by ID. - pub async fn get(&self, link_id: &str) -> Result> { - let row = sqlx::query_as::<_, LinkRow>( - "SELECT id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at - FROM agent_links - WHERE id = ?", - ) - .bind(link_id) - .fetch_optional(&self.pool) - .await - .context("failed to fetch link by id")?; - - row.map(LinkRow::into_link).transpose() - } - - /// Get all links in the instance. - pub async fn get_all(&self) -> Result> { - let rows = sqlx::query_as::<_, LinkRow>( - "SELECT id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at - FROM agent_links - ORDER BY created_at ASC", - ) - .fetch_all(&self.pool) - .await - .context("failed to fetch all links")?; - - rows.into_iter().map(LinkRow::into_link).collect() - } - - /// Get the link between two specific agents (if any). - /// - /// Checks both directions: from_agent_id→to_agent_id and the reverse. - pub async fn get_between( - &self, - from_agent_id: &str, - to_agent_id: &str, - ) -> Result> { - let row = sqlx::query_as::<_, LinkRow>( - "SELECT id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at - FROM agent_links - WHERE (from_agent_id = ? AND to_agent_id = ?) - OR (from_agent_id = ? AND to_agent_id = ?) - LIMIT 1", - ) - .bind(from_agent_id) - .bind(to_agent_id) - .bind(to_agent_id) - .bind(from_agent_id) - .fetch_optional(&self.pool) - .await - .context("failed to fetch link between agents")?; - - row.map(LinkRow::into_link).transpose() - } - - /// Create a new link. Returns error if a link already exists between these agents. - pub async fn create(&self, link: &AgentLink) -> Result<()> { - sqlx::query( - "INSERT INTO agent_links (id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - ) - .bind(&link.id) - .bind(&link.from_agent_id) - .bind(&link.to_agent_id) - .bind(link.direction.as_str()) - .bind(link.relationship.as_str()) - .bind(link.enabled) - .bind(link.created_at) - .bind(link.updated_at) - .execute(&self.pool) - .await - .context("failed to create agent link")?; - - Ok(()) - } - - /// Update link properties (direction, relationship, enabled). - pub async fn update(&self, link: &AgentLink) -> Result<()> { - let now = Utc::now(); - sqlx::query( - "UPDATE agent_links - SET direction = ?, relationship = ?, enabled = ?, updated_at = ? - WHERE id = ?", - ) - .bind(link.direction.as_str()) - .bind(link.relationship.as_str()) - .bind(link.enabled) - .bind(now) - .bind(&link.id) - .execute(&self.pool) - .await - .context("failed to update agent link")?; - - Ok(()) - } - - /// Delete a link by ID. - pub async fn delete(&self, link_id: &str) -> Result<()> { - sqlx::query("DELETE FROM agent_links WHERE id = ?") - .bind(link_id) - .execute(&self.pool) - .await - .context("failed to delete agent link")?; - - Ok(()) - } - - /// Upsert a link from config. If a link between the same agents already exists, - /// update its direction and relationship to match config. - pub async fn upsert_from_config(&self, link: &AgentLink) -> Result<()> { - let now = Utc::now(); - sqlx::query( - "INSERT INTO agent_links (id, from_agent_id, to_agent_id, direction, relationship, enabled, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(from_agent_id, to_agent_id) - DO UPDATE SET direction = excluded.direction, - relationship = excluded.relationship, - enabled = excluded.enabled, - updated_at = ?", - ) - .bind(&link.id) - .bind(&link.from_agent_id) - .bind(&link.to_agent_id) - .bind(link.direction.as_str()) - .bind(link.relationship.as_str()) - .bind(link.enabled) - .bind(link.created_at) - .bind(link.updated_at) - .bind(now) - .execute(&self.pool) - .await - .context("failed to upsert agent link from config")?; - - Ok(()) - } -} - -/// Internal row type for sqlx deserialization. -#[derive(sqlx::FromRow)] -struct LinkRow { - id: String, - from_agent_id: String, - to_agent_id: String, - direction: String, - relationship: String, - enabled: bool, - created_at: chrono::DateTime, - updated_at: chrono::DateTime, -} - -impl LinkRow { - fn into_link(self) -> Result { - let direction: LinkDirection = self.direction.parse().map_err(|e: String| { - anyhow::anyhow!("invalid link direction in database: {e}") - })?; - let relationship: LinkRelationship = self.relationship.parse().map_err(|e: String| { - anyhow::anyhow!("invalid link relationship in database: {e}") - })?; - - Ok(AgentLink { - id: self.id, - from_agent_id: self.from_agent_id, - to_agent_id: self.to_agent_id, - direction, - relationship, - enabled: self.enabled, - created_at: self.created_at, - updated_at: self.updated_at, - }) - } -} diff --git a/src/links/types.rs b/src/links/types.rs index 73448ef06..1eb6192c1 100644 --- a/src/links/types.rs +++ b/src/links/types.rs @@ -1,24 +1,55 @@ //! Types for the agent communication graph. -use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; /// A directed edge in the agent communication graph. /// -/// Represents a persistent, policy-governed communication channel between two agents. +/// Represents a policy-governed communication channel between two agents. /// When agent A has a link to agent B, agent A can send messages to agent B. /// The link carries direction and relationship flags that define the nature /// of the communication. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentLink { - pub id: String, pub from_agent_id: String, pub to_agent_id: String, pub direction: LinkDirection, pub relationship: LinkRelationship, - pub enabled: bool, - pub created_at: DateTime, - pub updated_at: DateTime, +} + +impl AgentLink { + /// Parse config link definitions into agent links. + pub fn from_config(defs: &[crate::config::LinkDef]) -> anyhow::Result> { + defs.iter() + .map(|def| { + let direction: LinkDirection = def + .direction + .parse() + .map_err(|e: String| anyhow::anyhow!("{e} (link {} → {})", def.from, def.to))?; + let relationship: LinkRelationship = def + .relationship + .parse() + .map_err(|e: String| anyhow::anyhow!("{e} (link {} → {})", def.from, def.to))?; + Ok(AgentLink { + from_agent_id: def.from.clone(), + to_agent_id: def.to.clone(), + direction, + relationship, + }) + }) + .collect() + } + + /// Stable identifier for the link channel conversation ID. + /// Deterministic from agent IDs so the same link always maps to the same channel. + pub fn channel_id(&self) -> String { + // Sort agent IDs to ensure the same pair always produces the same channel + let (a, b) = if self.from_agent_id <= self.to_agent_id { + (&self.from_agent_id, &self.to_agent_id) + } else { + (&self.to_agent_id, &self.from_agent_id) + }; + format!("link:{a}:{b}") + } } /// Direction policy for an agent link. diff --git a/src/main.rs b/src/main.rs index 5cb1ecd9a..fc68b7a24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -684,41 +684,12 @@ async fn run( let prompt_engine = spacebot::prompts::PromptEngine::new("en") .with_context(|| "failed to initialize prompt engine")?; - // Instance-level database for cross-agent data (agent links) - let instance_db = spacebot::db::InstanceDb::connect(&config.instance_dir) - .await - .with_context(|| "failed to connect instance database")?; - let link_store = Arc::new(spacebot::links::LinkStore::new(instance_db.sqlite.clone())); - - // Sync config-defined links to database - for link_def in &config.links { - use spacebot::links::types::{AgentLink, LinkDirection, LinkRelationship}; - let direction: LinkDirection = link_def.direction.parse().map_err(|e: String| { - anyhow::anyhow!("{e} (link {} → {})", link_def.from, link_def.to) - })?; - let relationship: LinkRelationship = - link_def.relationship.parse().map_err(|e: String| { - anyhow::anyhow!("{e} (link {} → {})", link_def.from, link_def.to) - })?; - let link = AgentLink { - id: uuid::Uuid::new_v4().to_string(), - from_agent_id: link_def.from.clone(), - to_agent_id: link_def.to.clone(), - direction, - relationship, - enabled: true, - created_at: chrono::Utc::now(), - updated_at: chrono::Utc::now(), - }; - link_store.upsert_from_config(&link).await.with_context(|| { - format!( - "failed to sync link {} → {} from config", - link_def.from, link_def.to - ) - })?; - } + // Parse config links into shared agent links (hot-reloadable via ArcSwap) + let agent_links = Arc::new(ArcSwap::from_pointee( + spacebot::links::AgentLink::from_config(&config.links)?, + )); if !config.links.is_empty() { - tracing::info!(count = config.links.len(), "synced config links to instance database"); + tracing::info!(count = config.links.len(), "loaded agent links from config"); } // These hold the initialized subsystems. Empty until agents are initialized. @@ -744,7 +715,7 @@ async fn run( api_state.set_embedding_model(embedding_model.clone()).await; api_state.set_prompt_engine(prompt_engine.clone()).await; api_state.set_defaults_config(config.defaults.clone()).await; - api_state.set_link_store(link_store.clone()); + api_state.set_agent_links((**agent_links.load()).clone()); // Track whether agents have been initialized let mut agents_initialized = false; @@ -776,7 +747,7 @@ async fn run( &mut slack_permissions, &mut telegram_permissions, &mut twitch_permissions, - Some(link_store.clone()), + agent_links.clone(), ) .await?; agents_initialized = true; @@ -793,6 +764,7 @@ async fn run( bindings.clone(), Some(messaging_manager.clone()), llm_manager.clone(), + agent_links.clone(), ); } else { // Start file watcher in setup mode (no agents to watch yet) @@ -807,6 +779,7 @@ async fn run( bindings.clone(), None, llm_manager.clone(), + agent_links.clone(), ); } @@ -1104,7 +1077,7 @@ async fn run( &mut new_slack_permissions, &mut new_telegram_permissions, &mut new_twitch_permissions, - Some(link_store.clone()), + agent_links.clone(), ).await { Ok(()) => { agents_initialized = true; @@ -1120,6 +1093,7 @@ async fn run( bindings.clone(), Some(messaging_manager.clone()), new_llm_manager.clone(), + agent_links.clone(), ); tracing::info!("agents initialized after provider setup"); } @@ -1168,8 +1142,6 @@ async fn run( agent.db.close().await; } - instance_db.close().await; - tracing::info!("spacebot stopped"); // Flush buffered OTLP spans before the process exits. Without this the @@ -1214,7 +1186,7 @@ async fn initialize_agents( slack_permissions: &mut Option>>, telegram_permissions: &mut Option>>, twitch_permissions: &mut Option>>, - link_store: Option>, + agent_links: Arc>>, ) -> anyhow::Result<()> { let resolved_agents = config.resolve_agents(); @@ -1349,7 +1321,7 @@ async fn initialize_agents( event_tx, sqlite_pool: db.sqlite.clone(), messaging_manager: None, - link_store: link_store.clone(), + links: agent_links.clone(), }; let agent = spacebot::Agent { diff --git a/src/tools/send_agent_message.rs b/src/tools/send_agent_message.rs index 8a01617a0..1a75ab44c 100644 --- a/src/tools/send_agent_message.rs +++ b/src/tools/send_agent_message.rs @@ -1,9 +1,10 @@ //! Send message to another agent through the communication graph. -use crate::links::LinkStore; +use crate::links::AgentLink; use crate::messaging::MessagingManager; use crate::{AgentId, InboundMessage, MessageContent, ProcessEvent}; +use arc_swap::ArcSwap; use chrono::Utc; use rig::completion::ToolDefinition; use rig::tool::Tool; @@ -22,7 +23,7 @@ use tokio::sync::broadcast; pub struct SendAgentMessageTool { agent_id: AgentId, agent_name: String, - link_store: Arc, + links: Arc>>, messaging_manager: Arc, event_tx: broadcast::Sender, /// Map of known agent IDs to display names, for resolving targets. @@ -41,7 +42,7 @@ impl SendAgentMessageTool { pub fn new( agent_id: AgentId, agent_name: String, - link_store: Arc, + links: Arc>>, messaging_manager: Arc, event_tx: broadcast::Sender, agent_names: Arc>, @@ -49,7 +50,7 @@ impl SendAgentMessageTool { Self { agent_id, agent_name, - link_store, + links, messaging_manager, event_tx, agent_names, @@ -76,7 +77,7 @@ pub struct SendAgentMessageArgs { pub struct SendAgentMessageOutput { pub success: bool, pub target_agent: String, - pub link_id: String, + pub channel_id: String, } impl Tool for SendAgentMessageTool { @@ -116,36 +117,23 @@ impl Tool for SendAgentMessageTool { ); // Resolve target agent ID (could be name or ID) - let target_agent_id = self - .resolve_agent_id(&args.target) - .ok_or_else(|| { - SendAgentMessageError(format!( - "unknown agent '{}'. Check your organization context for available agents.", - args.target - )) - })?; + let target_agent_id = self.resolve_agent_id(&args.target).ok_or_else(|| { + SendAgentMessageError(format!( + "unknown agent '{}'. Check your organization context for available agents.", + args.target + )) + })?; // Look up the link between sending agent and target - let link = self - .link_store - .get_between(&self.agent_id, &target_agent_id) - .await - .map_err(|error| { - SendAgentMessageError(format!("failed to look up link: {error}")) - })? + let links = self.links.load(); + let link = crate::links::find_link_between(&links, &self.agent_id, &target_agent_id) .ok_or_else(|| { SendAgentMessageError(format!( "no communication link exists between you and agent '{}'.", args.target )) - })?; - - if !link.enabled { - return Err(SendAgentMessageError(format!( - "the link to agent '{}' is currently disabled.", - args.target - ))); - } + })? + .clone(); // Check direction: if the link is one_way, only from_agent can initiate let sending_agent_id = self.agent_id.as_ref(); @@ -173,7 +161,7 @@ impl Tool for SendAgentMessageTool { }; let target_agent_arc: AgentId = Arc::from(receiving_agent_id.as_str()); - let conversation_id = format!("link:{}", link.id); + let conversation_id = link.channel_id(); // Construct the internal message let message = InboundMessage { @@ -185,7 +173,6 @@ impl Tool for SendAgentMessageTool { content: MessageContent::Text(args.message), timestamp: Utc::now(), metadata: HashMap::from([ - ("link_id".into(), serde_json::json!(link.id)), ("from_agent_id".into(), serde_json::json!(sending_agent_id)), ( "relationship".into(), @@ -208,7 +195,7 @@ impl Tool for SendAgentMessageTool { .send(ProcessEvent::AgentMessageSent { from_agent_id: self.agent_id.clone(), to_agent_id: Arc::from(receiving_agent_id.as_str()), - link_id: link.id.clone(), + link_id: conversation_id.clone(), channel_id: Arc::from(conversation_id.as_str()), }) .ok(); @@ -222,14 +209,14 @@ impl Tool for SendAgentMessageTool { tracing::info!( from = %self.agent_id, to = %receiving_agent_id, - link_id = %link.id, + channel_id = %conversation_id, "agent message sent" ); Ok(SendAgentMessageOutput { success: true, target_agent: target_display, - link_id: link.id, + channel_id: conversation_id, }) } } diff --git a/tests/bulletin.rs b/tests/bulletin.rs index a41cb84b9..a231b71e9 100644 --- a/tests/bulletin.rs +++ b/tests/bulletin.rs @@ -80,6 +80,7 @@ async fn bootstrap_deps() -> anyhow::Result { event_tx, sqlite_pool: db.sqlite.clone(), messaging_manager: None, + links: Arc::new(arc_swap::ArcSwap::from_pointee(Vec::new())), }) } diff --git a/tests/context_dump.rs b/tests/context_dump.rs index 2f94d5ddf..43d8401b9 100644 --- a/tests/context_dump.rs +++ b/tests/context_dump.rs @@ -80,6 +80,7 @@ async fn bootstrap_deps() -> anyhow::Result<(spacebot::AgentDeps, spacebot::conf event_tx, sqlite_pool: db.sqlite.clone(), messaging_manager: None, + links: Arc::new(arc_swap::ArcSwap::from_pointee(Vec::new())), }; Ok((deps, config)) @@ -203,6 +204,7 @@ async fn dump_channel_context() { skip_flag, replied_flag, None, + None, ) .await .expect("failed to add channel tools"); @@ -413,6 +415,7 @@ async fn dump_all_contexts() { skip_flag, replied_flag, None, + None, ) .await .expect("failed to add channel tools"); From 76808f02a5fd4e895aeb990ef5b7c4a50778573a Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sun, 22 Feb 2026 16:19:08 -0800 Subject: [PATCH 03/12] feat: implement agent links and topology management API - Added new API endpoints for creating, updating, and deleting agent links. - Introduced data structures for managing agent links and their relationships. - Enhanced the frontend with a TopologyGraph component to visualize agent connections. - Updated the LiveContext to track active links based on message activity. - Modified Overview component to integrate the new TopologyGraph and display active links. --- interface/bun.lock | 12 +- interface/package-lock.json | 7238 +++++++++++++++++--- interface/package.json | 1 + interface/src/api/client.ts | 92 + interface/src/components/TopologyGraph.tsx | 742 ++ interface/src/hooks/useLiveContext.tsx | 64 +- interface/src/router.tsx | 4 +- interface/src/routes/Overview.tsx | 497 +- interface/src/ui/style/colors.scss | 86 +- src/api/links.rs | 257 +- src/api/server.rs | 6 +- 11 files changed, 7704 insertions(+), 1295 deletions(-) create mode 100644 interface/src/components/TopologyGraph.tsx diff --git a/interface/bun.lock b/interface/bun.lock index 1e48e338b..0349bffde 100644 --- a/interface/bun.lock +++ b/interface/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "spacebot-interface", @@ -36,6 +37,7 @@ "@tanstack/react-router": "^1.159.5", "@tanstack/react-virtual": "^3.13.18", "@tanstack/router-devtools": "^1.159.5", + "@xyflow/react": "^12.10.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "codemirror": "^6.0.2", @@ -615,6 +617,10 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "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" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, ""], + "@xyflow/react": ["@xyflow/react@12.10.1", "", { "dependencies": { "@xyflow/system": "0.0.75", "classcat": "^5.0.3", "zustand": "^4.4.0" }, "peerDependencies": { "react": ">=17", "react-dom": ">=17" } }, "sha512-5eSWtIK/+rkldOuFbOOz44CRgQRjtS9v5nufk77DV+XBnfCGL9HAQ8PG00o2ZYKqkEU/Ak6wrKC95Tu+2zuK3Q=="], + + "@xyflow/system": ["@xyflow/system@0.0.75", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -679,6 +685,8 @@ "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + "classcat": ["classcat@5.0.5", "", {}, "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="], + "classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="], "clsx": ["clsx@2.1.1", "", {}, ""], @@ -1555,7 +1563,7 @@ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "zustand": ["zustand@3.7.2", "", { "peerDependencies": { "react": ">=16.8" }, "optionalPeers": ["react"] }, "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA=="], + "zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], "zwitch": ["zwitch@2.0.4", "", {}, ""], @@ -1619,6 +1627,8 @@ "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "leva/zustand": ["zustand@3.7.2", "", { "peerDependencies": { "react": ">=16.8" }, "optionalPeers": ["react"] }, "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, ""], "mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], diff --git a/interface/package-lock.json b/interface/package-lock.json index 23b49244c..8a6cc8497 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -8,6 +8,14 @@ "name": "spacebot-interface", "version": "0.1.0", "dependencies": { + "@codemirror/language": "^6.12.1", + "@codemirror/legacy-modes": "^6.5.2", + "@codemirror/state": "^6.5.4", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.39.14", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@fontsource/ibm-plex-sans": "^5.1.0", "@fortawesome/fontawesome-svg-core": "^7.2.0", "@fortawesome/free-brands-svg-icons": "^7.2.0", @@ -16,6 +24,7 @@ "@hookform/resolvers": "^5.2.2", "@hugeicons/core-free-icons": "^3.1.1", "@hugeicons/react": "^1.1.5", + "@lobehub/icons": "^4.6.0", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -31,8 +40,10 @@ "@tanstack/react-router": "^1.159.5", "@tanstack/react-virtual": "^3.13.18", "@tanstack/router-devtools": "^1.159.5", + "@xyflow/react": "^12.10.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "codemirror": "^6.0.2", "framer-motion": "^12.34.0", "graphology": "^0.26.0", "graphology-layout-forceatlas2": "^0.10.1", @@ -44,6 +55,7 @@ "recharts": "^3.7.0", "remark-gfm": "^4.0.1", "sigma": "^3.0.2", + "smol-toml": "^1.6.0", "sonner": "^2.0.7", "zod": "^4.3.6" }, @@ -71,9 +83,121 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.0.tgz", + "integrity": "sha512-eZFrPCnrYrF3XtL7qA4L75P0qA3TtZta8H3Yggy7UYFh8gZgu5bSMNF+v4UVCzGxzYmx8ZvPdgOce0BJ6PsW9g==", + "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.1", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.1.tgz", + "integrity": "sha512-RKxkj5pGFB+FkPJ5NGhoX3DK3xsv0pMltha7Ei1AnY3tILeq38L7tuhaWDPQI/5nlPxOog44wvqpNyyGcUsNMg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/cssinjs": "^2.1.0", + "@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.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.1.0.tgz", + "integrity": "sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==", + "license": "MIT", + "peer": true, + "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.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/react-slick": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz", + "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==", + "license": "MIT", + "peer": true, + "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/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -123,7 +247,6 @@ }, "node_modules/@babel/generator": { "version": "7.29.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.0", @@ -153,7 +276,6 @@ }, "node_modules/@babel/helper-globals": { "version": "7.28.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -161,7 +283,6 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.6", @@ -197,7 +318,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -205,7 +325,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -233,7 +352,6 @@ }, "node_modules/@babel/parser": { "version": "7.29.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -273,9 +391,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.28.6", @@ -288,7 +414,6 @@ }, "node_modules/@babel/traverse": { "version": "7.29.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", @@ -305,7 +430,6 @@ }, "node_modules/@babel/types": { "version": "7.29.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -315,308 +439,1658 @@ "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@base-ui/react": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@base-ui/react/-/react-1.0.0.tgz", + "integrity": "sha512-4USBWz++DUSLTuIYpbYkSgy1F9ZmNG9S/lXvlUN6qMK0P0RlW+6eQmDUB4DgZ7HVvtXl4pvi4z5J2fv6Z3+9hg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peer": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "@base-ui/utils": "0.2.3", + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", + "reselect": "^5.1.1", + "tabbable": "^6.3.0", + "use-sync-external-store": "^1.6.0" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@floating-ui/core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", - "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "node_modules/@base-ui/utils": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.2.3.tgz", + "integrity": "sha512-/CguQ2PDaOzeVOkllQR8nocJ0FFIDqsWIcURsVmm53QGo8NhFNpePjNlyPIB41luxfOqnG7PU0xicMEw3ls7XQ==", "license": "MIT", + "peer": true, "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@babel/runtime": "^7.28.4", + "@floating-ui/utils": "^0.2.10", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@floating-ui/dom": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", - "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", "license": "MIT", + "peer": true + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz", + "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" + "@chevrotain/gast": "11.1.1", + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", - "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", - "license": "MIT", + "node_modules/@chevrotain/gast": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz", + "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@floating-ui/dom": "^1.7.5" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "@chevrotain/types": "11.1.1", + "lodash-es": "4.17.23" } }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz", + "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==", + "license": "Apache-2.0", + "peer": true }, - "node_modules/@fontsource/ibm-plex-sans": { - "version": "5.2.8", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" + "node_modules/@chevrotain/types": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz", + "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@chevrotain/utils": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz", + "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", + "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" } }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.2.0.tgz", - "integrity": "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==", + "node_modules/@codemirror/commands": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz", + "integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==", "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" } }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.2.0.tgz", - "integrity": "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==", + "node_modules/@codemirror/language": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz", + "integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==", "license": "MIT", "dependencies": { - "@fortawesome/fontawesome-common-types": "7.2.0" - }, - "engines": { - "node": ">=6" + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" } }, - "node_modules/@fortawesome/free-brands-svg-icons": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.2.0.tgz", - "integrity": "sha512-VNG8xqOip1JuJcC3zsVsKRQ60oXG9+oYNDCosjoU/H9pgYmLTEwWw8pE0jhPz/JWdHeUuK6+NQ3qsM4gIbdbYQ==", - "license": "(CC-BY-4.0 AND MIT)", + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz", + "integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==", + "license": "MIT", "dependencies": { - "@fortawesome/fontawesome-common-types": "7.2.0" - }, - "engines": { - "node": ">=6" + "@codemirror/language": "^6.0.0" } }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.2.0.tgz", - "integrity": "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==", - "license": "(CC-BY-4.0 AND MIT)", + "node_modules/@codemirror/lint": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.4.tgz", + "integrity": "sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==", + "license": "MIT", "dependencies": { - "@fortawesome/fontawesome-common-types": "7.2.0" - }, - "engines": { - "node": ">=6" + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" } }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-3.0.0.tgz", - "integrity": "sha512-x6boc1RLEjf/QPrMS20VJcabTZeGCb1hbwNybPPLjJohGPowXfjOpwQlVK6aH6MVKfCq2JXeHRIlx+tYpS18FA==", + "node_modules/@codemirror/search": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", "license": "MIT", "dependencies": { - "semver": "^7.7.2" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~6 || ~7", - "react": "^18.0.0 || ^19.0.0" + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" } }, - "node_modules/@fortawesome/react-fontawesome/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "node_modules/@codemirror/state": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", + "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" } }, - "node_modules/@hookform/resolvers": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", - "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", "license": "MIT", "dependencies": { - "@standard-schema/utils": "^0.3.0" - }, - "peerDependencies": { - "react-hook-form": "^7.55.0" + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" } }, - "node_modules/@hugeicons/core-free-icons": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-3.1.1.tgz", - "integrity": "sha512-UpS2lUQFi5sKyJSWwM6rO+BnPLvVz1gsyCpPHeZyVuZqi89YH8ksliza4cwaODqKOZyeXmG8juo1ty4QtQofkg==", - "license": "MIT" + "node_modules/@codemirror/view": { + "version": "6.39.15", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.15.tgz", + "integrity": "sha512-aCWjgweIIXLBHh7bY6cACvXuyrZ0xGafjQ2VInjp4RM4gMfscK5uESiNdrH0pE+e1lZr2B4ONGsjchl2KsKZzg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } }, - "node_modules/@hugeicons/react": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@hugeicons/react/-/react-1.1.5.tgz", - "integrity": "sha512-JX/iDz3oO7hWdVqbjwFwRrAjHk8h2vI+mBkNzp4JcXG3t4idoupfjon73nLOA7cr27m0M8hrRC1Q2h6nEBGKVA==", + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, "peerDependencies": { - "react": ">=16.0.0" + "react": ">=16.8.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "dev": true, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "dev": true, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", "license": "MIT", + "peer": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "dev": true, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emoji-mart/data": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz", + "integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==", + "license": "MIT", + "peer": true + }, + "node_modules/@emoji-mart/react": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz", + "integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "emoji-mart": "^5.2", + "react": "^16.8 || ^17 || ^18" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "dev": true, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, + "node_modules/@emotion/cache/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/@emotion/css": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz", + "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/serialize/node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/serialize/node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.18", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.18.tgz", + "integrity": "sha512-xJWJxvmy3a05j643gQt+pRbht5XnTlGpsEsAPnMi5F5YTOEEJymA90uZKBD8OvIv5XvZ1qi4GcccSlqT3Bq44Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@floating-ui/react-dom": "^2.1.7", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5" }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@fontsource/ibm-plex-sans": { + "version": "5.2.8", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.2.0.tgz", + "integrity": "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=6" } }, - "node_modules/@parcel/watcher": { - "version": "2.5.6", - "dev": true, - "hasInstallScript": true, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.2.0.tgz", + "integrity": "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==", "license": "MIT", - "optional": true, "dependencies": { - "detect-libc": "^2.0.3", - "is-glob": "^4.0.3", - "node-addon-api": "^7.0.0", - "picomatch": "^4.0.3" + "@fortawesome/fontawesome-common-types": "7.2.0" }, "engines": { - "node": ">= 10.0.0" + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.2.0.tgz", + "integrity": "sha512-VNG8xqOip1JuJcC3zsVsKRQ60oXG9+oYNDCosjoU/H9pgYmLTEwWw8pE0jhPz/JWdHeUuK6+NQ3qsM4gIbdbYQ==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.2.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.2.0.tgz", + "integrity": "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.2.0" }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.6", - "@parcel/watcher-darwin-arm64": "2.5.6", - "@parcel/watcher-darwin-x64": "2.5.6", - "@parcel/watcher-freebsd-x64": "2.5.6", - "@parcel/watcher-linux-arm-glibc": "2.5.6", - "@parcel/watcher-linux-arm-musl": "2.5.6", - "@parcel/watcher-linux-arm64-glibc": "2.5.6", - "@parcel/watcher-linux-arm64-musl": "2.5.6", - "@parcel/watcher-linux-x64-glibc": "2.5.6", - "@parcel/watcher-linux-x64-musl": "2.5.6", - "@parcel/watcher-win32-arm64": "2.5.6", - "@parcel/watcher-win32-ia32": "2.5.6", - "@parcel/watcher-win32-x64": "2.5.6" + "engines": { + "node": ">=6" } }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.6", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@fortawesome/react-fontawesome": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-3.0.0.tgz", + "integrity": "sha512-x6boc1RLEjf/QPrMS20VJcabTZeGCb1hbwNybPPLjJohGPowXfjOpwQlVK6aH6MVKfCq2JXeHRIlx+tYpS18FA==", + "license": "MIT", + "dependencies": { + "semver": "^7.7.2" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~6 || ~7", + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@fortawesome/react-fontawesome/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@giscus/react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@giscus/react/-/react-3.1.0.tgz", + "integrity": "sha512-0TCO2TvL43+oOdyVVGHDItwxD1UMKP2ZYpT6gXmhFOqfAJtZxTzJ9hkn34iAF/b6YzyJ4Um89QIt9z/ajmAEeg==", + "peer": true, + "dependencies": { + "giscus": "^1.6.0" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18 || ^19", + "react-dom": "^16 || ^17 || ^18 || ^19" + } + }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, + "node_modules/@hugeicons/core-free-icons": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-3.1.1.tgz", + "integrity": "sha512-UpS2lUQFi5sKyJSWwM6rO+BnPLvVz1gsyCpPHeZyVuZqi89YH8ksliza4cwaODqKOZyeXmG8juo1ty4QtQofkg==", + "license": "MIT" + }, + "node_modules/@hugeicons/react": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@hugeicons/react/-/react-1.1.5.tgz", + "integrity": "sha512-JX/iDz3oO7hWdVqbjwFwRrAjHk8h2vI+mBkNzp4JcXG3t4idoupfjon73nLOA7cr27m0M8hrRC1Q2h6nEBGKVA==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT", + "peer": true + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "license": "MIT", + "peer": true, + "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", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "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", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", + "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", + "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@lobehub/emojilib": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lobehub/emojilib/-/emojilib-1.0.0.tgz", + "integrity": "sha512-s9KnjaPjsEefaNv150G3aifvB+J3P4eEKG+epY9zDPS2BeB6+V2jELWqAZll+nkogMaVovjEE813z3V751QwGw==", + "license": "MIT", + "peer": true + }, + "node_modules/@lobehub/fluent-emoji": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@lobehub/fluent-emoji/-/fluent-emoji-4.1.0.tgz", + "integrity": "sha512-R1MB2lfUkDvB7XAQdRzY75c1dx/tB7gEvBPaEEMarzKfCJWmXm7rheS6caVzmgwAlq5sfmTbxPL+un99sp//Yw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@lobehub/emojilib": "^1.0.0", + "antd-style": "^4.1.0", + "emoji-regex": "^10.6.0", + "es-toolkit": "^1.43.0", + "lucide-react": "^0.562.0", + "url-join": "^5.0.0" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "node_modules/@lobehub/icons": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@lobehub/icons/-/icons-4.6.0.tgz", + "integrity": "sha512-TuU0837kalurxQfGwfyd6UODwKtPluhvuT8XrgqHo/D0B/ggbYWrLF1pwIYG+p9ccA6oz6HeaEQhmNH2eQl2sw==", + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "antd-style": "^4.1.0", + "lucide-react": "^0.469.0", + "polished": "^4.3.1" + }, + "peerDependencies": { + "@lobehub/ui": "^4.3.3", + "antd": "^6.1.1", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "node_modules/@lobehub/icons/node_modules/lucide-react": { + "version": "0.469.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz", + "integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@lobehub/ui": { + "version": "4.38.4", + "resolved": "https://registry.npmjs.org/@lobehub/ui/-/ui-4.38.4.tgz", + "integrity": "sha512-FYQeWkR0CoZCaPqEX9AUGrhaIfkYeuacW2KtV+1GS7eGVjREFNNOAgY5PLk20ZMYV/cRFsn9fNG0rqn9PxChxw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/cssinjs": "^2.0.3", + "@base-ui/react": "1.0.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@emoji-mart/data": "^1.2.1", + "@emoji-mart/react": "^1.1.1", + "@emotion/is-prop-valid": "^1.4.0", + "@floating-ui/react": "^0.27.17", + "@giscus/react": "^3.1.0", + "@mdx-js/mdx": "^3.1.1", + "@mdx-js/react": "^3.1.1", + "@pierre/diffs": "^1.0.10", + "@radix-ui/react-slot": "^1.2.4", + "@shikijs/core": "^3.22.0", + "@shikijs/transformers": "^3.22.0", + "@splinetool/runtime": "0.9.526", + "ahooks": "^3.9.6", + "antd-style": "^4.1.0", + "chroma-js": "^3.2.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "dayjs": "^1.11.19", + "emoji-mart": "^5.6.0", + "es-toolkit": "^1.44.0", + "fast-deep-equal": "^3.1.3", + "immer": "^11.1.3", + "katex": "^0.16.28", + "leva": "^0.10.1", + "lucide-react": "^0.563.0", + "marked": "^17.0.1", + "mermaid": "^11.12.2", + "motion": "^12.30.0", + "numeral": "^2.0.6", + "polished": "^4.3.1", + "query-string": "^9.3.1", + "rc-collapse": "^4.0.0", + "rc-footer": "^0.6.8", + "rc-image": "^7.12.0", + "rc-input-number": "^9.5.0", + "rc-menu": "^9.16.1", + "re-resizable": "^6.11.2", + "react-avatar-editor": "^14.0.0", + "react-error-boundary": "^6.1.0", + "react-hotkeys-hook": "^5.2.4", + "react-markdown": "^10.1.0", + "react-merge-refs": "^3.0.2", + "react-rnd": "^10.5.2", + "react-zoom-pan-pinch": "^3.7.0", + "rehype-github-alerts": "^4.2.0", + "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", + "remark-breaks": "^4.0.0", + "remark-cjk-friendly": "^1.2.3", + "remark-gfm": "^4.0.1", + "remark-github": "^12.0.0", + "remark-math": "^6.0.0", + "remend": "^1.2.0", + "shiki": "^3.22.0", + "shiki-stream": "^0.1.4", + "swr": "^2.4.0", + "ts-md5": "^2.0.1", + "unified": "^11.0.5", + "url-join": "^5.0.0", + "use-merge-value": "^1.2.0", + "uuid": "^13.0.0", + "virtua": "^0.48.5" + }, + "peerDependencies": { + "@lobehub/fluent-emoji": "^4.0.0", + "@lobehub/icons": "^4.0.0", + "antd": "^6.1.1", + "motion": "^12.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "node_modules/@lobehub/ui/node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@lobehub/ui/node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/@lobehub/ui/node_modules/lucide-react": { + "version": "0.563.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", + "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==", + "license": "ISC", + "peer": true, + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz", + "integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==", + "license": "MIT", + "peer": true, + "dependencies": { + "langium": "^4.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pierre/diffs": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@pierre/diffs/-/diffs-1.0.11.tgz", + "integrity": "sha512-j6zIEoyImQy1HfcJqbrDwP0O5I7V2VNXAaw53FqQ+SykRfaNwABeZHs9uibXO4supaXPmTx6LEH9Lffr03e1Tw==", + "license": "apache-2.0", + "peer": true, + "dependencies": { + "@shikijs/core": "^3.0.0", + "@shikijs/engine-javascript": "^3.0.0", + "@shikijs/transformers": "^3.0.0", + "diff": "8.0.3", + "hast-util-to-html": "9.0.5", + "lru_map": "0.4.1", + "shiki": "^3.0.0" + }, + "peerDependencies": { + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.3.1 || ^19.0.0" + } + }, + "node_modules/@primer/octicons": { + "version": "19.22.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.22.0.tgz", + "integrity": "sha512-nWoh9PlE6u7xbiZF3KcUm3ktLpN2rQPt11trwp/t4EsKuYRNVWVbBp1LkCBsvZq7ScckNKUURLigIU0wS1FQdw==", + "license": "MIT", + "peer": true, + "dependencies": { + "object-assign": "^4.1.1" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@radix-ui/number": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", - "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", - "license": "MIT" - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -633,20 +2107,33 @@ } } }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", - "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", "license": "MIT", "dependencies": { + "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", @@ -663,16 +2150,23 @@ } } }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", "license": "MIT", "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -689,11 +2183,14 @@ } } }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -704,41 +2201,83 @@ } } }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true + }, + "@types/react-dom": { + "optional": true } } }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -755,10 +2294,10 @@ } } }, - "node_modules/@radix-ui/react-direction": { + "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -770,66 +2309,80 @@ } } }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { "optional": true } } }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -841,35 +2394,28 @@ } } }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" + "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-id": { + "node_modules/@radix-ui/react-use-size": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -884,30 +2430,13 @@ } } }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", @@ -924,561 +2453,773 @@ } } }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", - "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@rc-component/async-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz", + "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/cascader": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.14.0.tgz", + "integrity": "sha512-Ip9356xwZUR2nbW5PRVGif4B/bDve4pLa/N+PGbvBaTnjbvmN4PFMBGQSmlDlzKP1ovxaYMvwF/dI9lXNLT4iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@rc-component/select": "~1.6.0", + "@rc-component/tree": "~1.2.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/checkbox": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz", + "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "node_modules/@rc-component/collapse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz", + "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==", "license": "MIT", + "peer": true, "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" + "@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.0", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.1.0.tgz", + "integrity": "sha512-o7Vavj7yyfVxFmeynXf0fCHVlC0UTE9al74c6nYuLck+gjuVdQNWSVXR8Efq/mmWFy7891SCOsfaPq6Eqe1s/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/fast-color": "^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/context": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.1.tgz", + "integrity": "sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==", + "license": "MIT", + "peer": true, + "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.npmjs.org/@rc-component/dialog/-/dialog-1.8.4.tgz", + "integrity": "sha512-Ay6PM7phkTkquplG8fWfUGFZ2GTLx9diTl4f0d8Eqxd7W1u1KjE9AQooFQHOHnhZf0Ya3z51+5EKCWHmt/dNEw==", + "license": "MIT", + "peer": true, + "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.npmjs.org/@rc-component/drawer/-/drawer-1.4.2.tgz", + "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==", + "license": "MIT", + "peer": true, + "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.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz", + "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==", + "license": "MIT", + "peer": true, + "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.6.2", + "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.6.2.tgz", + "integrity": "sha512-OgIn2RAoaSBqaIgzJf/X6iflIa9LpTozci1lagLBdURDFhGA370v0+T0tXxOi8YShMjTha531sFhwtnrv+EJaQ==", + "license": "MIT", + "peer": true, + "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.6.0", + "resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.6.0.tgz", + "integrity": "sha512-tSfn2ZE/oP082g4QIOxeehkmgnXB7R+5AFj/lIFr4k7pEuxHBdyGIq9axoCY9qea8NN0DY6p4IB/F07tLqaT5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/portal": "^2.1.2", + "@rc-component/util": "^1.3.0", + "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.npmjs.org/@rc-component/input/-/input-1.1.2.tgz", + "integrity": "sha512-Q61IMR47piUBudgixJ30CciKIy9b1H95qe7GgEKOmSJVJXvFRWJllJfQry9tif+MX2cWFXWJf/RXz4kaCeq/Fg==", + "license": "MIT", + "peer": true, + "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.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz", + "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==", + "license": "MIT", + "peer": true, + "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.npmjs.org/@rc-component/mentions/-/mentions-1.6.0.tgz", + "integrity": "sha512-KIkQNP6habNuTsLhUv0UGEOwG67tlmE7KNIJoQZZNggEZl5lQJTytFDb69sl5CK3TDdISCTjKP3nGEBKgT61CQ==", + "license": "MIT", + "peer": true, + "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.npmjs.org/@rc-component/menu/-/menu-1.2.0.tgz", + "integrity": "sha512-VWwDuhvYHSnTGj4n6bV3ISrLACcPAzdPOq3d0BzkeiM5cve8BEYfvkEhNoM0PLzv51jpcejeyrLXeMVIJ+QJlg==", + "license": "MIT", + "peer": true, + "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.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/motion": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.1.6.tgz", + "integrity": "sha512-aEQobs/YA0kqRvHIPjQvOytdtdRVyhf/uXAal4chBjxDu6odHckExJzjn2D+Ju1aKK6hx3pAs6BXdV9+86xkgQ==", + "license": "MIT", + "peer": true, + "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.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", + "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==", + "license": "MIT", + "peer": true, + "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.npmjs.org/@rc-component/notification/-/notification-1.2.0.tgz", + "integrity": "sha512-OX3J+zVU7rvoJCikjrfW7qOUp7zlDeFBK2eA3SFbGSkDqo63Sl4Ss8A04kFP+fxHSxMDIS9jYVEZtU1FNCFuBA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "node_modules/@rc-component/overflow": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.0.tgz", + "integrity": "sha512-GSlBeoE0XTBi5cf3zl8Qh7Uqhn7v8RrlJ8ajeVpEkNe94HWy5l5BQ0Mwn2TVUq9gdgbfEMUmTX7tJFAg7mz0Rw==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@babel/runtime": "^7.11.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "node_modules/@rc-component/pagination": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.2.0.tgz", + "integrity": "sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "node_modules/@rc-component/picker": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.9.0.tgz", + "integrity": "sha512-OLisdk8AWVCG9goBU1dWzuH5QlBQk8jktmQ6p0/IyBFwdKGwyIZOSjnBYo8hooHiTdl0lU+wGf/OfMtVBw02KQ==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/react-slot": "1.2.3" + "@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": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" }, "peerDependenciesMeta": { - "@types/react": { + "date-fns": { "optional": true }, - "@types/react-dom": { + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { "optional": true } } }, - "node_modules/@radix-ui/react-radio-group": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", - "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "node_modules/@rc-component/portal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.2.0.tgz", + "integrity": "sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">=12.x" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" } }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "node_modules/@rc-component/progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz", + "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-select": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", - "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "node_modules/@rc-component/qrcode": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" + "@babel/runtime": "^7.24.7" }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">=8.x" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-slider": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", - "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "node_modules/@rc-component/rate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz", + "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">=8.x" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "node_modules/@rc-component/resize-observer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.1.tgz", + "integrity": "sha512-NfXXMmiR+SmUuKE1NwJESzEUYUFWIDUn2uXpxCTOLwiRUUakd62DRNFjRJArgzyFW8S5rsL4aX5XlyIXyC/vRA==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" + "@rc-component/util": "^1.2.0" }, "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/segmented": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.3.0.tgz", + "integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" } }, - "node_modules/@radix-ui/react-switch": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", - "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "node_modules/@rc-component/select": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.6.10.tgz", + "integrity": "sha512-y4+2LnyGZrAorIBwflk78PmFVUWcSc9pcljiH72oHj7K1YY/BFUmj224pD7P4o7J+tbIFES45Z7LIpjVmvYlNA==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" + "@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": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/slider": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.0.1.tgz", + "integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "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.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz", + "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==", + "license": "MIT", + "peer": true, + "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/@radix-ui/react-tabs": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "node_modules/@rc-component/switch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz", + "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", - "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "node_modules/@rc-component/table": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.9.1.tgz", + "integrity": "sha512-FVI5ZS/GdB3BcgexfCYKi3iHhZS3Fr59EtsxORszYGrfpH1eWr33eDNSYkVfLI6tfJ7vftJDd9D5apfFWqkdJg==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-visually-hidden": "1.2.3" + "@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" }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">=8.x" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" } }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "node_modules/@rc-component/tabs": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.7.0.tgz", + "integrity": "sha512-J48cs2iBi7Ho3nptBxxIqizEliUC+ExE23faspUQKGQ550vaBlv3aGF8Epv/UB1vFWeoJDTW/dNzgIU0Qj5i/w==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "peer": true, + "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" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "node_modules/@rc-component/textarea": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/textarea/-/textarea-1.1.2.tgz", + "integrity": "sha512-9rMUEODWZDMovfScIEHXWlVZuPljZ2pd1LKNjslJVitn4SldEzq5vO1CL3yy3Dnib6zZal2r2DPtjy84VVpF6A==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@rc-component/input": "~1.1.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" }, "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "node_modules/@rc-component/tooltip": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz", + "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@rc-component/trigger": "^3.7.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" }, "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "react": ">=18.0.0", + "react-dom": ">=18.0.0" } }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "node_modules/@rc-component/tour": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.3.0.tgz", + "integrity": "sha512-K04K9r32kUC+auBSQfr+Fss4SpSIS9JGe56oq/ALAX0p+i2ylYOI1MgR83yBY7v96eO6ZFXcM/igCQmubps0Ow==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" + "@rc-component/portal": "^2.2.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.7.0", + "clsx": "^2.1.1" }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">=8.x" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "node_modules/@rc-component/tree": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.2.3.tgz", + "integrity": "sha512-mG8hF2ogQcKaEpfyxzPvMWqqkptofd7Sf+YiXOpPzuXLTLwNKfLDJtysc1/oybopbnzxNqWh2Vgwi+GYwNIb7w==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "peer": true, + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/util": "^1.8.1", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" } }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", - "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "node_modules/@rc-component/tree-select": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.8.0.tgz", + "integrity": "sha512-iYsPq3nuLYvGqdvFAW+l+I9ASRIOVbMXyA8FGZg2lGym/GwkaWeJGzI4eJ7c9IOEhRj0oyfIN4S92Fl3J05mjQ==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "peer": true, + "dependencies": { + "@rc-component/select": "~1.6.0", + "@rc-component/tree": "~1.2.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "peerDependencies": { + "react": "*", + "react-dom": "*" } }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "node_modules/@rc-component/trigger": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.0.tgz", + "integrity": "sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/rect": "1.1.1" + "@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" }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">=8.x" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" } }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "node_modules/@rc-component/upload": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.0.tgz", + "integrity": "sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==", "license": "MIT", + "peer": true, "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" }, "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", - "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "node_modules/@rc-component/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.9.0.tgz", + "integrity": "sha512-5uW6AfhIigCWeEQDthTozlxiT4Prn6xYQWeO0xokjcaa186OtwPRHBZJ2o0T0FhbjGhZ3vXdbkv0sx3gAYW7Vg==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" }, "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "react": ">=18.0.0", + "react-dom": ">=18.0.0" } }, - "node_modules/@radix-ui/rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "node_modules/@rc-component/util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/@rc-component/virtual-list": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.0.2.tgz", + "integrity": "sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ==", + "license": "MIT", + "peer": true, + "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/@react-sigma/core": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@react-sigma/core/-/core-5.0.6.tgz", @@ -1514,30 +3255,125 @@ } } }, - "node_modules/@reduxjs/toolkit/node_modules/immer": { - "version": "11.1.4", + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.4", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", + "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", + "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", + "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", + "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", + "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-3.22.0.tgz", + "integrity": "sha512-E7eRV7mwDBjueLF6852n2oYeJYxBq3NSsDk+uyruYAXONv4U8holGmIrT+mPRJQ1J1SNOH6L8G19KRzmBawrFw==", "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" + "peer": true, + "dependencies": { + "@shikijs/core": "3.22.0", + "@shikijs/types": "3.22.0" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "dev": true, - "license": "MIT" + "node_modules/@shikijs/types": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", + "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "peer": true + }, + "node_modules/@splinetool/runtime": { + "version": "0.9.526", + "resolved": "https://registry.npmjs.org/@splinetool/runtime/-/runtime-0.9.526.tgz", + "integrity": "sha512-qznHbXA5aKwDbCgESAothCNm1IeEZcmNWG145p5aXj4w5uoqR1TZ9qkTHTKLTsUbHeitCwdhzmRqan1kxboLgQ==", + "peer": true, + "dependencies": { + "on-change": "^4.0.0", + "semver-compare": "^1.0.0" + } }, "node_modules/@standard-schema/spec": { "version": "1.1.0", @@ -1547,6 +3383,16 @@ "version": "0.3.0", "license": "MIT" }, + "node_modules/@stitches/react": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/react/-/react-1.2.8.tgz", + "integrity": "sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/@tanstack/history": { "version": "1.154.14", "license": "MIT", @@ -1784,18 +3630,166 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "peer": true, + "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", "license": "MIT" }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT", + "peer": true + }, "node_modules/@types/d3-color": { "version": "3.1.3", "license": "MIT" }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT", + "peer": true + }, "node_modules/@types/d3-ease": { "version": "3.0.2", "license": "MIT" }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT", + "peer": true + }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "license": "MIT", @@ -1807,6 +3801,27 @@ "version": "3.1.1", "license": "MIT" }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT", + "peer": true + }, "node_modules/@types/d3-scale": { "version": "4.0.9", "license": "MIT", @@ -1814,6 +3829,19 @@ "@types/d3-time": "*" } }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@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", "license": "MIT", @@ -1825,10 +3853,36 @@ "version": "3.0.4", "license": "MIT" }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT", + "peer": true + }, "node_modules/@types/d3-timer": { "version": "3.0.2", "license": "MIT" }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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/debug": { "version": "4.1.12", "license": "MIT", @@ -1847,6 +3901,13 @@ "@types/estree": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT", + "peer": true + }, "node_modules/@types/hast": { "version": "3.0.4", "license": "MIT", @@ -1854,6 +3915,20 @@ "@types/unist": "*" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "license": "MIT", + "peer": true + }, "node_modules/@types/mdast": { "version": "4.0.4", "license": "MIT", @@ -1861,10 +3936,23 @@ "@types/unist": "*" } }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/ms": { "version": "2.1.0", "license": "MIT" }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.14", "license": "MIT", @@ -1880,6 +3968,13 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/unist": { "version": "3.0.3", "license": "MIT" @@ -1892,6 +3987,26 @@ "version": "1.3.0", "license": "ISC" }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "license": "MIT", + "peer": true + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "dev": true, @@ -1911,6 +4026,169 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@xyflow/react": { + "version": "12.10.1", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.1.tgz", + "integrity": "sha512-5eSWtIK/+rkldOuFbOOz44CRgQRjtS9v5nufk77DV+XBnfCGL9HAQ8PG00o2ZYKqkEU/Ak6wrKC95Tu+2zuK3Q==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.75", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.75", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.75.tgz", + "integrity": "sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ahooks": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.9.6.tgz", + "integrity": "sha512-Mr7f05swd5SmKlR9SZo5U6M0LsL4ErweLzpdgXjA1JPmnZ78Vr6wzx0jUtvoxrcqGKYnX0Yjc02iEASVxHFPjQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.21.0", + "@types/js-cookie": "^3.0.6", + "dayjs": "^1.9.1", + "intersection-observer": "^0.12.0", + "js-cookie": "^3.0.5", + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "tslib": "^2.4.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/antd": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.3.0.tgz", + "integrity": "sha512-bbHJcASrRHp02wTpr940KtUHlTT6tvmaD4OAjqgOJXNmTQ/+qBDdBVWY/yeDV41p/WbWjTLlaqRGVbL3UEVpNw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/cssinjs": "^2.1.0", + "@ant-design/cssinjs-utils": "^2.1.1", + "@ant-design/fast-color": "^3.0.1", + "@ant-design/icons": "^6.1.0", + "@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.0", + "@rc-component/dialog": "~1.8.4", + "@rc-component/drawer": "~1.4.2", + "@rc-component/dropdown": "~1.0.2", + "@rc-component/form": "~1.6.2", + "@rc-component/image": "~1.6.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.1.6", + "@rc-component/mutate-observer": "^2.0.1", + "@rc-component/notification": "~1.2.0", + "@rc-component/pagination": "~1.2.0", + "@rc-component/picker": "~1.9.0", + "@rc-component/progress": "~1.0.2", + "@rc-component/qrcode": "~1.1.1", + "@rc-component/rate": "~1.0.1", + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/segmented": "~1.3.0", + "@rc-component/select": "~1.6.5", + "@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.3", + "@rc-component/tree-select": "~1.8.0", + "@rc-component/trigger": "^3.9.0", + "@rc-component/upload": "~1.1.0", + "@rc-component/util": "^1.9.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/antd-style": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/antd-style/-/antd-style-4.1.0.tgz", + "integrity": "sha512-vnPBGg0OVlSz90KRYZhxd89aZiOImTiesF+9MQqN8jsLGZUQTjbP04X9jTdEfsztKUuMbBWg/RmB/wHTakbtMQ==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.0.0", + "@babel/runtime": "^7.24.1", + "@emotion/cache": "^11.11.0", + "@emotion/css": "^11.11.2", + "@emotion/react": "^11.11.4", + "@emotion/serialize": "^1.1.3", + "@emotion/utils": "^1.2.1", + "use-merge-value": "^1.2.0" + }, + "peerDependencies": { + "antd": ">=6.0.0", + "react": ">=18" + } + }, "node_modules/any-promise": { "version": "1.3.0", "dev": true, @@ -1956,6 +4234,36 @@ "node": ">=10" } }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "peer": true, + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/autoprefixer": { "version": "10.4.24", "dev": true, @@ -1991,6 +4299,21 @@ "postcss": "^8.1.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/bail": { "version": "2.0.2", "license": "MIT", @@ -2061,6 +4384,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "dev": true, @@ -2128,6 +4460,34 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chevrotain": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz", + "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@chevrotain/cst-dts-gen": "11.1.1", + "@chevrotain/gast": "11.1.1", + "@chevrotain/regexp-to-ast": "11.1.1", + "@chevrotain/types": "11.1.1", + "@chevrotain/utils": "11.1.1", + "lodash-es": "4.17.23" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, "node_modules/chokidar": { "version": "4.0.3", "dev": true, @@ -2142,6 +4502,13 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chroma-js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-3.2.0.tgz", + "integrity": "sha512-os/OippSlX1RlWWr+QDPcGUZs0uoqr32urfxESG9U93lhUfbnlyckte84Q8P1UQY/qth983AS1JONKmLS4T0nw==", + "license": "(BSD-3-Clause AND Apache-2.0)", + "peer": true + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -2154,6 +4521,19 @@ "url": "https://polar.sh/cva" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT", + "peer": true + }, "node_modules/clsx": { "version": "2.1.1", "license": "MIT", @@ -2161,6 +4541,39 @@ "node": ">=6" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT", + "peer": true + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "license": "MIT", @@ -2173,9 +4586,23 @@ "version": "4.1.1", "dev": true, "license": "MIT", - "engines": { - "node": ">= 6" - } + "engines": { + "node": ">= 6" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT", + "peer": true + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT", + "peer": true }, "node_modules/convert-source-map": { "version": "2.0.0", @@ -2186,6 +4613,47 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "peer": true, + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "dev": true, @@ -2201,6 +4669,101 @@ "version": "3.2.3", "license": "MIT" }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "peer": true, + "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.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "peer": true, + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT", + "peer": true + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "peer": true, + "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", "license": "ISC", @@ -2211,6 +4774,46 @@ "node": ">=12" } }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "peer": true, + "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.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-color": { "version": "3.1.0", "license": "ISC", @@ -2218,6 +4821,90 @@ "node": ">=12" } }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "peer": true, + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "peer": true, + "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.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/d3-ease": { "version": "3.0.1", "license": "BSD-3-Clause", @@ -2225,6 +4912,34 @@ "node": ">=12" } }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.2", "license": "ISC", @@ -2232,6 +4947,29 @@ "node": ">=12" } }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-interpolate": { "version": "3.0.1", "license": "ISC", @@ -2249,6 +4987,81 @@ "node": ">=12" } }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "peer": true, + "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.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC", + "peer": true + }, "node_modules/d3-scale": { "version": "4.0.2", "license": "ISC", @@ -2263,6 +5076,29 @@ "node": ">=12" } }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/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", "license": "ISC", @@ -2300,6 +5136,59 @@ "node": ">=12" } }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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-d3-es": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", + "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT", + "peer": true + }, "node_modules/debug": { "version": "4.4.3", "license": "MIT", @@ -2330,6 +5219,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/decode-uri-component": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz", + "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "peer": true, + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/dequal": { "version": "2.0.3", "license": "MIT", @@ -2368,15 +5277,71 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dlv": { "version": "1.1.3", "dev": true, "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "dev": true, - "license": "ISC" + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-mart": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz", + "integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==", + "license": "MIT", + "peer": true + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT", + "peer": true + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } }, "node_modules/es-toolkit": { "version": "1.44.0", @@ -2386,6 +5351,40 @@ "benchmarks" ] }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esbuild": { "version": "0.25.12", "dev": true, @@ -2444,6 +5443,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", "license": "MIT", @@ -2452,6 +5482,62 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/eventemitter3": { "version": "5.0.4", "license": "MIT" @@ -2469,6 +5555,36 @@ "version": "3.0.2", "license": "MIT" }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "peer": true + }, "node_modules/fast-glob": { "version": "3.3.3", "dev": true, @@ -2519,6 +5635,19 @@ } } }, + "node_modules/file-selector": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.5.0.tgz", + "integrity": "sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "dev": true, @@ -2530,6 +5659,35 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz", + "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "dev": true, @@ -2543,10 +5701,12 @@ } }, "node_modules/framer-motion": { - "version": "12.34.0", + "version": "12.34.3", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.3.tgz", + "integrity": "sha512-v81ecyZKYO/DfpTwHivqkxSUBzvceOpoI+wLfgCgoUIKxlFKEXdg0oR9imxwXumT4SFy8vRk9xzJ5l3/Du/55Q==", "license": "MIT", "dependencies": { - "motion-dom": "^12.34.0", + "motion-dom": "^12.34.3", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, @@ -2581,7 +5741,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2595,6 +5754,19 @@ "node": ">=6.9.0" } }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -2604,6 +5776,26 @@ "node": ">=6" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/giscus": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/giscus/-/giscus-1.6.0.tgz", + "integrity": "sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "lit": "^3.2.1" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "dev": true, @@ -2630,46 +5822,232 @@ "dependencies": { "events": "^3.3.0" }, - "peerDependencies": { - "graphology-types": ">=0.24.0" + "peerDependencies": { + "graphology-types": ">=0.24.0" + } + }, + "node_modules/graphology-layout-forceatlas2": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/graphology-layout-forceatlas2/-/graphology-layout-forceatlas2-0.10.1.tgz", + "integrity": "sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ==", + "license": "MIT", + "dependencies": { + "graphology-utils": "^2.1.0" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-types": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.8.tgz", + "integrity": "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==", + "license": "MIT" + }, + "node_modules/graphology-utils": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz", + "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==", + "license": "MIT", + "peerDependencies": { + "graphology-types": ">=0.23.0" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT", + "peer": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "license": "ISC", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/graphology-layout-forceatlas2": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/graphology-layout-forceatlas2/-/graphology-layout-forceatlas2-0.10.1.tgz", - "integrity": "sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ==", + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", "license": "MIT", + "peer": true, "dependencies": { - "graphology-utils": "^2.1.0" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" }, - "peerDependencies": { - "graphology-types": ">=0.19.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/graphology-types": { - "version": "0.24.8", - "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.8.tgz", - "integrity": "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==", - "license": "MIT" - }, - "node_modules/graphology-utils": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz", - "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==", + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", "license": "MIT", - "peerDependencies": { - "graphology-types": ">=0.23.0" + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/hasown": { - "version": "2.0.2", - "dev": true, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", "license": "MIT", + "peer": true, "dependencies": { - "function-bind": "^1.1.2" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" }, - "engines": { - "node": ">= 0.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-to-jsx-runtime": { @@ -2697,6 +6075,43 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "license": "MIT", @@ -2708,6 +6123,39 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "peer": true, + "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/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/html-url-attributes": { "version": "3.0.1", "license": "MIT", @@ -2716,6 +6164,30 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/immer": { "version": "10.2.0", "license": "MIT", @@ -2729,6 +6201,22 @@ "dev": true, "license": "MIT" }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inline-style-parser": { "version": "0.2.7", "license": "MIT" @@ -2740,6 +6228,14 @@ "node": ">=12" } }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", + "deprecated": "The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.", + "license": "Apache-2.0", + "peer": true + }, "node_modules/is-alphabetical": { "version": "2.0.1", "license": "MIT", @@ -2760,6 +6256,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "dev": true, @@ -2773,7 +6275,6 @@ }, "node_modules/is-core-module": { "version": "2.16.1", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -2793,6 +6294,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "dev": true, @@ -2820,6 +6334,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "dev": true, @@ -2838,6 +6358,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "peer": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/isbot": { "version": "5.1.35", "license": "Unlicense", @@ -2845,6 +6378,16 @@ "node": ">=18" } }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jiti": { "version": "1.21.7", "dev": true, @@ -2853,14 +6396,22 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", - "dev": true, "license": "MIT" }, "node_modules/jsesc": { "version": "3.1.0", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -2869,6 +6420,22 @@ "node": ">=6" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "string-convert": "^0.2.0" + } + }, "node_modules/json5": { "version": "2.2.3", "dev": true, @@ -2880,6 +6447,106 @@ "node": ">=6" } }, + "node_modules/katex": { + "version": "0.16.32", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.32.tgz", + "integrity": "sha512-ac0FzkRJlpw4WyH3Zu/OgU9LmPKqjHr6O2BxfSrBt8uJ1BhvH2YK3oJ4ut/K+O+6qQt2MGpdbn0MrffVEnnUDQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "peer": true, + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==", + "peer": true + }, + "node_modules/langium": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", + "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "chevrotain": "~11.1.1", + "chevrotain-allstar": "~0.3.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.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT", + "peer": true + }, + "node_modules/leva": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/leva/-/leva-0.10.1.tgz", + "integrity": "sha512-BcjnfUX8jpmwZUz2L7AfBtF9vn4ggTH33hmeufDULbP3YgNZ/C+ss/oO3stbrqRQyaOmRwy70y7BGTGO81S3rA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@radix-ui/react-portal": "^1.1.4", + "@radix-ui/react-tooltip": "^1.1.8", + "@stitches/react": "^1.2.8", + "@use-gesture/react": "^10.2.5", + "colord": "^2.9.2", + "dequal": "^2.0.2", + "merge-value": "^1.0.0", + "react-colorful": "^5.5.1", + "react-dropzone": "^12.0.0", + "v8n": "^1.3.3", + "zustand": "^3.6.9" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/leva/node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/lilconfig": { "version": "3.1.3", "dev": true, @@ -2893,9 +6560,56 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "dev": true, "license": "MIT" }, + "node_modules/lit": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", + "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz", + "integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT", + "peer": true + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT", + "peer": true + }, "node_modules/longest-streak": { "version": "3.1.0", "license": "MIT", @@ -2904,6 +6618,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru_map": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", + "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", + "license": "MIT", + "peer": true + }, "node_modules/lru-cache": { "version": "5.1.1", "dev": true, @@ -2912,6 +6646,29 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.562.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", + "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", + "license": "ISC", + "peer": true, + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/markdown-table": { "version": "3.0.4", "license": "MIT", @@ -2920,6 +6677,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/marked": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.3.tgz", + "integrity": "sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "license": "MIT", @@ -3045,6 +6815,44 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "peer": true, + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", "license": "MIT", @@ -3099,6 +6907,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-newline-to-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz", + "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-find-and-replace": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", "license": "MIT", @@ -3160,6 +6983,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/merge-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/merge-value/-/merge-value-1.0.0.tgz", + "integrity": "sha512-fJMmvat4NeKz63Uv9iHWcPDjCWcCkoiRoajRTEO8hlhUC6rwaHg0QCF9hBOTjZmm4JuglPckPSTtcuJL5kp0TQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "get-value": "^2.0.6", + "is-extendable": "^1.0.0", + "mixin-deep": "^1.2.0", + "set-value": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "dev": true, @@ -3168,6 +7007,62 @@ "node": ">= 8" } }, + "node_modules/mermaid": { + "version": "11.12.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", + "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.1", + "@mermaid-js/parser": "^1.0.0", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "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.13", + "dayjs": "^1.11.18", + "dompurify": "^3.2.5", + "katex": "^0.16.22", + "khroma": "^2.1.0", + "lodash-es": "^4.17.23", + "marked": "^16.2.1", + "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.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mermaid/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/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", + "peer": true, + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/micromark": { "version": "4.0.2", "funding": [ @@ -3233,6 +7128,52 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-extension-cjk-friendly": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly/-/micromark-extension-cjk-friendly-1.2.3.tgz", + "integrity": "sha512-gRzVLUdjXBLX6zNPSnHGDoo+ZTp5zy+MZm0g3sv+3chPXY7l9gW+DnrcHcZh/jiPR6MjPKO4AEJNp4Aw6V9z5Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "devlop": "^1.1.0", + "micromark-extension-cjk-friendly-util": "2.1.1", + "micromark-util-chunked": "^2.0.1", + "micromark-util-resolve-all": "^2.0.1", + "micromark-util-symbol": "^2.0.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "micromark": "^4.0.0", + "micromark-util-types": "^2.0.0" + }, + "peerDependenciesMeta": { + "micromark-util-types": { + "optional": true + } + } + }, + "node_modules/micromark-extension-cjk-friendly-util": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly-util/-/micromark-extension-cjk-friendly-util-2.1.1.tgz", + "integrity": "sha512-egs6+12JU2yutskHY55FyR48ZiEcFOJFyk9rsiyIhcJ6IvWB6ABBqVrBw8IobqJTDZ/wdSr9eoXDPb5S2nW1bg==", + "license": "MIT", + "peer": true, + "dependencies": { + "get-east-asian-width": "^1.3.0", + "micromark-util-character": "^2.1.1", + "micromark-util-symbol": "^2.0.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependenciesMeta": { + "micromark-util-types": { + "optional": true + } + } + }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", "license": "MIT", @@ -3340,6 +7281,133 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-factory-destination": { "version": "2.0.1", "funding": [ @@ -3379,6 +7447,34 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, "node_modules/micromark-factory-space": { "version": "2.0.1", "funding": [ @@ -3560,6 +7656,32 @@ ], "license": "MIT" }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", "funding": [ @@ -3694,12 +7816,68 @@ "engines": { "node": ">=8.6" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/motion": { + "version": "12.34.3", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.34.3.tgz", + "integrity": "sha512-xZIkBGO7v/Uvm+EyaqYd+9IpXu0sZqLywVlGdCFrrMiaO9JI4Kx51mO9KlHSWwll+gZUVY5OJsWgYI5FywJ/tw==", + "license": "MIT", + "peer": true, + "dependencies": { + "framer-motion": "^12.34.3", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, "node_modules/motion-dom": { - "version": "12.34.0", + "version": "12.34.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.3.tgz", + "integrity": "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ==", "license": "MIT", "dependencies": { "motion-utils": "^12.29.2" @@ -3707,6 +7885,8 @@ }, "node_modules/motion-utils": { "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", "license": "MIT" }, "node_modules/ms": { @@ -3759,9 +7939,18 @@ "node": ">=0.10.0" } }, + "node_modules/numeral": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", + "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3775,6 +7964,57 @@ "node": ">= 6" } }, + "node_modules/on-change": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/on-change/-/on-change-4.0.2.tgz", + "integrity": "sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/on-change?sponsor=1" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT", + "peer": true + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "peer": true, + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT", + "peer": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-entities": { "version": "4.0.2", "license": "MIT", @@ -3796,14 +8036,66 @@ "version": "2.0.11", "license": "MIT" }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "peer": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT", + "peer": true + }, "node_modules/path-parse": { "version": "1.0.7", - "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT", + "peer": true + }, "node_modules/picocolors": { "version": "1.1.1", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -3833,6 +8125,48 @@ "node": ">= 6" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "peer": true, + "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.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT", + "peer": true + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/postcss": { "version": "8.5.6", "dev": true, @@ -3982,6 +8316,25 @@ "dev": true, "license": "MIT" }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, "node_modules/property-information": { "version": "7.1.0", "license": "MIT", @@ -3990,6 +8343,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/query-string": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.3.1.tgz", + "integrity": "sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "decode-uri-component": "^0.4.1", + "filter-obj": "^5.1.0", + "split-on-first": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "dev": true, @@ -4009,23 +8380,389 @@ ], "license": "MIT" }, + "node_modules/rc-collapse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-4.0.0.tgz", + "integrity": "sha512-SwoOByE39/3oIokDs/BnkqI+ltwirZbP8HZdq1/3SkPSBi7xDdvWHTp7cpNI9ullozkR6mwTWQi6/E/9huQVrA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog/node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-footer": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/rc-footer/-/rc-footer-0.6.8.tgz", + "integrity": "sha512-JBZ+xcb6kkex8XnBd4VHw1ZxjV6kmcwUumSHaIFdka2qzMCo7Klcy4sI6G0XtUpG/vtpislQCc+S9Bc+NLHYMg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image/node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu/node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu/node_modules/@rc-component/trigger": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.1.tgz", + "integrity": "sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.5.0.tgz", + "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT", + "peer": true + }, + "node_modules/re-resizable": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", + "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react": { "version": "19.2.4", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/react-avatar-editor": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/react-avatar-editor/-/react-avatar-editor-14.0.0.tgz", + "integrity": "sha512-NaQM3oo4u0a1/Njjutc2FjwKX35vQV+t6S8hovsbAlMpBN1ntIwP/g+Yr9eDIIfaNtRXL0AqboTnPmRxhD/i8A==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "license": "MIT", + "peer": true, + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-draggable/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" } }, - "node_modules/react-dom": { - "version": "19.2.4", + "node_modules/react-dropzone": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.1.0.tgz", + "integrity": "sha512-iBYHA1rbopIvtzokEX4QubO6qk5IF/x3BtKGu74rF2JkQDXnwC4uO/lHKpaw4PJIV6iIAYOlwLv2FpiGyqHNog==", "license": "MIT", + "peer": true, "dependencies": { - "scheduler": "^0.27.0" + "attr-accept": "^2.2.2", + "file-selector": "^0.5.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" }, "peerDependencies": { - "react": "^19.2.4" + "react": ">= 16.8" + } + }, + "node_modules/react-error-boundary": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.1.tgz", + "integrity": "sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT", + "peer": true + }, "node_modules/react-hook-form": { "version": "7.71.1", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz", @@ -4042,6 +8779,17 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-hotkeys-hook": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-5.2.4.tgz", + "integrity": "sha512-BgKg+A1+TawkYluh5Bo4cTmcgMN5L29uhJbDUQdHwPX+qgXRjIPYU5kIDHyxnAwCkCBiu9V5OpB2mpyeluVF2A==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-is": { "version": "19.2.4", "license": "MIT", @@ -4072,6 +8820,25 @@ "react": ">=18" } }, + "node_modules/react-merge-refs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-3.0.2.tgz", + "integrity": "sha512-MSZAfwFfdbEvwkKWP5EI5chuLYnNUxNS7vyS0i1Jp+wtd8J4Ga2ddzhaE68aMol2Z4vCnRM/oGOo1a3V75UPlw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "react": ">=16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/react-redux": { "version": "9.2.0", "license": "MIT", @@ -4148,6 +8915,29 @@ } } }, + "node_modules/react-rnd": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.5.2.tgz", + "integrity": "sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==", + "license": "MIT", + "peer": true, + "dependencies": { + "re-resizable": "6.11.2", + "react-draggable": "4.4.6", + "tslib": "2.6.2" + }, + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, + "node_modules/react-rnd/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD", + "peer": true + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -4170,6 +8960,21 @@ } } }, + "node_modules/react-zoom-pan-pinch": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.7.0.tgz", + "integrity": "sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/read-cache": { "version": "1.0.0", "dev": true, @@ -4218,6 +9023,77 @@ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "peer": true, + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/redux": { "version": "5.0.1", "license": "MIT" @@ -4229,6 +9105,139 @@ "redux": "^5.0.0" } }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "peer": true, + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT", + "peer": true + }, + "node_modules/rehype-github-alerts": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/rehype-github-alerts/-/rehype-github-alerts-4.2.0.tgz", + "integrity": "sha512-6di6kEu9WUHKLKrkKG2xX6AOuaCMGghg0Wq7MEuM/jBYUPVIq6PJpMe00dxMfU+/YSBtDXhffpDimgDi+BObIQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@primer/octicons": "^19.20.0", + "hast-util-from-html": "^2.0.3", + "hast-util-is-element": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/chrisweb" + } + }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-breaks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", + "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-newline-to-break": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-cjk-friendly": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/remark-cjk-friendly/-/remark-cjk-friendly-1.2.3.tgz", + "integrity": "sha512-UvAgxwlNk+l9Oqgl/9MWK2eWRS7zgBW/nXX9AthV7nd/3lNejF138E7Xbmk9Zs4WjTJGs721r7fAEc7tNFoH7g==", + "license": "MIT", + "peer": true, + "dependencies": { + "micromark-extension-cjk-friendly": "1.2.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@types/mdast": "^4.0.0", + "unified": "^11.0.0" + }, + "peerDependenciesMeta": { + "@types/mdast": { + "optional": true + } + } + }, "node_modules/remark-gfm": { "version": "4.0.1", "license": "MIT", @@ -4245,6 +9254,57 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-github": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/remark-github/-/remark-github-12.0.0.tgz", + "integrity": "sha512-ByefQKFN184LeiGRCabfl7zUJsdlMYWEhiLX1gpmQ11yFg6xSuOTW7LVCv0oc1x+YvUMJW23NU36sJX2RWGgvg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "to-vfile": "^8.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "peer": true, + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-parse": { "version": "11.0.0", "license": "MIT", @@ -4287,13 +9347,26 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remend": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/remend/-/remend-1.2.1.tgz", + "integrity": "sha512-4wC12bgXsfKAjF1ewwkNIQz5sqewz/z1xgIgjEMb3r1pEytQ37F0Cm6i+OhbTWEvguJD7lhOUJhK5fSasw9f0w==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/reselect": { "version": "5.1.1", "license": "MIT" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT", + "peer": true + }, "node_modules/resolve": { "version": "1.22.11", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -4310,6 +9383,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.1.0", "dev": true, @@ -4319,6 +9401,13 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense", + "peer": true + }, "node_modules/rollup": { "version": "4.57.1", "dev": true, @@ -4362,6 +9451,19 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "peer": true, + "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/run-parallel": { "version": "1.2.0", "dev": true, @@ -4384,6 +9486,20 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "peer": true + }, "node_modules/sass": { "version": "1.97.3", "dev": true, @@ -4407,6 +9523,29 @@ "version": "0.27.0", "license": "MIT" }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, "node_modules/semver": { "version": "6.3.1", "dev": true, @@ -4415,6 +9554,13 @@ "semver": "bin/semver.js" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "license": "MIT", + "peer": true + }, "node_modules/seroval": { "version": "1.5.0", "license": "MIT", @@ -4432,6 +9578,78 @@ "seroval": "^1.0" } }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shiki": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", + "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/core": "3.22.0", + "@shikijs/engine-javascript": "3.22.0", + "@shikijs/engine-oniguruma": "3.22.0", + "@shikijs/langs": "3.22.0", + "@shikijs/themes": "3.22.0", + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/shiki-stream": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/shiki-stream/-/shiki-stream-0.1.4.tgz", + "integrity": "sha512-4pz6JGSDmVTTkPJ/ueixHkFAXY4ySCc+unvCaDZV7hqq/sdJZirRxgIXSuNSKgiFlGTgRR97sdu2R8K55sPsrw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/core": "^3.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "react": "^19.0.0", + "solid-js": "^1.9.0", + "vue": "^3.2.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, "node_modules/sigma": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sigma/-/sigma-3.0.2.tgz", @@ -4442,6 +9660,18 @@ "graphology-utils": "^2.5.2" } }, + "node_modules/smol-toml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", + "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/sonner": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", @@ -4452,6 +9682,16 @@ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "dev": true, @@ -4468,6 +9708,53 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/split-on-first": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz", + "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT", + "peer": true + }, "node_modules/stringify-entities": { "version": "4.0.4", "license": "MIT", @@ -4480,6 +9767,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/style-to-js": { "version": "1.1.21", "license": "MIT", @@ -4494,6 +9787,12 @@ "inline-style-parser": "0.2.7" } }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.1", "dev": true, @@ -4517,7 +9816,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4526,6 +9824,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.4.0.tgz", + "integrity": "sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw==", + "license": "MIT", + "peer": true, + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT", + "peer": true + }, "node_modules/tailwindcss": { "version": "3.4.19", "dev": true, @@ -4657,6 +9976,16 @@ "node": ">=0.8" } }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.22" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "license": "MIT" @@ -4665,6 +9994,16 @@ "version": "1.0.3", "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "dev": true, @@ -4691,6 +10030,20 @@ "node": ">=8.0" } }, + "node_modules/to-vfile": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-8.0.0.tgz", + "integrity": "sha512-IcmH1xB5576MJc9qcfEC/m/nQCFt3fzMHz45sSlgJyTWjRbKW1HAkJpuf3DgE57YzIlZcwcBZA5ENQbBo4aLkg==", + "license": "MIT", + "peer": true, + "dependencies": { + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "license": "MIT", @@ -4707,11 +10060,31 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.10" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "dev": true, "license": "Apache-2.0" }, + "node_modules/ts-md5": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-2.0.1.tgz", + "integrity": "sha512-yF35FCoEOFBzOclSkMNEUbFQZuv89KEQ+5Xz03HrMSGUGB1+r+El+JiGOFwsP4p9RFNzwlrydYoTLvPOuICl9w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/tslib": { "version": "2.8.1", "license": "0BSD" @@ -4728,6 +10101,13 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT", + "peer": true + }, "node_modules/unified": { "version": "11.0.5", "license": "MIT", @@ -4745,6 +10125,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.1", "license": "MIT", @@ -4767,6 +10162,35 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "license": "MIT", @@ -4832,6 +10256,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -4853,6 +10287,15 @@ } } }, + "node_modules/use-merge-value": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-merge-value/-/use-merge-value-1.2.0.tgz", + "integrity": "sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.x" + } + }, "node_modules/use-sidecar": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", @@ -4887,6 +10330,27 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/v8n": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/v8n/-/v8n-1.5.1.tgz", + "integrity": "sha512-LdabyT4OffkyXFCe9UT+uMkxNBs5rcTVuZClvxQr08D5TUgo1OFKkoT65qYRCsiKBl/usHjpXvP4hHMzzDRj3A==", + "license": "MIT", + "peer": true + }, "node_modules/vfile": { "version": "6.0.3", "license": "MIT", @@ -4899,6 +10363,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vfile-message": { "version": "4.0.3", "license": "MIT", @@ -4931,6 +10410,37 @@ "d3-timer": "^3.0.1" } }, + "node_modules/virtua": { + "version": "0.48.6", + "resolved": "https://registry.npmjs.org/virtua/-/virtua-0.48.6.tgz", + "integrity": "sha512-Cl4uMvMV5c9RuOy9zhkFMYwx/V4YLBMYLRSWkO8J46opQZ3P7KMq0CqCVOOAKUckjl/r//D2jWTBGYWzmgtzrQ==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0", + "solid-js": ">=1.0", + "svelte": ">=5.0", + "vue": ">=3.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, "node_modules/vite": { "version": "6.4.1", "dev": true, @@ -5004,6 +10514,78 @@ } } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "peer": true, + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "peer": true, + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT", + "peer": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT", + "peer": true + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT", + "peer": true + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/yallist": { "version": "3.1.1", "dev": true, @@ -5018,6 +10600,34 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "license": "MIT", diff --git a/interface/package.json b/interface/package.json index 4d4f91b56..249dbad30 100644 --- a/interface/package.json +++ b/interface/package.json @@ -41,6 +41,7 @@ "@tanstack/react-router": "^1.159.5", "@tanstack/react-virtual": "^3.13.18", "@tanstack/router-devtools": "^1.159.5", + "@xyflow/react": "^12.10.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "codemirror": "^6.0.2", diff --git a/interface/src/api/client.ts b/interface/src/api/client.ts index 8a320a265..69c7d4a3d 100644 --- a/interface/src/api/client.ts +++ b/interface/src/api/client.ts @@ -943,6 +943,58 @@ export interface RawConfigUpdateResponse { message: string; } +// -- Agent Links & Topology -- + +export type LinkDirection = "one_way" | "two_way"; +export type LinkRelationship = "peer" | "superior" | "subordinate"; + +export interface AgentLinkResponse { + from_agent_id: string; + to_agent_id: string; + direction: LinkDirection; + relationship: LinkRelationship; +} + +export interface LinksResponse { + links: AgentLinkResponse[]; +} + +export interface TopologyAgent { + id: string; + name: string; +} + +export interface TopologyLink { + from: string; + to: string; + direction: string; + relationship: string; +} + +export interface TopologyResponse { + agents: TopologyAgent[]; + links: TopologyLink[]; +} + +export interface CreateLinkRequest { + from: string; + to: string; + direction?: LinkDirection; + relationship?: LinkRelationship; +} + +export interface UpdateLinkRequest { + direction?: LinkDirection; + relationship?: LinkRelationship; +} + +export interface AgentMessageEvent { + from_agent_id: string; + to_agent_id: string; + link_id: string; + channel_id: string; +} + export const api = { status: () => fetchJson("/status"), overview: () => fetchJson("/overview"), @@ -1366,6 +1418,46 @@ export const api = { `/skills/registry/search?q=${encodeURIComponent(query)}&limit=${limit}`, ), + // Agent Links & Topology API + topology: () => fetchJson("/topology"), + links: () => fetchJson("/links"), + agentLinks: (agentId: string) => + fetchJson(`/agents/${encodeURIComponent(agentId)}/links`), + createLink: async (request: CreateLinkRequest): Promise<{ link: AgentLinkResponse }> => { + const response = await fetch(`${API_BASE}/links`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + }); + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + return response.json(); + }, + updateLink: async (from: string, to: string, request: UpdateLinkRequest): Promise<{ link: AgentLinkResponse }> => { + const response = await fetch( + `${API_BASE}/links/${encodeURIComponent(from)}/${encodeURIComponent(to)}`, + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + }, + ); + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + return response.json(); + }, + deleteLink: async (from: string, to: string): Promise => { + const response = await fetch( + `${API_BASE}/links/${encodeURIComponent(from)}/${encodeURIComponent(to)}`, + { method: "DELETE" }, + ); + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + }, + // Web Chat API webChatSend: (agentId: string, sessionId: string, message: string, senderName?: string) => fetch(`${API_BASE}/webchat/send`, { diff --git a/interface/src/components/TopologyGraph.tsx b/interface/src/components/TopologyGraph.tsx new file mode 100644 index 000000000..1bc1aeefa --- /dev/null +++ b/interface/src/components/TopologyGraph.tsx @@ -0,0 +1,742 @@ +import { useCallback, useMemo, useRef, useState } from "react"; +import { + ReactFlow, + Background, + Controls, + type Node, + type Edge, + type Connection, + type NodeTypes, + type EdgeTypes, + type NodeProps, + type EdgeProps, + useNodesState, + useEdgesState, + MarkerType, + Handle, + Position, + BaseEdge, + getSmoothStepPath, + ReactFlowProvider, +} from "@xyflow/react"; +import "@xyflow/react/dist/style.css"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { AnimatePresence, motion } from "framer-motion"; +import { + api, + type AgentSummary, + type TopologyResponse, + type LinkDirection, + type LinkRelationship, +} from "@/api/client"; +import { Button } from "@/ui"; +import { Link } from "@tanstack/react-router"; + +// -- Colors -- + +const RELATIONSHIP_COLORS: Record = { + peer: "#6366f1", + superior: "#f59e0b", + subordinate: "#22c55e", +}; + +const RELATIONSHIP_LABELS: Record = { + peer: "Peer", + superior: "Superior", + subordinate: "Subordinate", +}; + +/** Deterministic gradient from a seed string. */ +function seedGradient(seed: string): [string, string] { + let hash = 0; + for (let i = 0; i < seed.length; i++) { + hash = seed.charCodeAt(i) + ((hash << 5) - hash); + hash |= 0; + } + const hue1 = (hash >>> 0) % 360; + const hue2 = (hue1 + 40 + ((hash >>> 8) % 60)) % 360; + return [`hsl(${hue1}, 70%, 55%)`, `hsl(${hue2}, 60%, 45%)`]; +} + +// -- Custom Node: Agent Profile Card -- + +const NODE_WIDTH = 240; + +function AgentNode({ data, selected }: NodeProps) { + const avatarSeed = (data.avatarSeed as string) ?? (data.agentId as string); + const [c1, c2] = seedGradient(avatarSeed); + const displayName = (data.displayName as string) ?? (data.agentId as string); + const status = data.status as string | null; + const bio = data.bio as string | null; + const isOnline = data.isOnline as boolean; + const channelCount = data.channelCount as number; + const memoryCount = data.memoryCount as number; + const agentId = data.agentId as string; + + return ( +
+ {/* Gradient banner */} +
+ + + + + + + + + +
+ + {/* Avatar overlapping banner */} +
+
+ + + + + + + + + +
+
+
+ + {/* Profile content */} +
+ {/* Name + link to agent */} + e.stopPropagation()} + > + {displayName} + + + {/* Status line */} + {status && ( +

+ {status} +

+ )} + + {/* Bio */} + {bio && ( +

+ {bio} +

+ )} + + {/* Stats */} +
+ + {channelCount} channels + + + + {memoryCount >= 1000 ? `${(memoryCount / 1000).toFixed(1)}k` : memoryCount} + memories + +
+
+ + {/* Handles */} + + +
+ ); +} + +// -- Custom Edge -- + +function LinkEdge({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + data, + selected, +}: EdgeProps) { + const [edgePath, labelX, labelY] = getSmoothStepPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + borderRadius: 16, + }); + + const relationship = (data?.relationship as string) ?? "peer"; + const color = RELATIONSHIP_COLORS[relationship] ?? "#6366f1"; + const isActive = data?.active as boolean; + + return ( + <> + + {/* Edge label */} + +
+ + {RELATIONSHIP_LABELS[relationship] ?? relationship} + +
+
+ {/* Activity pulse */} + {isActive && ( + + + + )} + + ); +} + +const nodeTypes: NodeTypes = { + agent: AgentNode, +}; + +const edgeTypes: EdgeTypes = { + link: LinkEdge, +}; + +// -- Edge Config Panel -- + +interface EdgeConfigPanelProps { + edge: Edge; + onUpdate: (direction: LinkDirection, relationship: LinkRelationship) => void; + onDelete: () => void; + onClose: () => void; +} + +function EdgeConfigPanel({ + edge, + onUpdate, + onDelete, + onClose, +}: EdgeConfigPanelProps) { + const [direction, setDirection] = useState( + (edge.data?.direction as LinkDirection) ?? "two_way", + ); + const [relationship, setRelationship] = useState( + (edge.data?.relationship as LinkRelationship) ?? "peer", + ); + + return ( + +
+ Link Settings + +
+ +
+ {edge.source} → {edge.target} +
+ + {/* Direction */} +
+ +
+ {(["one_way", "two_way"] as const).map((d) => ( + + ))} +
+
+ + {/* Relationship */} +
+ +
+ {(["peer", "superior", "subordinate"] as const).map((r) => ( + + ))} +
+
+ +
+ + +
+
+ ); +} + +// -- Main Component (inner, needs ReactFlowProvider) -- + +interface TopologyGraphInnerProps { + activeEdges: Set; + agents: AgentSummary[]; +} + +function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { + const queryClient = useQueryClient(); + const [selectedEdge, setSelectedEdge] = useState(null); + + const { data, isLoading, error } = useQuery({ + queryKey: ["topology"], + queryFn: api.topology, + refetchInterval: 10_000, + }); + + // Build agent profile lookup + const agentProfiles = useMemo(() => { + const map = new Map(); + for (const agent of agents) { + map.set(agent.id, agent); + } + return map; + }, [agents]); + + // Build nodes and edges from topology data + const { initialNodes, initialEdges } = useMemo(() => { + if (!data) return { initialNodes: [], initialEdges: [] }; + return buildGraph(data, activeEdges, agentProfiles); + }, [data, activeEdges, agentProfiles]); + + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + + // Sync when topology data or agent profiles change + const prevDataRef = useRef(data); + const prevProfilesRef = useRef(agentProfiles); + if (data !== prevDataRef.current || agentProfiles !== prevProfilesRef.current) { + prevDataRef.current = data; + prevProfilesRef.current = agentProfiles; + if (data) { + const { initialNodes: newNodes, initialEdges: newEdges } = buildGraph( + data, + activeEdges, + agentProfiles, + ); + + // Preserve existing node positions + const positionMap = new Map( + nodes.map((n) => [n.id, n.position]), + ); + const mergedNodes = newNodes.map((n) => ({ + ...n, + position: positionMap.get(n.id) ?? n.position, + })); + + setNodes(mergedNodes); + setEdges(newEdges); + } + } + + // Update edge activity state when activeEdges changes + const prevActiveRef = useRef(activeEdges); + if (activeEdges !== prevActiveRef.current) { + prevActiveRef.current = activeEdges; + setEdges((eds) => + eds.map((e) => ({ + ...e, + data: { ...e.data, active: activeEdges.has(e.id) }, + })), + ); + } + + // Mutations + const createLink = useMutation({ + mutationFn: (params: { + from: string; + to: string; + direction: LinkDirection; + relationship: LinkRelationship; + }) => + api.createLink({ + from: params.from, + to: params.to, + direction: params.direction, + relationship: params.relationship, + }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["topology"] }); + }, + }); + + const updateLink = useMutation({ + mutationFn: (params: { + from: string; + to: string; + direction: LinkDirection; + relationship: LinkRelationship; + }) => + api.updateLink(params.from, params.to, { + direction: params.direction, + relationship: params.relationship, + }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["topology"] }); + setSelectedEdge(null); + }, + }); + + const deleteLink = useMutation({ + mutationFn: (params: { from: string; to: string }) => + api.deleteLink(params.from, params.to), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["topology"] }); + setSelectedEdge(null); + }, + }); + + // Handle new connection (drag from handle to handle) + const onConnect = useCallback( + (connection: Connection) => { + if (!connection.source || !connection.target) return; + if (connection.source === connection.target) return; + + // Check if link already exists + const exists = edges.some( + (e) => + e.source === connection.source && e.target === connection.target, + ); + if (exists) return; + + createLink.mutate({ + from: connection.source, + to: connection.target, + direction: "two_way", + relationship: "peer", + }); + }, + [edges, createLink], + ); + + const onEdgeClick = useCallback( + (_: React.MouseEvent, edge: Edge) => { + setSelectedEdge(edge); + }, + [], + ); + + const onPaneClick = useCallback(() => { + setSelectedEdge(null); + }, []); + + if (isLoading) { + return ( +
+
+
+ Loading topology... +
+
+ ); + } + + if (error) { + return ( +
+

+ Failed to load topology +

+
+ ); + } + + if (!data || data.agents.length === 0) { + return ( +
+

+ No agents configured +

+
+ ); + } + + return ( +
+ + + + + + {/* Legend */} +
+
+ Relationships +
+
+ {Object.entries(RELATIONSHIP_COLORS).map( + ([type, color]) => ( +
+ + + {type} + +
+ ), + )} +
+
+ Drag between nodes to create links +
+
+ + {/* Edge config panel */} + + {selectedEdge && ( + + updateLink.mutate({ + from: selectedEdge.source, + to: selectedEdge.target, + direction, + relationship, + }) + } + onDelete={() => + deleteLink.mutate({ + from: selectedEdge.source, + to: selectedEdge.target, + }) + } + onClose={() => setSelectedEdge(null)} + /> + )} + +
+ ); +} + +// -- Graph Builder -- + +function buildGraph( + data: TopologyResponse, + activeEdges: Set, + agentProfiles: Map, +): { initialNodes: Node[]; initialEdges: Edge[] } { + const agentCount = data.agents.length; + + // Arrange agents in a circle for initial layout + const radius = Math.max(200, agentCount * 80); + const centerX = 400; + const centerY = 300; + + const initialNodes: Node[] = data.agents.map((agent, index) => { + const angle = (2 * Math.PI * index) / agentCount - Math.PI / 2; + const summary = agentProfiles.get(agent.id); + const profile = summary?.profile; + const isOnline = + summary?.last_activity_at != null && + new Date(summary.last_activity_at).getTime() > Date.now() - 5 * 60 * 1000; + + return { + id: agent.id, + type: "agent", + position: { + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }, + data: { + agentId: agent.id, + displayName: profile?.display_name ?? agent.name, + avatarSeed: profile?.avatar_seed ?? agent.id, + status: profile?.status ?? null, + bio: profile?.bio ?? null, + isOnline, + channelCount: summary?.channel_count ?? 0, + memoryCount: summary?.memory_total ?? 0, + }, + }; + }); + + const initialEdges: Edge[] = data.links.map((link) => { + const edgeId = `${link.from}->${link.to}`; + return { + id: edgeId, + source: link.from, + target: link.to, + type: "link", + data: { + direction: link.direction, + relationship: link.relationship, + active: activeEdges.has(edgeId), + }, + markerEnd: + link.direction === "one_way" + ? { + type: MarkerType.ArrowClosed, + width: 16, + height: 16, + color: + RELATIONSHIP_COLORS[link.relationship] ?? + "#6366f1", + } + : undefined, + style: { + stroke: + RELATIONSHIP_COLORS[link.relationship] ?? "#6366f1", + }, + }; + }); + + return { initialNodes, initialEdges }; +} + +// -- Exported component with provider wrapper -- + +export interface TopologyGraphProps { + activeEdges?: Set; + agents?: AgentSummary[]; +} + +export function TopologyGraph({ activeEdges, agents }: TopologyGraphProps) { + const edges = activeEdges ?? new Set(); + const agentList = agents ?? []; + return ( + + + + ); +} diff --git a/interface/src/hooks/useLiveContext.tsx b/interface/src/hooks/useLiveContext.tsx index e48305c5c..95d79c74f 100644 --- a/interface/src/hooks/useLiveContext.tsx +++ b/interface/src/hooks/useLiveContext.tsx @@ -1,6 +1,6 @@ -import { createContext, useContext, useCallback, type ReactNode } from "react"; +import { createContext, useContext, useCallback, useRef, useState, useMemo, type ReactNode } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { api, type ChannelInfo } from "@/api/client"; +import { api, type AgentMessageEvent, type ChannelInfo } from "@/api/client"; import { useEventSource, type ConnectionState } from "@/hooks/useEventSource"; import { useChannelLiveState, type ChannelLiveState } from "@/hooks/useChannelLiveState"; @@ -10,6 +10,8 @@ interface LiveContextValue { connectionState: ConnectionState; hasData: boolean; loadOlderMessages: (channelId: string) => void; + /** Set of edge IDs ("from->to") with recent message activity */ + activeLinks: Set; } const LiveContext = createContext({ @@ -18,12 +20,16 @@ const LiveContext = createContext({ connectionState: "connecting", hasData: false, loadOlderMessages: () => {}, + activeLinks: new Set(), }); export function useLiveContext() { return useContext(LiveContext); } +/** Duration (ms) an edge stays "active" after a message flows through it. */ +const LINK_ACTIVE_DURATION = 3000; + export function LiveContextProvider({ children }: { children: ReactNode }) { const queryClient = useQueryClient(); @@ -34,7 +40,57 @@ export function LiveContextProvider({ children }: { children: ReactNode }) { }); const channels = channelsData?.channels ?? []; - const { liveStates, handlers, syncStatusSnapshot, loadOlderMessages } = useChannelLiveState(channels); + const { liveStates, handlers: channelHandlers, syncStatusSnapshot, loadOlderMessages } = useChannelLiveState(channels); + + // Track recently active link edges + const [activeLinks, setActiveLinks] = useState>(new Set()); + const timersRef = useRef>>(new Map()); + + const markEdgeActive = useCallback((from: string, to: string) => { + const edgeId = `${from}->${to}`; + setActiveLinks((prev) => { + const next = new Set(prev); + next.add(edgeId); + return next; + }); + + // Clear existing timer for this edge + const existing = timersRef.current.get(edgeId); + if (existing) clearTimeout(existing); + + // Set expiry timer + timersRef.current.set( + edgeId, + setTimeout(() => { + timersRef.current.delete(edgeId); + setActiveLinks((prev) => { + const next = new Set(prev); + next.delete(edgeId); + return next; + }); + }, LINK_ACTIVE_DURATION), + ); + }, []); + + const handleAgentMessage = useCallback( + (data: unknown) => { + const event = data as AgentMessageEvent; + if (event.from_agent_id && event.to_agent_id) { + markEdgeActive(event.from_agent_id, event.to_agent_id); + } + }, + [markEdgeActive], + ); + + // Merge channel handlers with agent message handlers + const handlers = useMemo( + () => ({ + ...channelHandlers, + agent_message_sent: handleAgentMessage, + agent_message_received: handleAgentMessage, + }), + [channelHandlers, handleAgentMessage], + ); const onReconnect = useCallback(() => { syncStatusSnapshot(); @@ -52,7 +108,7 @@ export function LiveContextProvider({ children }: { children: ReactNode }) { const hasData = channels.length > 0 || channelsData !== undefined; return ( - + {children} ); diff --git a/interface/src/router.tsx b/interface/src/router.tsx index de3f4a940..1c6950f05 100644 --- a/interface/src/router.tsx +++ b/interface/src/router.tsx @@ -67,8 +67,8 @@ const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: "/", component: function IndexPage() { - const {liveStates} = useLiveContext(); - return ; + const {liveStates, activeLinks} = useLiveContext(); + return ; }, }); diff --git a/interface/src/routes/Overview.tsx b/interface/src/routes/Overview.tsx index cc7da9f24..4196aa501 100644 --- a/interface/src/routes/Overview.tsx +++ b/interface/src/routes/Overview.tsx @@ -1,17 +1,17 @@ import { useMemo, useState } from "react"; -import { Link } from "@tanstack/react-router"; import { useQuery } from "@tanstack/react-query"; -import { api, type AgentSummary } from "@/api/client"; +import { api } from "@/api/client"; import { CreateAgentDialog } from "@/components/CreateAgentDialog"; +import { TopologyGraph } from "@/components/TopologyGraph"; import type { ChannelLiveState } from "@/hooks/useChannelLiveState"; -import { formatTimeAgo, formatUptime } from "@/lib/format"; -import { ResponsiveContainer, AreaChart, Area } from "recharts"; +import { formatUptime } from "@/lib/format"; interface OverviewProps { liveStates: Record; + activeLinks?: Set; } -export function Overview({ liveStates }: OverviewProps) { +export function Overview({ liveStates, activeLinks }: OverviewProps) { const [createOpen, setCreateOpen] = useState(false); const { data: statusData } = useQuery({ @@ -39,453 +39,92 @@ export function Overview({ liveStates }: OverviewProps) { const activity = useMemo(() => { let workers = 0; let branches = 0; - let typing = 0; for (const state of Object.values(liveStates)) { workers += Object.keys(state.workers).length; branches += Object.keys(state.branches).length; - if (state.isTyping) typing++; } - return { workers, branches, typing }; + return { workers, branches }; }, [liveStates]); - // Get live activity for a specific agent - const getAgentActivity = (agentId: string) => { - let workers = 0; - let branches = 0; - for (const channel of channels) { - if (channel.agent_id !== agentId) continue; - const live = liveStates[channel.id]; - if (!live) continue; - workers += Object.keys(live.workers).length; - branches += Object.keys(live.branches).length; - } - return { workers, branches, hasActivity: workers > 0 || branches > 0 }; - }; - - // Recent channels (sorted by last activity, max 6) - const recentChannels = useMemo(() => { - return [...channels] - .sort((a, b) => new Date(b.last_activity_at).getTime() - new Date(a.last_activity_at).getTime()) - .slice(0, 6); - }, [channels]); + const uptime = statusData?.uptime_seconds ?? 0; return (
- {/* Instance Hero */} - - - {/* Content */} -
- {overviewLoading ? ( -
-
- Loading dashboard... + {/* Compact status bar */} +
+
+
+

Spacebot

+ {statusData ? ( +
+ ) : ( +
+ )}
- ) : agents.length === 0 ? ( -
-

No agents configured.

- - + +
+ {agents.length} agent{agents.length !== 1 ? "s" : ""} + {channels.length} channel{channels.length !== 1 ? "s" : ""} + {formatUptime(uptime)}
- ) : ( -
- {/* Agent Cards */} -
-
-

Agents

- -
-
- {agents.map((agent) => ( - - ))} -
- -
- {/* Recent Channels */} - {recentChannels.length > 0 && ( -
-
-

Recent Activity

- {channels.length} total channels -
-
- {recentChannels.map((channel) => ( - - ))} -
-
+ {(activity.workers > 0 || activity.branches > 0) && ( +
+ {activity.workers > 0 && ( + + + + {activity.workers}w + + + )} + {activity.branches > 0 && ( + + + + {activity.branches}b + + )}
)} -
-
- ); -} - -// -- Components -- +
-function HeroSection({ - status, - totalChannels, - totalAgents, - activity, -}: { - status: { status: string; pid: number; uptime_seconds: number } | undefined; - totalChannels: number; - totalAgents: number; - activity: { workers: number; branches: number; typing: number }; -}) { - const uptime = status?.uptime_seconds ?? 0; + +
- return ( -
-
-
- {/* Title row */} -
-
-

Spacebot

- {status ? ( -
-
- Running -
- ) : ( -
-
- Unreachable -
- )} + {/* Full-screen topology */} +
+ {overviewLoading ? ( +
+
+
+ Loading...
- - - {formatUptime(uptime)} uptime -
- - {/* Stats row */} -
-
-
- {totalAgents} - agent{totalAgents !== 1 ? "s" : ""} -
-
- {totalChannels} - channel{totalChannels !== 1 ? "s" : ""} -
+ ) : agents.length === 0 ? ( +
+
+

No agents configured

+
- - {(activity.workers > 0 || activity.branches > 0) && ( -
- {activity.workers > 0 && ( -
-
- - {activity.workers} worker{activity.workers !== 1 ? "s" : ""} - -
- )} - {activity.branches > 0 && ( -
-
- - {activity.branches} branch{activity.branches !== 1 ? "es" : ""} - -
- )} -
- )}
-
-
-
- ); -} - -/** Deterministic gradient from a seed string. Produces two HSL colors. */ -function seedGradient(seed: string): [string, string] { - let hash = 0; - for (let i = 0; i < seed.length; i++) { - hash = seed.charCodeAt(i) + ((hash << 5) - hash); - hash |= 0; - } - const hue1 = ((hash >>> 0) % 360); - const hue2 = (hue1 + 40 + ((hash >>> 8) % 60)) % 360; - return [ - `hsl(${hue1}, 70%, 55%)`, - `hsl(${hue2}, 60%, 45%)`, - ]; -} - -function AgentAvatar({ seed, size = 64 }: { seed: string; size?: number }) { - const [c1, c2] = seedGradient(seed); - const gradientId = `avatar-${seed.replace(/[^a-z0-9]/gi, "")}`; - return ( - - - - - - - - - - ); -} - -/** Gradient banner SVG that fills the card top. Uses the same seed as the avatar. */ -function BannerGradient({ seed }: { seed: string }) { - const [c1, c2] = seedGradient(seed); - const bannerId = `banner-${seed.replace(/[^a-z0-9]/gi, "")}`; - return ( - - - - - - - - - - ); -} - -function AgentCard({ - agent, - liveActivity, -}: { - agent: AgentSummary; - liveActivity: { workers: number; branches: number; hasActivity: boolean }; -}) { - const isActive = liveActivity.hasActivity || (agent.last_activity_at && new Date(agent.last_activity_at).getTime() > Date.now() - 5 * 60 * 1000); - const profile = agent.profile; - const displayName = profile?.display_name ?? agent.id; - const avatarSeed = profile?.avatar_seed ?? agent.id; - - return ( - - {/* Banner + Avatar */} -
- - {/* Live activity badges — top right */} - {(liveActivity.workers > 0 || liveActivity.branches > 0) && ( -
- {liveActivity.workers > 0 && ( - - - {liveActivity.workers}w - - )} - {liveActivity.branches > 0 && ( - - - {liveActivity.branches}b - - )} -
- )} -
- - {/* Avatar — overlapping the banner */} -
-
-
- -
-
-
-
- - {/* Name + status */} -
-

- {displayName} -

- {profile?.status ? ( -

- {profile.status} -

- ) : agent.last_activity_at ? ( -

- Active {formatTimeAgo(agent.last_activity_at)} -

- ) : null} -
- - {/* Bio — full text, no truncation */} - {profile?.bio ? ( -

- {profile.bio} -

- ) : ( -

- This agent will fill out its own profile as it develops a personality through conversations. -

- )} - - {/* Spacer pushes footer down when bio is short */} -
- - {/* Sparkline */} - {agent.activity_sparkline?.some((v) => v > 0) && ( -
- -
- )} - - {/* Stats footer */} -
- - - {agent.cron_job_count > 0 && ( - - )} - {agent.last_bulletin_at && ( - - Bulletin {formatTimeAgo(agent.last_bulletin_at)} - + ) : ( + )}
- - ); -} - -function StatPill({ label, value }: { label: string; value: number }) { - return ( - - - {value >= 1000 ? `${(value / 1000).toFixed(1)}k` : value} - - {" "}{label} - - ); -} - -const CHART_COLORS = { - accent: "#6366f1", - accentBg: "#1e1b4b", -}; - -function SparklineChart({ data }: { data: number[] }) { - if (data.length === 0 || data.every((v) => v === 0)) { - return
; - } - - // Simple line chart without axes - just the sparkline - const chartData = data.map((value, idx) => ({ idx, value })); - const hasGradient = data.length > 0; - - return ( - - - {hasGradient && ( - - - - - - - )} - - - - ); -} -// -- Channel Card (lightweight version) -- - -interface ChannelInfo { - id: string; - agent_id: string; - platform: string; - display_name: string | null; - last_activity_at: string; -} - -function ChannelCard({ - channel, - liveState, -}: { - channel: ChannelInfo; - liveState: ChannelLiveState | undefined; -}) { - const isTyping = liveState?.isTyping ?? false; - const workers = Object.keys(liveState?.workers ?? {}).length; - const branches = Object.keys(liveState?.branches ?? {}).length; - const hasActivity = workers > 0 || branches > 0; - - return ( - -
-

- {channel.display_name ?? channel.id} -

-
-
-
-
-
- {channel.platform} - {formatTimeAgo(channel.last_activity_at)} - {hasActivity && ( - - {workers > 0 && `${workers}w`} - {workers > 0 && branches > 0 && " "} - {branches > 0 && `${branches}b`} - - )} -
- + +
); } diff --git a/interface/src/ui/style/colors.scss b/interface/src/ui/style/colors.scss index 4067c12de..5a6270348 100644 --- a/interface/src/ui/style/colors.scss +++ b/interface/src/ui/style/colors.scss @@ -1,56 +1,56 @@ :root { - --dark-hue: 235; - --light-hue: 235; + --dark-hue: 230; + --light-hue: 230; // global --color-black: 0, 0%, 0%; --color-white: 0, 0%, 100%; // accent theme colors - --color-accent: 208, 100%, 57%; - --color-accent-faint: 208, 100%, 64%; - --color-accent-deep: 208, 100%, 47%; + --color-accent: 282, 70%, 57%; + --color-accent-faint: 282, 65%, 64%; + --color-accent-deep: 282, 75%, 48%; // text - --color-ink: var(--dark-hue), 35%, 92%; - --color-ink-dull: var(--dark-hue), 10%, 70%; - --color-ink-faint: var(--dark-hue), 10%, 55%; + --color-ink: var(--dark-hue), 15%, 94%; + --color-ink-dull: var(--dark-hue), 8%, 65%; + --color-ink-faint: var(--dark-hue), 5%, 45%; // sidebar - --color-sidebar: var(--dark-hue), 15%, 7%; - --color-sidebar-box: var(--dark-hue), 15%, 16%; - --color-sidebar-line: var(--dark-hue), 15%, 23%; - --color-sidebar-ink: var(--dark-hue), 15%, 92%; - --color-sidebar-ink-dull: var(--dark-hue), 10%, 70%; - --color-sidebar-ink-faint: var(--dark-hue), 10%, 55%; - --color-sidebar-divider: var(--dark-hue), 15%, 17%; - --color-sidebar-button: var(--dark-hue), 15%, 18%; - --color-sidebar-selected: var(--dark-hue), 15%, 16%; - --color-sidebar-shade: var(--dark-hue), 15%, 23%; + --color-sidebar: var(--dark-hue), 8%, 3%; + --color-sidebar-box: var(--dark-hue), 8%, 6%; + --color-sidebar-line: var(--dark-hue), 8%, 12%; + --color-sidebar-ink: var(--dark-hue), 15%, 94%; + --color-sidebar-ink-dull: var(--dark-hue), 8%, 65%; + --color-sidebar-ink-faint: var(--dark-hue), 5%, 45%; + --color-sidebar-divider: var(--dark-hue), 8%, 5%; + --color-sidebar-button: var(--dark-hue), 8%, 12%; + --color-sidebar-selected: var(--dark-hue), 8%, 15%; + --color-sidebar-shade: var(--dark-hue), 8%, 12%; // main - --color-app: var(--dark-hue), 15%, 13%; - --color-app-box: var(--dark-hue), 15%, 18%; - --color-app-dark-box: var(--dark-hue), 15%, 15%; - --color-app-darker-box: var(--dark-hue), 16%, 11%; - --color-app-light-box: var(--dark-hue), 15%, 23%; - --color-app-overlay: var(--dark-hue), 15%, 17%; - --color-app-input: var(--dark-hue), 15%, 20%; - --color-app-focus: var(--dark-hue), 15%, 10%; - --color-app-line: var(--dark-hue), 15%, 23%; - --color-app-divider: var(--dark-hue), 15%, 5%; - --color-app-button: var(--dark-hue), 15%, 23%; - --color-app-hover: var(--dark-hue), 15%, 25%; - --color-app-selected: var(--dark-hue), 15%, 20%; - --color-app-selected-item: var(--dark-hue), 15%, 18%; - --color-app-active: var(--dark-hue), 15%, 30%; - --color-app-shade: var(--dark-hue), 15%, 0%; - --color-app-frame: var(--dark-hue), 15%, 25%; - --color-app-slider: var(--dark-hue), 15%, 20%; - --color-app-explorer-scrollbar: var(--dark-hue), 20%, 25%; + --color-app: var(--dark-hue), 8%, 5%; + --color-app-box: var(--dark-hue), 8%, 8%; + --color-app-dark-box: var(--dark-hue), 8%, 6%; + --color-app-darker-box: var(--dark-hue), 8%, 4%; + --color-app-light-box: var(--dark-hue), 8%, 15%; + --color-app-overlay: var(--dark-hue), 8%, 7%; + --color-app-input: var(--dark-hue), 8%, 10%; + --color-app-focus: var(--dark-hue), 8%, 4%; + --color-app-line: var(--dark-hue), 8%, 14%; + --color-app-divider: var(--dark-hue), 8%, 3%; + --color-app-button: var(--dark-hue), 8%, 12%; + --color-app-hover: var(--dark-hue), 8%, 16%; + --color-app-selected: var(--dark-hue), 8%, 18%; + --color-app-selected-item: var(--dark-hue), 8%, 8%; + --color-app-active: var(--dark-hue), 8%, 20%; + --color-app-shade: var(--dark-hue), 8%, 0%; + --color-app-frame: var(--dark-hue), 8%, 16%; + --color-app-slider: var(--dark-hue), 8%, 10%; + --color-app-explorer-scrollbar: var(--dark-hue), 8%, 18%; // menu - --color-menu: var(--dark-hue), 15%, 10%; - --color-menu-line: var(--dark-hue), 15%, 14%; - --color-menu-ink: var(--dark-hue), 25%, 92%; - --color-menu-faint: var(--dark-hue), 5%, 80%; - --color-menu-hover: var(--dark-hue), 15%, 30%; - --color-menu-selected: var(--dark-hue), 5%, 30%; + --color-menu: var(--dark-hue), 8%, 4%; + --color-menu-line: var(--dark-hue), 8%, 8%; + --color-menu-ink: var(--dark-hue), 15%, 94%; + --color-menu-faint: var(--dark-hue), 5%, 65%; + --color-menu-hover: var(--dark-hue), 8%, 16%; + --color-menu-selected: var(--dark-hue), 5%, 18%; --color-menu-shade: var(--dark-hue), 5%, 0%; } diff --git a/src/api/links.rs b/src/api/links.rs index e1d2a57bb..bc5565be0 100644 --- a/src/api/links.rs +++ b/src/api/links.rs @@ -1,11 +1,13 @@ //! API handlers for agent links and topology. use crate::api::state::ApiState; +use crate::links::{AgentLink, LinkDirection, LinkRelationship}; use axum::Json; use axum::extract::{Path, State}; +use axum::http::StatusCode; use axum::response::IntoResponse; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::sync::Arc; /// List all links in the instance. @@ -69,3 +71,256 @@ pub async fn topology(State(state): State>) -> impl IntoResponse { Json(TopologyResponse { agents, links }) } + +// -- Write endpoints -- + +#[derive(Debug, Deserialize)] +pub struct CreateLinkRequest { + pub from: String, + pub to: String, + #[serde(default = "default_direction")] + pub direction: String, + #[serde(default = "default_relationship")] + pub relationship: String, +} + +fn default_direction() -> String { + "two_way".into() +} + +fn default_relationship() -> String { + "peer".into() +} + +#[derive(Debug, Deserialize)] +pub struct UpdateLinkRequest { + pub direction: Option, + pub relationship: Option, +} + +/// Create a new link between two agents. Persists to config.toml and updates in-memory state. +pub async fn create_link( + State(state): State>, + Json(request): Json, +) -> Result { + // Validate direction and relationship parse correctly + let direction: LinkDirection = request + .direction + .parse() + .map_err(|_| StatusCode::BAD_REQUEST)?; + let relationship: LinkRelationship = request + .relationship + .parse() + .map_err(|_| StatusCode::BAD_REQUEST)?; + + // Validate agents exist + let agent_configs = state.agent_configs.load(); + let from_exists = agent_configs.iter().any(|a| a.id == request.from); + let to_exists = agent_configs.iter().any(|a| a.id == request.to); + if !from_exists || !to_exists { + return Err(StatusCode::NOT_FOUND); + } + + if request.from == request.to { + return Err(StatusCode::BAD_REQUEST); + } + + // Check for duplicate + let existing = state.agent_links.load(); + let duplicate = existing.iter().any(|link| { + link.from_agent_id == request.from && link.to_agent_id == request.to + }); + if duplicate { + return Err(StatusCode::CONFLICT); + } + + // Write to config.toml + let config_path = state.config_path.read().await.clone(); + let content = tokio::fs::read_to_string(&config_path) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to read config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + let mut doc: toml_edit::DocumentMut = content.parse().map_err(|error| { + tracing::warn!(%error, "failed to parse config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + // Get or create the [[links]] array + if doc.get("links").is_none() { + doc["links"] = toml_edit::Item::ArrayOfTables(toml_edit::ArrayOfTables::new()); + } + let links_array = doc["links"] + .as_array_of_tables_mut() + .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?; + + let mut link_table = toml_edit::Table::new(); + link_table["from"] = toml_edit::value(&request.from); + link_table["to"] = toml_edit::value(&request.to); + link_table["direction"] = toml_edit::value(direction.as_str()); + link_table["relationship"] = toml_edit::value(relationship.as_str()); + links_array.push(link_table); + + tokio::fs::write(&config_path, doc.to_string()) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to write config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + // Update in-memory state + let new_link = AgentLink { + from_agent_id: request.from.clone(), + to_agent_id: request.to.clone(), + direction, + relationship, + }; + let mut links = (**existing).clone(); + links.push(new_link.clone()); + state.set_agent_links(links); + + tracing::info!( + from = %request.from, + to = %request.to, + direction = %direction, + relationship = %relationship, + "agent link created via API" + ); + + Ok(( + StatusCode::CREATED, + Json(serde_json::json!({ + "link": new_link, + })), + )) +} + +/// Update a link's properties. Identifies the link by from/to agent IDs in the path. +pub async fn update_link( + State(state): State>, + Path((from, to)): Path<(String, String)>, + Json(request): Json, +) -> Result { + let existing = state.agent_links.load(); + let link_index = existing + .iter() + .position(|link| link.from_agent_id == from && link.to_agent_id == to) + .ok_or(StatusCode::NOT_FOUND)?; + + let mut updated = existing[link_index].clone(); + if let Some(dir) = &request.direction { + updated.direction = dir.parse().map_err(|_| StatusCode::BAD_REQUEST)?; + } + if let Some(rel) = &request.relationship { + updated.relationship = rel.parse().map_err(|_| StatusCode::BAD_REQUEST)?; + } + + // Write to config.toml + let config_path = state.config_path.read().await.clone(); + let content = tokio::fs::read_to_string(&config_path) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to read config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + let mut doc: toml_edit::DocumentMut = content.parse().map_err(|error| { + tracing::warn!(%error, "failed to parse config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + // Find and update the matching [[links]] entry + if let Some(links_array) = doc.get_mut("links").and_then(|l| l.as_array_of_tables_mut()) { + for table in links_array.iter_mut() { + let table_from = table.get("from").and_then(|v| v.as_str()); + let table_to = table.get("to").and_then(|v| v.as_str()); + if table_from == Some(&from) && table_to == Some(&to) { + if request.direction.is_some() { + table["direction"] = toml_edit::value(updated.direction.as_str()); + } + if request.relationship.is_some() { + table["relationship"] = toml_edit::value(updated.relationship.as_str()); + } + break; + } + } + } + + tokio::fs::write(&config_path, doc.to_string()) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to write config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + // Update in-memory state + let mut links = (**existing).clone(); + links[link_index] = updated.clone(); + state.set_agent_links(links); + + tracing::info!(from = %from, to = %to, "agent link updated via API"); + + Ok(Json(serde_json::json!({ "link": updated }))) +} + +/// Delete a link between two agents. +pub async fn delete_link( + State(state): State>, + Path((from, to)): Path<(String, String)>, +) -> Result { + let existing = state.agent_links.load(); + let had_link = existing + .iter() + .any(|link| link.from_agent_id == from && link.to_agent_id == to); + if !had_link { + return Err(StatusCode::NOT_FOUND); + } + + // Write to config.toml + let config_path = state.config_path.read().await.clone(); + let content = tokio::fs::read_to_string(&config_path) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to read config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + let mut doc: toml_edit::DocumentMut = content.parse().map_err(|error| { + tracing::warn!(%error, "failed to parse config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + // Remove the matching [[links]] entry + if let Some(links_array) = doc.get_mut("links").and_then(|l| l.as_array_of_tables_mut()) { + let mut remove_index = None; + for (idx, table) in links_array.iter().enumerate() { + let table_from = table.get("from").and_then(|v| v.as_str()); + let table_to = table.get("to").and_then(|v| v.as_str()); + if table_from == Some(&from) && table_to == Some(&to) { + remove_index = Some(idx); + break; + } + } + if let Some(idx) = remove_index { + links_array.remove(idx); + } + } + + tokio::fs::write(&config_path, doc.to_string()) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to write config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + // Update in-memory state + let links: Vec<_> = existing + .iter() + .filter(|link| !(link.from_agent_id == from && link.to_agent_id == to)) + .cloned() + .collect(); + state.set_agent_links(links); + + tracing::info!(from = %from, to = %to, "agent link deleted via API"); + + Ok(StatusCode::NO_CONTENT) +} diff --git a/src/api/server.rs b/src/api/server.rs index da9bbf053..296ae1627 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -159,7 +159,11 @@ pub async fn start_http_server( .route("/update/apply", post(settings::update_apply)) .route("/webchat/send", post(webchat::webchat_send)) .route("/webchat/history", get(webchat::webchat_history)) - .route("/links", get(links::list_links)) + .route("/links", get(links::list_links).post(links::create_link)) + .route( + "/links/{from}/{to}", + put(links::update_link).delete(links::delete_link), + ) .route("/agents/{id}/links", get(links::agent_links)) .route("/topology", get(links::topology)) .layer(DefaultBodyLimit::max(10 * 1024 * 1024)) From 4733d307d3896493885998665cc0757d4c636b93 Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sun, 22 Feb 2026 17:35:44 -0800 Subject: [PATCH 04/12] feat: enhance agent management with display names, roles, and group functionality - Added optional display name and role fields to the AgentInfo and TopologyAgent interfaces. - Introduced new TopologyGroup interface and related API endpoints for creating, updating, and deleting agent groups. - Updated CreateAgentDialog to support input for display names and roles. - Enhanced TopologyGraph to visualize agent groups and their relationships. - Modified API and state management to handle agent groups effectively. --- interface/src/api/client.ts | 64 +- .../src/components/CreateAgentDialog.tsx | 49 +- interface/src/components/TopologyGraph.tsx | 687 ++++++++++++++++-- interface/src/routes/Overview.tsx | 34 +- src/agent/channel.rs | 20 +- src/api/agents.rs | 20 + src/api/links.rs | 259 ++++++- src/api/server.rs | 5 + src/api/state.rs | 12 + src/config.rs | 49 ++ src/lib.rs | 2 + src/main.rs | 15 + 12 files changed, 1140 insertions(+), 76 deletions(-) diff --git a/interface/src/api/client.ts b/interface/src/api/client.ts index 69c7d4a3d..9b7676ad5 100644 --- a/interface/src/api/client.ts +++ b/interface/src/api/client.ts @@ -195,6 +195,8 @@ export type ChannelStatusResponse = Record; export interface AgentInfo { id: string; + display_name?: string; + role?: string; workspace: string; context_window: number; max_turns: number; @@ -962,6 +964,8 @@ export interface LinksResponse { export interface TopologyAgent { id: string; name: string; + display_name?: string; + role?: string; } export interface TopologyLink { @@ -971,9 +975,28 @@ export interface TopologyLink { relationship: string; } +export interface TopologyGroup { + name: string; + agent_ids: string[]; + color?: string; +} + export interface TopologyResponse { agents: TopologyAgent[]; links: TopologyLink[]; + groups: TopologyGroup[]; +} + +export interface CreateGroupRequest { + name: string; + agent_ids?: string[]; + color?: string; +} + +export interface UpdateGroupRequest { + name?: string; + agent_ids?: string[]; + color?: string; } export interface CreateLinkRequest { @@ -1074,11 +1097,11 @@ export const api = { } return response.json() as Promise; }, - createAgent: async (agentId: string) => { + createAgent: async (agentId: string, displayName?: string, role?: string) => { const response = await fetch(`${API_BASE}/agents`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ agent_id: agentId }), + body: JSON.stringify({ agent_id: agentId, display_name: displayName || undefined, role: role || undefined }), }); if (!response.ok) { throw new Error(`API error: ${response.status}`); @@ -1458,6 +1481,43 @@ export const api = { } }, + // Agent Groups API + groups: () => fetchJson<{ groups: TopologyGroup[] }>("/groups"), + createGroup: async (request: CreateGroupRequest): Promise<{ group: TopologyGroup }> => { + const response = await fetch(`${API_BASE}/groups`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + }); + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + return response.json(); + }, + updateGroup: async (name: string, request: UpdateGroupRequest): Promise<{ group: TopologyGroup }> => { + const response = await fetch( + `${API_BASE}/groups/${encodeURIComponent(name)}`, + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + }, + ); + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + return response.json(); + }, + deleteGroup: async (name: string): Promise => { + const response = await fetch( + `${API_BASE}/groups/${encodeURIComponent(name)}`, + { method: "DELETE" }, + ); + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + }, + // Web Chat API webChatSend: (agentId: string, sessionId: string, message: string, senderName?: string) => fetch(`${API_BASE}/webchat/send`, { diff --git a/interface/src/components/CreateAgentDialog.tsx b/interface/src/components/CreateAgentDialog.tsx index 41b5d1252..024c06f65 100644 --- a/interface/src/components/CreateAgentDialog.tsx +++ b/interface/src/components/CreateAgentDialog.tsx @@ -11,18 +11,24 @@ interface CreateAgentDialogProps { export function CreateAgentDialog({open, onOpenChange}: CreateAgentDialogProps) { const [agentId, setAgentId] = useState(""); + const [displayName, setDisplayName] = useState(""); + const [role, setRole] = useState(""); const [error, setError] = useState(null); const queryClient = useQueryClient(); const navigate = useNavigate(); const createMutation = useMutation({ - mutationFn: (id: string) => api.createAgent(id), + mutationFn: (params: { id: string; displayName: string; role: string }) => + api.createAgent(params.id, params.displayName, params.role), onSuccess: (result) => { if (result.success) { queryClient.invalidateQueries({queryKey: ["agents"]}); queryClient.invalidateQueries({queryKey: ["overview"]}); + queryClient.invalidateQueries({queryKey: ["topology"]}); onOpenChange(false); setAgentId(""); + setDisplayName(""); + setRole(""); setError(null); navigate({to: "/agents/$agentId", params: {agentId: result.agent_id}}); } else { @@ -39,11 +45,22 @@ export function CreateAgentDialog({open, onOpenChange}: CreateAgentDialogProps) return; } setError(null); - createMutation.mutate(trimmed); + createMutation.mutate({ + id: trimmed, + displayName: displayName.trim(), + role: role.trim(), + }); + } + + function handleClose() { + setError(null); + setAgentId(""); + setDisplayName(""); + setRole(""); } return ( - { if (!v) { setError(null); setAgentId(""); } onOpenChange(v); }}> + { if (!v) handleClose(); onOpenChange(v); }}> Create Agent @@ -63,6 +80,32 @@ export function CreateAgentDialog({open, onOpenChange}: CreateAgentDialogProps) Lowercase letters, numbers, hyphens, and underscores only.

+
+ + setDisplayName(e.target.value)} + placeholder="e.g. Research Agent" + onKeyDown={(e) => { if (e.key === "Enter") handleSubmit(); }} + /> +
+
+ + setRole(e.target.value)} + placeholder="e.g. Handles tier 1 support tickets" + onKeyDown={(e) => { if (e.key === "Enter") handleSubmit(); }} + /> +
{error && (
{error} diff --git a/interface/src/components/TopologyGraph.tsx b/interface/src/components/TopologyGraph.tsx index 1bc1aeefa..61243dbaf 100644 --- a/interface/src/components/TopologyGraph.tsx +++ b/interface/src/components/TopologyGraph.tsx @@ -10,6 +10,7 @@ import { type EdgeTypes, type NodeProps, type EdgeProps, + type NodeChange, useNodesState, useEdgesState, MarkerType, @@ -26,6 +27,7 @@ import { api, type AgentSummary, type TopologyResponse, + type TopologyGroup, type LinkDirection, type LinkRelationship, } from "@/api/client"; @@ -46,6 +48,45 @@ const RELATIONSHIP_LABELS: Record = { subordinate: "Subordinate", }; +const GROUP_COLORS = [ + "#6366f1", + "#8b5cf6", + "#ec4899", + "#f59e0b", + "#22c55e", + "#06b6d4", + "#f97316", + "#14b8a6", +]; + +// -- Position persistence -- + +const POSITIONS_KEY = "spacebot:topology:positions"; + +type SavedPositions = Record; + +function loadPositions(): SavedPositions { + try { + const raw = localStorage.getItem(POSITIONS_KEY); + if (raw) return JSON.parse(raw); + } catch { + // ignore + } + return {}; +} + +function savePositions(nodes: Node[]) { + const positions: SavedPositions = {}; + for (const node of nodes) { + positions[node.id] = node.position; + } + try { + localStorage.setItem(POSITIONS_KEY, JSON.stringify(positions)); + } catch { + // ignore + } +} + /** Deterministic gradient from a seed string. */ function seedGradient(seed: string): [string, string] { let hash = 0; @@ -58,6 +99,49 @@ function seedGradient(seed: string): [string, string] { return [`hsl(${hue1}, 70%, 55%)`, `hsl(${hue2}, 60%, 45%)`]; } +// -- Custom Node: Group -- + +const GROUP_PADDING = 30; +const GROUP_HEADER = 36; + +function GroupNode({ data, selected }: NodeProps) { + const color = (data.color as string) ?? "#6366f1"; + const name = data.label as string; + + return ( +
+
+ + + {name} + +
+
+ ); +} + // -- Custom Node: Agent Profile Card -- const NODE_WIDTH = 240; @@ -65,13 +149,16 @@ const NODE_WIDTH = 240; function AgentNode({ data, selected }: NodeProps) { const avatarSeed = (data.avatarSeed as string) ?? (data.agentId as string); const [c1, c2] = seedGradient(avatarSeed); - const displayName = (data.displayName as string) ?? (data.agentId as string); - const status = data.status as string | null; + const agentId = data.agentId as string; + const configDisplayName = data.configDisplayName as string | null; + const configRole = data.configRole as string | null; + const chosenName = data.chosenName as string | null; const bio = data.bio as string | null; const isOnline = data.isOnline as boolean; const channelCount = data.channelCount as number; const memoryCount = data.memoryCount as number; - const agentId = data.agentId as string; + const connected = (data.connectedHandles as Record) ?? {}; + const primaryName = configDisplayName ?? agentId; return (
- {/* Name + link to agent */} + {/* Primary name + self-chosen name inline */} e.stopPropagation()} > - {displayName} + + {primaryName} + + {chosenName && chosenName !== primaryName && chosenName !== agentId && ( + + "{chosenName}" + + )} - {/* Status line */} - {status && ( -

- {status} + {/* Role */} + {configRole && ( +

+ {configRole}

)} @@ -175,16 +269,55 @@ function AgentNode({ data, selected }: NodeProps) {
- {/* Handles */} + {/* Handles on all four sides for relationship-based routing */} + + + + {/* Target handles (same positions, different type) */} + + +
); @@ -259,8 +392,43 @@ function LinkEdge({ ); } +/** Map a relationship to source/target handle IDs. */ +function getHandlesForRelationship(relationship: string): { + sourceHandle: string; + targetHandle: string; +} { + switch (relationship) { + case "superior": + // from_agent is superior → connects downward to subordinate + return { sourceHandle: "bottom", targetHandle: "top" }; + case "subordinate": + // from_agent is subordinate → connects upward to superior + return { sourceHandle: "top", targetHandle: "bottom" }; + default: + // peer → horizontal + return { sourceHandle: "right", targetHandle: "left" }; + } +} + +/** Infer relationship from the handle the user dragged from. */ +function inferRelationshipFromHandle( + sourceHandle: string | null, +): { relationship: LinkRelationship } { + switch (sourceHandle) { + case "top": + // Dragged from top = "I am subordinate to the target" + return { relationship: "subordinate" }; + case "bottom": + // Dragged from bottom = "I am superior to the target" + return { relationship: "superior" }; + default: + return { relationship: "peer" }; + } +} + const nodeTypes: NodeTypes = { agent: AgentNode, + group: GroupNode, }; const edgeTypes: EdgeTypes = { @@ -376,6 +544,109 @@ function EdgeConfigPanel({ ); } +// -- Group Config Panel -- + +interface GroupConfigPanelProps { + group: TopologyGroup; + allAgents: string[]; + onUpdate: (agentIds: string[], name: string) => void; + onDelete: () => void; + onClose: () => void; +} + +function GroupConfigPanel({ + group, + allAgents, + onUpdate, + onDelete, + onClose, +}: GroupConfigPanelProps) { + const [name, setName] = useState(group.name); + const [agentIds, setAgentIds] = useState>(new Set(group.agent_ids)); + + const toggleAgent = (id: string) => { + setAgentIds((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + }; + + return ( + +
+ Group Settings + +
+ + {/* Name */} +
+ + setName(e.target.value)} + className="w-full rounded bg-app-input px-2.5 py-1.5 text-sm text-ink outline-none border border-app-line/50 focus:border-accent/50" + /> +
+ + {/* Agent membership */} +
+ +
+ {allAgents.map((id) => ( + + ))} +
+
+ +
+ + +
+
+ ); +} + // -- Main Component (inner, needs ReactFlowProvider) -- interface TopologyGraphInnerProps { @@ -386,6 +657,7 @@ interface TopologyGraphInnerProps { function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { const queryClient = useQueryClient(); const [selectedEdge, setSelectedEdge] = useState(null); + const [selectedGroup, setSelectedGroup] = useState(null); const { data, isLoading, error } = useQuery({ queryKey: ["topology"], @@ -450,7 +722,8 @@ function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { ); } - // Mutations + // -- Mutations -- + const createLink = useMutation({ mutationFn: (params: { from: string; @@ -495,24 +768,55 @@ function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { }, }); + const createGroup = useMutation({ + mutationFn: (name: string) => + api.createGroup({ name, agent_ids: [] }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["topology"] }); + }, + }); + + const updateGroup = useMutation({ + mutationFn: (params: { originalName: string; agentIds: string[]; name: string }) => + api.updateGroup(params.originalName, { + name: params.name, + agent_ids: params.agentIds, + }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["topology"] }); + setSelectedGroup(null); + }, + }); + + const deleteGroup = useMutation({ + mutationFn: (name: string) => api.deleteGroup(name), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["topology"] }); + setSelectedGroup(null); + }, + }); + // Handle new connection (drag from handle to handle) const onConnect = useCallback( (connection: Connection) => { if (!connection.source || !connection.target) return; if (connection.source === connection.target) return; - // Check if link already exists const exists = edges.some( (e) => e.source === connection.source && e.target === connection.target, ); if (exists) return; + const { relationship } = inferRelationshipFromHandle( + connection.sourceHandle ?? null, + ); + createLink.mutate({ from: connection.source, to: connection.target, direction: "two_way", - relationship: "peer", + relationship, }); }, [edges, createLink], @@ -521,14 +825,121 @@ function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { const onEdgeClick = useCallback( (_: React.MouseEvent, edge: Edge) => { setSelectedEdge(edge); + setSelectedGroup(null); }, [], ); + const groups = data?.groups ?? []; + + const onNodeClick = useCallback( + (_: React.MouseEvent, node: Node) => { + if (node.type === "group" && groups.length > 0) { + const group = groups.find((g) => `group:${g.name}` === node.id); + if (group) { + setSelectedGroup(group); + setSelectedEdge(null); + } + } else { + setSelectedGroup(null); + } + }, + [groups], + ); + const onPaneClick = useCallback(() => { setSelectedEdge(null); + setSelectedGroup(null); }, []); + // Handle node drops into/out of groups via position change + const handleNodesChange = useCallback( + (changes: NodeChange[]) => { + onNodesChange(changes); + + // Persist positions when any drag ends + const hasDragEnd = changes.some( + (c) => c.type === "position" && !c.dragging, + ); + if (hasDragEnd) { + // Read latest nodes after the change is applied + setNodes((current) => { + savePositions(current); + return current; + }); + } + + // After a drag ends, check if an agent node was dropped onto a group + for (const change of changes) { + if (change.type === "position" && !change.dragging && groups.length > 0) { + const draggedNode = nodes.find((n) => n.id === change.id); + if (!draggedNode || draggedNode.type !== "agent") continue; + + const agentId = draggedNode.id; + const currentGroup = groups.find((g) => + g.agent_ids.includes(agentId), + ); + + // Find if the agent was dropped onto a group node + const groupNodes = nodes.filter((n) => n.type === "group"); + let targetGroup: TopologyGroup | null = null; + + for (const gNode of groupNodes) { + const gw = (gNode.data.width as number) ?? 0; + const gh = (gNode.data.height as number) ?? 0; + const pos = draggedNode.position; + if ( + pos.x > gNode.position.x && + pos.x < gNode.position.x + gw && + pos.y > gNode.position.y && + pos.y < gNode.position.y + gh + ) { + const group = groups.find( + (g) => `group:${g.name}` === gNode.id, + ); + if (group) { + targetGroup = group; + break; + } + } + } + + if (targetGroup && !targetGroup.agent_ids.includes(agentId)) { + // Add to target group + const newIds = [...targetGroup.agent_ids, agentId]; + // Remove from current group if any + if (currentGroup && currentGroup.name !== targetGroup.name) { + api.updateGroup(currentGroup.name, { + agent_ids: currentGroup.agent_ids.filter( + (id) => id !== agentId, + ), + }).then(() => queryClient.invalidateQueries({ queryKey: ["topology"] })); + } + api.updateGroup(targetGroup.name, { + agent_ids: newIds, + }).then(() => queryClient.invalidateQueries({ queryKey: ["topology"] })); + } else if ( + !targetGroup && + currentGroup + ) { + // Dragged out of a group + api.updateGroup(currentGroup.name, { + agent_ids: currentGroup.agent_ids.filter( + (id) => id !== agentId, + ), + }).then(() => queryClient.invalidateQueries({ queryKey: ["topology"] })); + } + } + } + }, + [onNodesChange, nodes, groups, queryClient], + ); + + const handleCreateGroup = useCallback(() => { + const name = `Group ${groups.length + 1}`; + createGroup.mutate(name); + }, [data, createGroup]); + if (isLoading) { return (
@@ -560,15 +971,18 @@ function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { ); } + const allAgentIds = data.agents.map((a) => a.id); + return ( -
+
- {/* Legend */} + {/* Legend + controls */}
- Relationships -
-
- {Object.entries(RELATIONSHIP_COLORS).map( - ([type, color]) => ( -
- - - {type} - -
- ), - )} + Drag handles to link
-
- Drag between nodes to create links +
+
+ + Top → Superior +
+
+ + Bottom → Subordinate +
+
+ + Side → Peer +
+
{/* Edge config panel */} @@ -645,58 +1061,215 @@ function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { /> )} + + {/* Group config panel */} + + {selectedGroup && ( + + updateGroup.mutate({ + originalName: selectedGroup.name, + agentIds, + name, + }) + } + onDelete={() => deleteGroup.mutate(selectedGroup.name)} + onClose={() => setSelectedGroup(null)} + /> + )} +
); } // -- Graph Builder -- +/** Estimate the rendered height of an agent node based on profile data. */ +function estimateNodeHeight(summary: AgentSummary | undefined): number { + let h = 48 + 24 + 8 + 16 + 24; // banner + avatar + name row + stats + padding + if (summary?.profile?.status) h += 16; + if (summary?.profile?.bio) h += 40; + return h; +} + function buildGraph( data: TopologyResponse, activeEdges: Set, agentProfiles: Map, ): { initialNodes: Node[]; initialEdges: Edge[] } { - const agentCount = data.agents.length; + const saved = loadPositions(); + const allNodes: Node[] = []; + + // Topology agent lookup for display_name / role + const topologyAgentMap = new Map(data.agents.map((a) => [a.id, a])); - // Arrange agents in a circle for initial layout - const radius = Math.max(200, agentCount * 80); - const centerX = 400; + // Build a set of connected handles per agent from link relationships. + // Key: "agentId:handleId", e.g. "support:top" + const connectedHandles = new Set(); + const links = data.links ?? []; + for (const link of links) { + const { sourceHandle, targetHandle } = getHandlesForRelationship(link.relationship); + connectedHandles.add(`${link.from}:${sourceHandle}`); + connectedHandles.add(`${link.to}:${targetHandle}`); + } + + // Build group membership lookup + const groups = data.groups ?? []; + const agentToGroup = new Map(); + for (const group of groups) { + for (const agentId of group.agent_ids) { + agentToGroup.set(agentId, group); + } + } + + // Agents not in any group + const ungroupedAgents = data.agents.filter((a) => !agentToGroup.has(a.id)); + + // Create group nodes + const groupPositions = new Map(); + let groupX = 0; + + for (let gi = 0; gi < groups.length; gi++) { + const group = groups[gi]; + const memberCount = group.agent_ids.length; + const cols = Math.max(1, Math.min(memberCount, 2)); + const rows = Math.ceil(memberCount / cols); + const groupWidth = + cols * (NODE_WIDTH + GROUP_PADDING) + GROUP_PADDING; + const maxMemberHeight = group.agent_ids.reduce((max, id) => { + return Math.max(max, estimateNodeHeight(agentProfiles.get(id))); + }, 170); + const groupHeight = + GROUP_HEADER + rows * (maxMemberHeight + GROUP_PADDING) + GROUP_PADDING; + + const color = group.color ?? GROUP_COLORS[gi % GROUP_COLORS.length]; + const pos = { x: groupX, y: 0 }; + groupPositions.set(group.name, pos); + + allNodes.push({ + id: `group:${group.name}`, + type: "group", + position: pos, + data: { + label: group.name, + color, + width: groupWidth, + height: groupHeight, + }, + style: { width: groupWidth, height: groupHeight }, + draggable: true, + selectable: true, + zIndex: -1, + }); + + // Position member agents inside the group + group.agent_ids.forEach((agentId, idx) => { + const col = idx % cols; + const row = Math.floor(idx / cols); + const summary = agentProfiles.get(agentId); + const profile = summary?.profile; + const isOnline = + summary?.last_activity_at != null && + new Date(summary.last_activity_at).getTime() > + Date.now() - 5 * 60 * 1000; + + const topoAgent = topologyAgentMap.get(agentId); + allNodes.push({ + id: agentId, + type: "agent", + position: { + x: GROUP_PADDING + col * (NODE_WIDTH + GROUP_PADDING), + y: GROUP_HEADER + GROUP_PADDING + row * (maxMemberHeight + GROUP_PADDING), + }, + parentId: `group:${group.name}`, + extent: "parent" as const, + data: { + agentId, + configDisplayName: topoAgent?.display_name ?? null, + configRole: topoAgent?.role ?? null, + chosenName: profile?.display_name ?? null, + avatarSeed: profile?.avatar_seed ?? agentId, + bio: profile?.bio ?? null, + isOnline, + channelCount: summary?.channel_count ?? 0, + memoryCount: summary?.memory_total ?? 0, + connectedHandles: { + top: connectedHandles.has(`${agentId}:top`), + bottom: connectedHandles.has(`${agentId}:bottom`), + left: connectedHandles.has(`${agentId}:left`), + right: connectedHandles.has(`${agentId}:right`), + }, + }, + }); + }); + + groupX += groupWidth + 80; + } + + // Position ungrouped agents + const ungroupedStartX = groupX; + const radius = Math.max(200, ungroupedAgents.length * 80); + const centerX = ungroupedStartX + radius + NODE_WIDTH / 2; const centerY = 300; - const initialNodes: Node[] = data.agents.map((agent, index) => { - const angle = (2 * Math.PI * index) / agentCount - Math.PI / 2; + ungroupedAgents.forEach((agent, index) => { + const count = ungroupedAgents.length; + const angle = (2 * Math.PI * index) / count - Math.PI / 2; const summary = agentProfiles.get(agent.id); const profile = summary?.profile; const isOnline = summary?.last_activity_at != null && - new Date(summary.last_activity_at).getTime() > Date.now() - 5 * 60 * 1000; + new Date(summary.last_activity_at).getTime() > + Date.now() - 5 * 60 * 1000; - return { + allNodes.push({ id: agent.id, type: "agent", - position: { - x: centerX + radius * Math.cos(angle), - y: centerY + radius * Math.sin(angle), - }, + position: + count === 1 + ? { x: ungroupedStartX, y: 100 } + : { + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }, data: { agentId: agent.id, - displayName: profile?.display_name ?? agent.name, + configDisplayName: agent.display_name ?? null, + configRole: agent.role ?? null, + chosenName: profile?.display_name ?? null, avatarSeed: profile?.avatar_seed ?? agent.id, - status: profile?.status ?? null, bio: profile?.bio ?? null, isOnline, channelCount: summary?.channel_count ?? 0, memoryCount: summary?.memory_total ?? 0, + connectedHandles: { + top: connectedHandles.has(`${agent.id}:top`), + bottom: connectedHandles.has(`${agent.id}:bottom`), + left: connectedHandles.has(`${agent.id}:left`), + right: connectedHandles.has(`${agent.id}:right`), + }, }, - }; + }); }); const initialEdges: Edge[] = data.links.map((link) => { const edgeId = `${link.from}->${link.to}`; + // Route edges through handles based on relationship: + // superior: from is above to → source bottom, target top + // subordinate: from is below to → source top, target bottom + // peer: horizontal → source right, target left + const { sourceHandle, targetHandle } = getHandlesForRelationship( + link.relationship, + ); return { id: edgeId, source: link.from, target: link.to, + sourceHandle, + targetHandle, type: "link", data: { direction: link.direction, @@ -721,7 +1294,15 @@ function buildGraph( }; }); - return { initialNodes, initialEdges }; + // Apply saved positions (override computed layout) + for (const node of allNodes) { + const savedPos = saved[node.id]; + if (savedPos) { + node.position = savedPos; + } + } + + return { initialNodes: allNodes, initialEdges }; } // -- Exported component with provider wrapper -- diff --git a/interface/src/routes/Overview.tsx b/interface/src/routes/Overview.tsx index 4196aa501..43e7200e3 100644 --- a/interface/src/routes/Overview.tsx +++ b/interface/src/routes/Overview.tsx @@ -1,32 +1,32 @@ -import { useMemo, useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { api } from "@/api/client"; -import { CreateAgentDialog } from "@/components/CreateAgentDialog"; -import { TopologyGraph } from "@/components/TopologyGraph"; -import type { ChannelLiveState } from "@/hooks/useChannelLiveState"; -import { formatUptime } from "@/lib/format"; +import {useMemo, useState} from "react"; +import {useQuery} from "@tanstack/react-query"; +import {api} from "@/api/client"; +import {CreateAgentDialog} from "@/components/CreateAgentDialog"; +import {TopologyGraph} from "@/components/TopologyGraph"; +import type {ChannelLiveState} from "@/hooks/useChannelLiveState"; +import {formatUptime} from "@/lib/format"; interface OverviewProps { liveStates: Record; activeLinks?: Set; } -export function Overview({ liveStates, activeLinks }: OverviewProps) { +export function Overview({liveStates, activeLinks}: OverviewProps) { const [createOpen, setCreateOpen] = useState(false); - const { data: statusData } = useQuery({ + const {data: statusData} = useQuery({ queryKey: ["status"], queryFn: api.status, refetchInterval: 5000, }); - const { data: overviewData, isLoading: overviewLoading } = useQuery({ + const {data: overviewData, isLoading: overviewLoading} = useQuery({ queryKey: ["overview"], queryFn: api.overview, refetchInterval: 10_000, }); - const { data: channelsData } = useQuery({ + const {data: channelsData} = useQuery({ queryKey: ["channels"], queryFn: api.channels, refetchInterval: 10000, @@ -43,7 +43,7 @@ export function Overview({ liveStates, activeLinks }: OverviewProps) { workers += Object.keys(state.workers).length; branches += Object.keys(state.branches).length; } - return { workers, branches }; + return {workers, branches}; }, [liveStates]); const uptime = statusData?.uptime_seconds ?? 0; @@ -51,7 +51,7 @@ export function Overview({ liveStates, activeLinks }: OverviewProps) { return (
{/* Compact status bar */} -
+

Spacebot

@@ -63,8 +63,12 @@ export function Overview({ liveStates, activeLinks }: OverviewProps) {
- {agents.length} agent{agents.length !== 1 ? "s" : ""} - {channels.length} channel{channels.length !== 1 ? "s" : ""} + + {agents.length} agent{agents.length !== 1 ? "s" : ""} + + + {channels.length} channel{channels.length !== 1 ? "s" : ""} + {formatUptime(uptime)}
diff --git a/src/agent/channel.rs b/src/agent/channel.rs index 2a78f9380..7efeca5ad 100644 --- a/src/agent/channel.rs +++ b/src/agent/channel.rs @@ -204,6 +204,24 @@ impl Channel { // concurrent channels sharing per-turn add/remove cycles. let tool_server = ToolServer::new().run(); + // Construct the send_agent_message tool if this agent has links and a messaging manager. + let send_agent_message_tool = { + let has_links = crate::links::links_for_agent(&deps.links.load(), &deps.agent_id) + .len() + > 0; + match (&deps.messaging_manager, has_links) { + (Some(mm), true) => Some(crate::tools::SendAgentMessageTool::new( + deps.agent_id.clone(), + deps.agent_id.to_string(), + deps.links.clone(), + mm.clone(), + deps.event_tx.clone(), + deps.agent_names.clone(), + )), + _ => None, + } + }; + let self_tx = message_tx.clone(); let channel = Self { id: id.clone(), @@ -228,7 +246,7 @@ impl Channel { pending_retrigger: false, pending_retrigger_metadata: HashMap::new(), retrigger_deadline: None, - send_agent_message_tool: None, + send_agent_message_tool, }; (channel, message_tx) diff --git a/src/api/agents.rs b/src/api/agents.rs index a61c0325e..0bcf3c1b9 100644 --- a/src/api/agents.rs +++ b/src/api/agents.rs @@ -127,6 +127,8 @@ pub(super) struct AgentOverviewQuery { #[derive(Deserialize)] pub(super) struct CreateAgentRequest { agent_id: String, + display_name: Option, + role: Option, } #[derive(Deserialize)] @@ -263,6 +265,16 @@ pub(super) async fn create_agent( let mut new_table = toml_edit::Table::new(); new_table["id"] = toml_edit::value(&agent_id); + if let Some(display_name) = &request.display_name { + if !display_name.is_empty() { + new_table["display_name"] = toml_edit::value(display_name.as_str()); + } + } + if let Some(role) = &request.role { + if !role.is_empty() { + new_table["role"] = toml_edit::value(role.as_str()); + } + } agents_array.push(new_table); tokio::fs::write(&config_path, doc.to_string()) @@ -281,6 +293,8 @@ pub(super) async fn create_agent( let raw_config = crate::config::AgentConfig { id: agent_id.clone(), default: false, + display_name: request.display_name.clone().filter(|s| !s.is_empty()), + role: request.role.clone().filter(|s| !s.is_empty()), workspace: None, routing: None, max_concurrent_branches: None, @@ -434,6 +448,10 @@ pub(super) async fn create_agent( guard.as_ref().cloned() }, links: Arc::new(arc_swap::ArcSwap::from_pointee((**state.agent_links.load()).clone())), + agent_names: { + let configs = state.agent_configs.load(); + Arc::new(configs.iter().map(|c| (c.id.clone(), c.id.clone())).collect()) + }, }; let event_rx = event_tx.subscribe(); @@ -530,6 +548,8 @@ pub(super) async fn create_agent( let mut agent_infos = (**state.agent_configs.load()).clone(); agent_infos.push(AgentInfo { id: agent_config.id.clone(), + display_name: agent_config.display_name.clone(), + role: agent_config.role.clone(), workspace: agent_config.workspace.clone(), context_window: agent_config.context_window, max_turns: agent_config.max_turns, diff --git a/src/api/links.rs b/src/api/links.rs index bc5565be0..7c0124391 100644 --- a/src/api/links.rs +++ b/src/api/links.rs @@ -31,12 +31,17 @@ pub async fn agent_links( struct TopologyResponse { agents: Vec, links: Vec, + groups: Vec, } #[derive(Debug, Serialize)] struct TopologyAgent { id: String, name: String, + #[serde(skip_serializing_if = "Option::is_none")] + display_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + role: Option, } #[derive(Debug, Serialize)] @@ -47,6 +52,14 @@ struct TopologyLink { relationship: String, } +#[derive(Debug, Serialize)] +struct TopologyGroup { + name: String, + agent_ids: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + color: Option, +} + /// Get the full agent topology for graph rendering. pub async fn topology(State(state): State>) -> impl IntoResponse { let agent_configs = state.agent_configs.load(); @@ -54,7 +67,9 @@ pub async fn topology(State(state): State>) -> impl IntoResponse { .iter() .map(|config| TopologyAgent { id: config.id.clone(), - name: config.id.clone(), + name: config.display_name.clone().unwrap_or_else(|| config.id.clone()), + display_name: config.display_name.clone(), + role: config.role.clone(), }) .collect(); @@ -69,7 +84,21 @@ pub async fn topology(State(state): State>) -> impl IntoResponse { }) .collect(); - Json(TopologyResponse { agents, links }) + let all_groups = state.agent_groups.load(); + let groups: Vec = all_groups + .iter() + .map(|group| TopologyGroup { + name: group.name.clone(), + agent_ids: group.agent_ids.clone(), + color: group.color.clone(), + }) + .collect(); + + Json(TopologyResponse { + agents, + links, + groups, + }) } // -- Write endpoints -- @@ -324,3 +353,229 @@ pub async fn delete_link( Ok(StatusCode::NO_CONTENT) } + +// -- Group CRUD -- + +#[derive(Debug, Deserialize)] +pub struct CreateGroupRequest { + pub name: String, + #[serde(default)] + pub agent_ids: Vec, + pub color: Option, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateGroupRequest { + pub name: Option, + pub agent_ids: Option>, + pub color: Option, +} + +/// List all groups. +pub async fn list_groups(State(state): State>) -> impl IntoResponse { + let groups = state.agent_groups.load(); + Json(serde_json::json!({ "groups": &**groups })) +} + +/// Create a visual agent group. +pub async fn create_group( + State(state): State>, + Json(request): Json, +) -> Result { + if request.name.is_empty() { + return Err(StatusCode::BAD_REQUEST); + } + + let existing = state.agent_groups.load(); + if existing.iter().any(|g| g.name == request.name) { + return Err(StatusCode::CONFLICT); + } + + // Write to config.toml + let config_path = state.config_path.read().await.clone(); + let content = tokio::fs::read_to_string(&config_path) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to read config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + let mut doc: toml_edit::DocumentMut = content.parse().map_err(|error| { + tracing::warn!(%error, "failed to parse config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + if doc.get("groups").is_none() { + doc["groups"] = toml_edit::Item::ArrayOfTables(toml_edit::ArrayOfTables::new()); + } + let groups_array = doc["groups"] + .as_array_of_tables_mut() + .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?; + + let mut group_table = toml_edit::Table::new(); + group_table["name"] = toml_edit::value(&request.name); + let mut ids = toml_edit::Array::new(); + for id in &request.agent_ids { + ids.push(id.as_str()); + } + group_table["agent_ids"] = toml_edit::value(ids); + if let Some(color) = &request.color { + group_table["color"] = toml_edit::value(color.as_str()); + } + groups_array.push(group_table); + + tokio::fs::write(&config_path, doc.to_string()) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to write config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + let new_group = crate::config::GroupDef { + name: request.name.clone(), + agent_ids: request.agent_ids.clone(), + color: request.color.clone(), + }; + let mut groups = (**existing).clone(); + groups.push(new_group.clone()); + state.set_agent_groups(groups); + + tracing::info!(name = %request.name, "agent group created via API"); + + Ok((StatusCode::CREATED, Json(serde_json::json!({ "group": new_group })))) +} + +/// Update a group by name. +pub async fn update_group( + State(state): State>, + Path(group_name): Path, + Json(request): Json, +) -> Result { + let existing = state.agent_groups.load(); + let index = existing + .iter() + .position(|g| g.name == group_name) + .ok_or(StatusCode::NOT_FOUND)?; + + let mut updated = existing[index].clone(); + let new_name = request.name.as_deref().unwrap_or(&group_name); + + // If renaming, check for conflict + if new_name != group_name && existing.iter().any(|g| g.name == new_name) { + return Err(StatusCode::CONFLICT); + } + + if let Some(name) = &request.name { + updated.name = name.clone(); + } + if let Some(agent_ids) = &request.agent_ids { + updated.agent_ids = agent_ids.clone(); + } + if let Some(color) = &request.color { + updated.color = Some(color.clone()); + } + + // Write to config.toml + let config_path = state.config_path.read().await.clone(); + let content = tokio::fs::read_to_string(&config_path) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to read config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + let mut doc: toml_edit::DocumentMut = content.parse().map_err(|error| { + tracing::warn!(%error, "failed to parse config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + if let Some(groups_array) = doc.get_mut("groups").and_then(|g| g.as_array_of_tables_mut()) { + for table in groups_array.iter_mut() { + let table_name = table.get("name").and_then(|v| v.as_str()); + if table_name == Some(&group_name) { + if request.name.is_some() { + table["name"] = toml_edit::value(updated.name.as_str()); + } + if let Some(agent_ids) = &request.agent_ids { + let mut arr = toml_edit::Array::new(); + for id in agent_ids { + arr.push(id.as_str()); + } + table["agent_ids"] = toml_edit::value(arr); + } + if let Some(color) = &request.color { + table["color"] = toml_edit::value(color.as_str()); + } + break; + } + } + } + + tokio::fs::write(&config_path, doc.to_string()) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to write config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + let mut groups = (**existing).clone(); + groups[index] = updated.clone(); + state.set_agent_groups(groups); + + tracing::info!(name = %group_name, "agent group updated via API"); + + Ok(Json(serde_json::json!({ "group": updated }))) +} + +/// Delete a group by name. +pub async fn delete_group( + State(state): State>, + Path(group_name): Path, +) -> Result { + let existing = state.agent_groups.load(); + if !existing.iter().any(|g| g.name == group_name) { + return Err(StatusCode::NOT_FOUND); + } + + let config_path = state.config_path.read().await.clone(); + let content = tokio::fs::read_to_string(&config_path) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to read config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + let mut doc: toml_edit::DocumentMut = content.parse().map_err(|error| { + tracing::warn!(%error, "failed to parse config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + if let Some(groups_array) = doc.get_mut("groups").and_then(|g| g.as_array_of_tables_mut()) { + let mut remove_index = None; + for (idx, table) in groups_array.iter().enumerate() { + let table_name = table.get("name").and_then(|v| v.as_str()); + if table_name == Some(&group_name) { + remove_index = Some(idx); + break; + } + } + if let Some(idx) = remove_index { + groups_array.remove(idx); + } + } + + tokio::fs::write(&config_path, doc.to_string()) + .await + .map_err(|error| { + tracing::warn!(%error, "failed to write config.toml"); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + let groups: Vec<_> = existing + .iter() + .filter(|g| g.name != group_name) + .cloned() + .collect(); + state.set_agent_groups(groups); + + tracing::info!(name = %group_name, "agent group deleted via API"); + + Ok(StatusCode::NO_CONTENT) +} diff --git a/src/api/server.rs b/src/api/server.rs index 296ae1627..ae6ac30d0 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -166,6 +166,11 @@ pub async fn start_http_server( ) .route("/agents/{id}/links", get(links::agent_links)) .route("/topology", get(links::topology)) + .route("/groups", get(links::list_groups).post(links::create_group)) + .route( + "/groups/{name}", + put(links::update_group).delete(links::delete_group), + ) .layer(DefaultBodyLimit::max(10 * 1024 * 1024)) .layer(middleware::from_fn_with_state( state.clone(), diff --git a/src/api/state.rs b/src/api/state.rs index e3b5d7505..e4d67d63e 100644 --- a/src/api/state.rs +++ b/src/api/state.rs @@ -27,6 +27,10 @@ use tokio::sync::{RwLock, broadcast, mpsc}; #[derive(Debug, Clone, Serialize)] pub struct AgentInfo { pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub role: Option, pub workspace: PathBuf, pub context_window: usize, pub max_turns: usize, @@ -95,6 +99,8 @@ pub struct ApiState { pub webchat_adapter: ArcSwap>>, /// Instance-level agent links for the communication graph. pub agent_links: ArcSwap>, + /// Visual agent groups for the topology UI. + pub agent_groups: ArcSwap>, } /// Events sent to SSE clients. Wraps ProcessEvents with agent context. @@ -228,6 +234,7 @@ impl ApiState { agent_remove_tx, webchat_adapter: ArcSwap::from_pointee(None), agent_links: ArcSwap::from_pointee(Vec::new()), + agent_groups: ArcSwap::from_pointee(Vec::new()), } } @@ -531,6 +538,11 @@ impl ApiState { self.agent_links.store(Arc::new(links)); } + /// Set the visual agent groups for the topology UI. + pub fn set_agent_groups(&self, groups: Vec) { + self.agent_groups.store(Arc::new(groups)); + } + /// Send an event to all SSE subscribers. pub fn send_event(&self, event: ApiEvent) { let _ = self.event_tx.send(event); diff --git a/src/config.rs b/src/config.rs index c88ffa5c7..0da801408 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,6 +44,8 @@ pub struct Config { pub agents: Vec, /// Agent communication graph links. pub links: Vec, + /// Visual grouping of agents in the topology UI. + pub groups: Vec, /// Messaging platform credentials. pub messaging: MessagingConfig, /// Routing bindings (maps platform conversations to agents). @@ -65,6 +67,15 @@ pub struct LinkDef { pub relationship: String, } +/// A visual group definition for the topology UI. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GroupDef { + pub name: String, + pub agent_ids: Vec, + #[serde(default)] + pub color: Option, +} + /// HTTP API server configuration. #[derive(Debug, Clone)] pub struct ApiConfig { @@ -536,6 +547,10 @@ impl Default for CortexConfig { pub struct AgentConfig { pub id: String, pub default: bool, + /// User-defined display name for the agent (shown in UI). + pub display_name: Option, + /// User-defined role description (e.g. "handles tier 1 support"). + pub role: Option, /// Custom workspace path. If None, resolved to instance_dir/agents/{id}/workspace. pub workspace: Option, /// Per-agent routing overrides. None inherits from defaults. @@ -581,6 +596,8 @@ pub struct CronDef { #[derive(Debug, Clone)] pub struct ResolvedAgentConfig { pub id: String, + pub display_name: Option, + pub role: Option, pub workspace: PathBuf, pub data_dir: PathBuf, pub archives_dir: PathBuf, @@ -637,6 +654,8 @@ impl AgentConfig { ResolvedAgentConfig { id: self.id.clone(), + display_name: self.display_name.clone(), + role: self.role.clone(), workspace: self .workspace .clone() @@ -1230,6 +1249,8 @@ struct TomlConfig { #[serde(default)] links: Vec, #[serde(default)] + groups: Vec, + #[serde(default)] messaging: TomlMessagingConfig, #[serde(default)] bindings: Vec, @@ -1259,6 +1280,14 @@ fn default_link_relationship() -> String { "peer".into() } +#[derive(Deserialize)] +struct TomlGroupDef { + name: String, + #[serde(default)] + agent_ids: Vec, + color: Option, +} + #[derive(Deserialize, Default)] struct TomlTelemetryConfig { otlp_endpoint: Option, @@ -1585,6 +1614,8 @@ struct TomlAgentConfig { id: String, #[serde(default)] default: bool, + display_name: Option, + role: Option, workspace: Option, routing: Option, max_concurrent_branches: Option, @@ -2193,6 +2224,8 @@ impl Config { let agents = vec![AgentConfig { id: "main".into(), default: true, + display_name: None, + role: None, workspace: None, routing: Some(routing), max_concurrent_branches: None, @@ -2221,6 +2254,7 @@ impl Config { defaults: DefaultsConfig::default(), agents, links: Vec::new(), + groups: Vec::new(), messaging: MessagingConfig::default(), bindings: Vec::new(), api, @@ -2771,6 +2805,8 @@ impl Config { Ok(AgentConfig { id: a.id, default: a.default, + display_name: a.display_name, + role: a.role, workspace: a.workspace.map(PathBuf::from), routing: agent_routing, max_concurrent_branches: a.max_concurrent_branches, @@ -2880,6 +2916,8 @@ impl Config { agents.push(AgentConfig { id: "main".into(), default: true, + display_name: None, + role: None, workspace: None, routing: None, max_concurrent_branches: None, @@ -3066,12 +3104,23 @@ impl Config { }) .collect(); + let groups = toml + .groups + .into_iter() + .map(|g| GroupDef { + name: g.name, + agent_ids: g.agent_ids, + color: g.color, + }) + .collect(); + Ok(Config { instance_dir, llm, defaults, agents, links, + groups, messaging, bindings, api, diff --git a/src/lib.rs b/src/lib.rs index 0ae295fb0..52ad4ce77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,6 +198,8 @@ pub struct AgentDeps { pub sqlite_pool: sqlx::SqlitePool, pub messaging_manager: Option>, pub links: Arc>>, + /// Map of all agent IDs to display names, for inter-agent message routing. + pub agent_names: Arc>, } impl AgentDeps { diff --git a/src/main.rs b/src/main.rs index 74135eb9e..3a765fabd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -731,6 +731,7 @@ async fn run( api_state.set_prompt_engine(prompt_engine.clone()).await; api_state.set_defaults_config(config.defaults.clone()).await; api_state.set_agent_links((**agent_links.load()).clone()); + api_state.set_agent_groups(config.groups.clone()); // Track whether agents have been initialized let mut agents_initialized = false; @@ -1205,6 +1206,17 @@ async fn initialize_agents( ) -> anyhow::Result<()> { let resolved_agents = config.resolve_agents(); + // Build agent name map for inter-agent message routing + let agent_name_map: Arc> = Arc::new( + resolved_agents + .iter() + .map(|a| { + let name = a.display_name.clone().unwrap_or_else(|| a.id.clone()); + (a.id.clone(), name) + }) + .collect(), + ); + for agent_config in &resolved_agents { tracing::info!(agent_id = %agent_config.id, "initializing agent"); @@ -1337,6 +1349,7 @@ async fn initialize_agents( sqlite_pool: db.sqlite.clone(), messaging_manager: None, links: agent_links.clone(), + agent_names: agent_name_map.clone(), }; let agent = spacebot::Agent { @@ -1370,6 +1383,8 @@ async fn initialize_agents( runtime_configs.insert(agent_id.to_string(), agent.deps.runtime_config.clone()); agent_configs.push(spacebot::api::AgentInfo { id: agent.config.id.clone(), + display_name: agent.config.display_name.clone(), + role: agent.config.role.clone(), workspace: agent.config.workspace.clone(), context_window: agent.config.context_window, max_turns: agent.config.max_turns, From 41d2a067c54bd9f634133fc41b9b423f4bed6c3d Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sun, 22 Feb 2026 18:18:49 -0800 Subject: [PATCH 05/12] feat: enhance chat panel components with improved UI and functionality - Updated ToolActivityIndicator and WebChatPanel for better layout and responsiveness. - Introduced FloatingChatInput for a more user-friendly input experience. - Added ThinkingIndicator for visual feedback during processing. - Enhanced message display logic for clearer user interactions. - Improved styling and structure for better visual consistency across components. --- interface/src/components/CortexChatPanel.tsx | 206 +++++++++++------- interface/src/components/TopologyGraph.tsx | 102 ++++++--- interface/src/components/WebChatPanel.tsx | 212 ++++++++++++------- interface/src/ui/style/style.scss | 9 + src/agent/channel.rs | 8 +- 5 files changed, 356 insertions(+), 181 deletions(-) diff --git a/interface/src/components/CortexChatPanel.tsx b/interface/src/components/CortexChatPanel.tsx index fe4b478c5..dd763848d 100644 --- a/interface/src/components/CortexChatPanel.tsx +++ b/interface/src/components/CortexChatPanel.tsx @@ -15,11 +15,11 @@ function ToolActivityIndicator({ activity }: { activity: ToolActivity[] }) { if (activity.length === 0) return null; return ( -
+
{activity.map((tool, index) => ( -
{tool.status === "running" ? ( @@ -28,34 +28,115 @@ function ToolActivityIndicator({ activity }: { activity: ToolActivity[] }) { )} {tool.tool} {tool.status === "done" && tool.result_preview && ( - + {tool.result_preview.slice(0, 80)} )} -
+ ))}
); } +function ThinkingIndicator() { + return ( +
+ + + +
+ ); +} + +function CortexChatInput({ + value, + onChange, + onSubmit, + isStreaming, +}: { + value: string; + onChange: (value: string) => void; + onSubmit: () => void; + isStreaming: boolean; +}) { + const textareaRef = useRef(null); + + useEffect(() => { + textareaRef.current?.focus(); + }, []); + + useEffect(() => { + const textarea = textareaRef.current; + if (!textarea) return; + + const adjustHeight = () => { + textarea.style.height = "auto"; + const scrollHeight = textarea.scrollHeight; + const maxHeight = 160; + textarea.style.height = `${Math.min(scrollHeight, maxHeight)}px`; + textarea.style.overflowY = scrollHeight > maxHeight ? "auto" : "hidden"; + }; + + adjustHeight(); + textarea.addEventListener("input", adjustHeight); + return () => textarea.removeEventListener("input", adjustHeight); + }, [value]); + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); + onSubmit(); + } + }; + + return ( +
+
+