From deff028fed505f93fb6e695f97d394f4d8a7c859 Mon Sep 17 00:00:00 2001 From: elpresidank Date: Sun, 5 Apr 2026 21:07:35 -0500 Subject: [PATCH 001/151] Squashed 'ai-context/trustgraph-client/' content from commit 908f18cf git-subtree-dir: ai-context/trustgraph-client git-subtree-split: 908f18cf814470ec3b72cc336bb945fb792ffdec --- .github/workflows/ci.yml | 34 + .github/workflows/publish.yml | 51 + .gitignore | 7 + .prettierrc | 3 + LICENSE | 176 ++ README.md | 319 +++ docs/tech-specs/client-module.md | 44 + docs/tech-specs/streaming-support.md | 808 ++++++++ eslint.config.js | 35 + package.json | 66 + rollup.config.js | 30 + src/__tests__/flows-api.test.ts | 221 ++ src/__tests__/messages.test.ts | 370 ++++ src/__tests__/service-call-multi.test.ts | 285 +++ src/__tests__/service-call.test.ts | 239 +++ src/index.ts | 10 + src/models/Triple.ts | 40 + src/models/messages.ts | 496 +++++ src/models/namespaces.ts | 42 + src/socket/service-call-multi.ts | 171 ++ src/socket/service-call.ts | 239 +++ src/socket/trustgraph-socket.ts | 2353 ++++++++++++++++++++++ src/types.ts | 3 + test-graphrag.js | 94 + test-streaming.js | 111 + tsconfig.json | 23 + vitest.config.ts | 8 + 27 files changed, 6278 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/tech-specs/client-module.md create mode 100644 docs/tech-specs/streaming-support.md create mode 100644 eslint.config.js create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/__tests__/flows-api.test.ts create mode 100644 src/__tests__/messages.test.ts create mode 100644 src/__tests__/service-call-multi.test.ts create mode 100644 src/__tests__/service-call.test.ts create mode 100644 src/index.ts create mode 100644 src/models/Triple.ts create mode 100644 src/models/messages.ts create mode 100644 src/models/namespaces.ts create mode 100644 src/socket/service-call-multi.ts create mode 100644 src/socket/service-call.ts create mode 100644 src/socket/trustgraph-socket.ts create mode 100644 src/types.ts create mode 100755 test-graphrag.js create mode 100755 test-streaming.js create mode 100644 tsconfig.json create mode 100644 vitest.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..67cee0e9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install dependencies + run: npm install + + - name: Type check + run: npm run typecheck + + - name: Lint + run: npm run lint + + - name: Run tests + run: npm test + + - name: Build + run: npm run build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..fd681ba0 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,51 @@ +name: Publish to npm + +on: + push: + tags: + - 'v*' + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Upgrade npm for OIDC support + run: npm install -g npm@latest + + - name: Install dependencies + run: npm install + + - name: Type check + run: npm run typecheck + + - name: Lint + run: npm run lint + + - name: Run tests + run: npm test + + - name: Build + run: npm run build + + - name: Verify version matches tag + run: | + TAG_VERSION=${GITHUB_REF#refs/tags/v} + PKG_VERSION=$(node -p "require('./package.json').version") + if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then + echo "Tag version ($TAG_VERSION) doesn't match package.json ($PKG_VERSION)" + exit 1 + fi + + - name: Publish to npm + run: npm publish --access public --provenance diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..58aa58fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +*.log +.DS_Store +*.tsbuildinfo +package-lock.json +*~ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..8b6b8803 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "printWidth": 79 +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d9a10c0d --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 00000000..cb25bc94 --- /dev/null +++ b/README.md @@ -0,0 +1,319 @@ +# @trustgraph/client + +TypeScript/JavaScript client library for TrustGraph WebSocket API. This package provides a framework-agnostic client for communicating with TrustGraph services. + +## Features + +- 🌐 **WebSocket-based** - Real-time communication with TrustGraph services +- 📦 **Zero Dependencies** - No external runtime dependencies +- 🔐 **Authentication Support** - Optional API key authentication +- 🔄 **Auto-reconnection** - Handles connection failures gracefully +- 📝 **Full TypeScript Support** - Complete type definitions +- 🎯 **Framework Agnostic** - Works with any JavaScript framework or vanilla JS + +## Installation + +```bash +npm install @trustgraph/client +``` + +## Quick Start + +```typescript +import { createTrustGraphSocket } from "@trustgraph/client"; + +// Create a socket connection +const socket = createTrustGraphSocket("your-username"); + +// Query triples from the knowledge graph +const triples = await socket.triplesQuery( + { v: "http://example.org/subject", e: true }, + { v: "http://example.org/predicate", e: true }, + undefined, + 10, // limit +); + +console.log(triples); +``` + +## With Authentication + +```typescript +const socket = createTrustGraphSocket("your-username", "your-api-key"); +``` + +## Core APIs + +### Knowledge Graph Operations + +**Query Triples** + +```typescript +const triples = await socket.triplesQuery( + subject?: Value, // Optional subject filter + predicate?: Value, // Optional predicate filter + object?: Value, // Optional object filter + limit: number, // Maximum results + collection?: string // Optional collection name +); +``` + +**Graph Embeddings Query** + +```typescript +const entities = await socket.graphEmbeddingsQuery( + vectors: number[][], // Embedding vectors + limit: number, // Maximum results + collection?: string // Optional collection name +); +``` + +### Text & LLM Operations + +**Text Completion** + +```typescript +const response = await socket.textCompletion( + system: string, // System prompt + prompt: string, // User prompt + temperature?: number +); +``` + +**Graph RAG** + +```typescript +const answer = await socket.graphRag( + query: string, + options?: { + 'entity-limit'?: number, + 'triple-limit'?: number, + 'max-subgraph-size'?: number, + 'max-path-length'?: number + }, + collection?: string +); +``` + +**Agent** + +```typescript +socket.agent( + question: string, + think: (thought: string) => void, // Called when agent is thinking + observe: (observation: string) => void, // Called on observations + answer: (answer: string) => void, // Called with final answer + error: (error: string) => void, // Called on errors + collection?: string +); +``` + +**Embeddings** + +```typescript +const vectors = await socket.embeddings(text: string); +``` + +### Document Operations + +**Load Document** + +```typescript +await socket.loadDocument( + id: string, // Document ID + data: string, // Base64-encoded document + metadata: Triple[], // Document metadata as triples + collection?: string +); +``` + +**Load Text** + +```typescript +await socket.loadText( + id: string, // Document ID + text: string, // Plain text content + charset: string, // Character encoding (e.g., 'utf-8') + metadata: Triple[], // Document metadata as triples + collection?: string +); +``` + +### Library Operations + +**List Documents** + +```typescript +const docs = await socket.library.listDocuments( + user?: string, + collection?: string +); +``` + +**Get Document** + +```typescript +const doc = await socket.library.getDocument( + id: string, + user?: string, + collection?: string +); +``` + +**Delete Document** + +```typescript +await socket.library.deleteDocument( + id: string, + user?: string, + collection?: string +); +``` + +### Flow Operations + +Flows represent processing pipelines for documents and queries. + +**Create Flow API** + +```typescript +const flowApi = socket.flow("flow-id"); +// flowApi has same methods as socket but scoped to this flow +``` + +**Start Flow** + +```typescript +await socket.flows.startFlow( + flowId: string, + className: string, + description: string +); +``` + +**Stop Flow** + +```typescript +await socket.flows.stopFlow(flowId: string); +``` + +**List Flows** + +```typescript +const flowIds = await socket.flows.getFlows(); +``` + +**Get Flow Definition** + +```typescript +const flowDef = await socket.flows.getFlow(flowId: string); +``` + +**List Flow Classes** + +```typescript +const classes = await socket.flows.getFlowClasses(); +``` + +**Get Flow Class** + +```typescript +const classDef = await socket.flows.getFlowClass(className: string); +``` + +## Connection State Monitoring + +```typescript +// Subscribe to connection state changes +const unsubscribe = socket.onConnectionStateChange((state) => { + console.log("Status:", state.status); // 'connecting' | 'connected' | 'authenticated' | 'disconnected' | 'error' + console.log("Authenticated:", state.authenticated); + console.log("Error:", state.error); +}); + +// Unsubscribe when done +unsubscribe(); +``` + +## Data Types + +### Value + +Represents a subject, predicate, or object in a triple: + +```typescript +interface Value { + v: string; // Value (URI or literal) + e: boolean; // Is entity (true) or literal (false) + label?: string; // Optional human-readable label +} +``` + +### Triple + +Represents a subject-predicate-object relationship: + +```typescript +interface Triple { + s: Value; // Subject + p: Value; // Predicate + o: Value; // Object +} +``` + +## Advanced Usage + +### Custom Timeout and Retries + +Most methods accept optional timeout and retry parameters: + +```typescript +await socket.triplesQuery( + subject, + predicate, + object, + limit, + collection, + 30000, // timeout in ms + 5, // retry attempts +); +``` + +### Closing the Connection + +```typescript +socket.close(); +``` + +## Error Handling + +All async methods return Promises that reject on error: + +```typescript +try { + const result = await socket.triplesQuery(...); +} catch (error) { + console.error('Query failed:', error); +} +``` + +## React Integration + +For React applications, use the companion package: + +```bash +npm install @trustgraph/react-provider +``` + +See [@trustgraph/react-provider](https://github.com/trustgraph-ai/trustgraph-client) for React-specific hooks and providers. + +## API Reference + +Full API documentation is available in the TypeScript definitions. Your IDE will provide autocomplete and inline documentation for all methods. + +## License + +Apache 2.0 + +(c) KnowNext Inc., KnowNext Limited 2025 + diff --git a/docs/tech-specs/client-module.md b/docs/tech-specs/client-module.md new file mode 100644 index 00000000..93e24d9e --- /dev/null +++ b/docs/tech-specs/client-module.md @@ -0,0 +1,44 @@ +# TrustGraph Client Module - Technical Specification + +## Overview + +This module extracts reusable code from the existing TrustGraph Workbench +application and packages it as a standalone client library. The goal is to +enable developers to build TrustGraph user experiences without having to +reimplement API communication and state management from scratch. + +## Goals + +- Extract and package reusable WebSocket API code from TrustGraph Workbench +- Provide a clean, well-documented interface for TrustGraph WebSocket + communication +- Enable developers to quickly build TrustGraph UX applications +- Eliminate code duplication across TrustGraph UI projects +- Maintain compatibility with existing TrustGraph backend services + +## Non-Goals + +- REST API implementations (WebSocket only) +- UI components or presentation layer code +- Backend service implementations +- Authentication/authorization logic beyond what's needed for WebSocket + connections +- Application-specific business logic + +## Architecture + +## API Design + +## Implementation Plan + +## Testing Strategy + +## Dependencies + +## Security Considerations + +## Performance Considerations + +## Open Questions + +## References diff --git a/docs/tech-specs/streaming-support.md b/docs/tech-specs/streaming-support.md new file mode 100644 index 00000000..3f4207f1 --- /dev/null +++ b/docs/tech-specs/streaming-support.md @@ -0,0 +1,808 @@ +# Streaming Support for TrustGraph Client + +**Status**: Draft for Review +**Author**: Claude +**Date**: 2025-11-27 +**Version**: 1.0 + +## Executive Summary + +Extend the TrustGraph TypeScript client to support streaming responses for Graph RAG, Document RAG, Text Completion, and Prompt services. The client already has streaming infrastructure (`ServiceCallMulti`) used by Agent, but the other services only support single-response mode. This spec proposes minimal changes to enable streaming across all services while maintaining backward compatibility. + +## Background + +### Current State + +The client has **two request patterns**: + +1. **Single-response** (`makeRequest` → `ServiceCall`) + - Used by: text-completion, graph-rag, document-rag, prompt, and most other services + - Returns Promise that resolves with single response + - Example: `graphRag(text: string): Promise` + +2. **Multi-response** (`makeRequestMulti` → `ServiceCallMulti`) + - Used by: agent (thoughts/observations/answer), knowledge.getKgCore (large graph streaming) + - Accepts `receiver: (resp: unknown) => boolean` callback + - Receiver returns `true` to signal end-of-stream + - Example: `agent(question, think, observe, answer, error): void` + +### Backend Streaming Protocol + +Per `STREAMING-IMPLEMENTATION-NOTES.txt`, the backend supports streaming when `streaming: true` is added to requests: + +**Graph RAG / Document RAG**: +- Chunks arrive with `chunk` field +- Final chunk has `end_of_stream: true` + +**Text Completion**: +- Chunks arrive with `response` field +- Final chunk has `end_of_stream: true` + +**Prompt**: +- Chunks arrive with `text` field +- Final chunk has `end_of_stream: true` + +**Agent** (already implemented): +- Multiple messages with `chunk_type` (thought/observation/final-answer) +- Final chunk has `end_of_dialog: true` + +## Problem Statement + +**Primary Issue**: Users who want streaming responses for Graph RAG, Document RAG, Text Completion, or Prompt services must: +1. Drop down to `makeRequestMulti` and handle raw responses +2. Manually parse `chunk`/`response`/`text` fields +3. Check `end_of_stream` flag +4. Handle errors mid-stream + +**Secondary Issue**: The Agent API doesn't correctly implement the backend streaming protocol. The backend sends: +``` +{chunk_type: "thought", content: "I need to", end_of_message: false, end_of_dialog: false} +{chunk_type: "thought", content: " search", end_of_message: false, end_of_dialog: false} +``` + +But the client expects: +``` +{thought?: string, observation?: string, answer?: string} +``` + +The Agent implementation needs to be updated to handle incremental chunks with completion flags. + +## Goals + +1. **Fix Agent API** to correctly implement backend streaming protocol with chunk-level callbacks +2. **Add streaming variants** for text-completion, graph-rag, document-rag, and prompt services +3. **Maintain backward compatibility** - existing non-streaming APIs unchanged (except Agent which needs fixing) +4. **Policy-free implementation** - no state management (accumulation, buffering, etc.) in client layer +5. **Minimal callback interface** - single receiver callback with chunk and completion flag +6. **Minimal type changes** - reuse existing request/response types where possible + +## Non-Goals + +- Changing the existing non-streaming APIs +- Supporting streaming for services that don't stream (embeddings, triples, etc.) +- Implementing state management (accumulation, buffering) - that belongs in higher layers +- Changing the underlying `ServiceCallMulti` implementation + +## Design + +### 1. Type Additions + +Add streaming-specific response types to `src/models/messages.ts`: + +```typescript +// Agent streaming response (NEW - replaces old AgentResponse for streaming) +export interface AgentStreamingResponse { + chunk_type?: "thought" | "action" | "observation" | "final-answer" | "error"; + content?: string; + end_of_message?: boolean; // Current chunk type is complete + end_of_dialog?: boolean; // Entire agent dialog is complete + + // Legacy fields for backward compatibility with non-streaming + thought?: string; + observation?: string; + answer?: string; + error?: string; +} + +// Generic streaming response wrapper for RAG/completion services +export interface StreamingChunk { + chunk?: string; // Graph RAG, Document RAG + response?: string; // Text Completion + text?: string; // Prompt + end_of_stream?: boolean; + error?: { + message: string; + type?: string; + }; +} + +// Request types get optional streaming flag +export interface AgentRequest { + question: string; + user?: string; + streaming?: boolean; // NEW - enable streaming mode +} + +export interface GraphRagRequest { + query: string; + user?: string; + collection?: string; + "entity-limit"?: number; + "triple-limit"?: number; + "max-subgraph-size"?: number; + "max-path-length"?: number; + streaming?: boolean; // NEW +} + +export interface DocumentRagRequest { + query: string; + user?: string; + collection?: string; + "doc-limit"?: number; + streaming?: boolean; // NEW +} + +export interface TextCompletionRequest { + system: string; + prompt: string; + streaming?: boolean; // NEW +} + +export interface PromptRequest { + id: string; + terms: Record; + streaming?: boolean; // NEW +} + +export interface PromptResponse { + text: string; +} +``` + +### 2. BaseApi Additions + +No changes needed to `BaseApi` - `makeRequestMulti` already exists. + +### 3. FlowApi Changes + +#### 3.1 Fix Agent Method + +Update the existing `agent()` method to correctly handle the backend streaming protocol: + +```typescript +export class FlowApi { + /** + * Interacts with an AI agent that provides streaming responses + * BREAKING CHANGE: Callbacks now receive (chunk, complete) instead of full messages + */ + agent( + question: string, + think: (chunk: string, complete: boolean) => void, + observe: (chunk: string, complete: boolean) => void, + answer: (chunk: string, complete: boolean) => void, + error: (s: string) => void, + ) { + const receiver = (response: unknown) => { + const resp = response as AgentStreamingResponse; + + // Check for errors + if (resp.chunk_type === "error" || resp.error) { + const errorMessage = resp.content || resp.error || "Unknown agent error"; + error(typeof errorMessage === "string" ? errorMessage : String(errorMessage)); + return true; // End streaming on error + } + + // Handle streaming chunks by chunk_type + const content = resp.content || ""; + const messageComplete = !!resp.end_of_message; + const dialogComplete = !!resp.end_of_dialog; + + switch (resp.chunk_type) { + case "thought": + think(content, messageComplete); + break; + case "observation": + observe(content, messageComplete); + break; + case "final-answer": + answer(content, messageComplete); + break; + case "action": + // Actions are typically not streamed incrementally, just logged + console.log("Agent action:", content); + break; + } + + return dialogComplete; // End when backend signals end_of_dialog + }; + + return this.api + .makeRequestMulti( + "agent", + { + question: question, + user: this.api.user, + streaming: true, // Always use streaming mode + }, + receiver, + 120000, + 2, + this.flowId, + ) + .catch((err) => { + const errorMessage = + err instanceof Error ? err.message : err?.toString() || "Unknown error"; + error(`Agent request failed: ${errorMessage}`); + }); + } + +#### 3.2 Add New Streaming Methods + +Add streaming variants for other services alongside existing methods in `src/socket/trustgraph-socket.ts`: + +```typescript + // ... existing non-streaming methods unchanged ... + + /** + * Performs Graph RAG query with streaming response + * @param text - Query text + * @param receiver - Called for each chunk with (chunk, complete) where complete=true on final chunk + * @param onError - Called on error + * @param options - Graph RAG options + * @param collection - Collection name + */ + graphRagStreaming( + text: string, + receiver: (chunk: string, complete: boolean) => void, + onError: (error: string) => void, + options?: GraphRagOptions, + collection?: string, + ): void { + const recv = (response: unknown): boolean => { + const resp = response as StreamingChunk; + + if (resp.error) { + onError(resp.error.message); + return true; // End streaming + } + + const chunk = resp.chunk || ""; + const complete = !!resp.end_of_stream; + + receiver(chunk, complete); + + return complete; // End when backend signals end_of_stream + }; + + this.api.makeRequestMulti( + "graph-rag", + { + query: text, + user: this.api.user, + collection: collection || "default", + "entity-limit": options?.entityLimit, + "triple-limit": options?.tripleLimit, + "max-subgraph-size": options?.maxSubgraphSize, + "max-path-length": options?.pathLength, + streaming: true, + }, + recv, + 60000, + undefined, + this.flowId, + ); + } + + /** + * Performs Document RAG query with streaming response + * @param text - Query text + * @param receiver - Called for each chunk with (chunk, complete) where complete=true on final chunk + * @param onError - Called on error + * @param docLimit - Maximum documents to retrieve + * @param collection - Collection name + */ + documentRagStreaming( + text: string, + receiver: (chunk: string, complete: boolean) => void, + onError: (error: string) => void, + docLimit?: number, + collection?: string, + ): void { + const recv = (response: unknown): boolean => { + const resp = response as StreamingChunk; + + if (resp.error) { + onError(resp.error.message); + return true; + } + + const chunk = resp.chunk || ""; + const complete = !!resp.end_of_stream; + + receiver(chunk, complete); + + return complete; + }; + + this.api.makeRequestMulti( + "document-rag", + { + query: text, + user: this.api.user, + collection: collection || "default", + "doc-limit": docLimit, + streaming: true, + }, + recv, + 60000, + undefined, + this.flowId, + ); + } + + /** + * Performs text completion with streaming response + * @param system - System prompt + * @param text - User prompt + * @param receiver - Called for each chunk with (chunk, complete) where complete=true on final chunk + * @param onError - Called on error + */ + textCompletionStreaming( + system: string, + text: string, + receiver: (chunk: string, complete: boolean) => void, + onError: (error: string) => void, + ): void { + const recv = (response: unknown): boolean => { + const resp = response as StreamingChunk; + + if (resp.error) { + onError(resp.error.message); + return true; + } + + // Text completion uses 'response' field, not 'chunk' + const chunk = resp.response || ""; + const complete = !!resp.end_of_stream; + + receiver(chunk, complete); + + return complete; + }; + + this.api.makeRequestMulti( + "text-completion", + { + system: system, + prompt: text, + streaming: true, + }, + recv, + 30000, + undefined, + this.flowId, + ); + } + + /** + * Executes a prompt template with streaming response + * @param id - Prompt template ID + * @param terms - Template variables + * @param receiver - Called for each chunk with (chunk, complete) where complete=true on final chunk + * @param onError - Called on error + */ + promptStreaming( + id: string, + terms: Record, + receiver: (chunk: string, complete: boolean) => void, + onError: (error: string) => void, + ): void { + const recv = (response: unknown): boolean => { + const resp = response as StreamingChunk; + + if (resp.error) { + onError(resp.error.message); + return true; + } + + // Prompt service uses 'text' field + const chunk = resp.text || ""; + const complete = !!resp.end_of_stream; + + receiver(chunk, complete); + + return complete; + }; + + this.api.makeRequestMulti( + "prompt", + { + id: id, + terms: terms, + streaming: true, + }, + recv, + 30000, + undefined, + this.flowId, + ); + } +} +``` + +### 4. BaseApi Convenience Methods (Optional) + +For users who don't need flow routing, add streaming methods to BaseApi: + +```typescript +export class BaseApi { + // Existing methods... + + /** + * Streaming text completion without flow routing + */ + textCompletionStreaming( + system: string, + prompt: string, + receiver: (chunk: string, complete: boolean) => void, + onError: (error: string) => void, + ): void { + const flowApi = new FlowApi(this, undefined); + flowApi.textCompletionStreaming(system, prompt, receiver, onError); + } + + // Similar for graphRagStreaming, documentRagStreaming, promptStreaming... +} +``` + +**Recommendation**: Add these for consistency with existing non-streaming methods on BaseApi. + +## Implementation Plan + +### Phase 1: Core Types (1 hour) +1. Add `streaming?: boolean` to request types +2. Add `StreamingChunk` interface +3. Add `PromptRequest` and `PromptResponse` types (currently missing) + +### Phase 2: FlowApi Streaming Methods (2 hours) +1. Implement `textCompletionStreaming` +2. Implement `graphRagStreaming` +3. Implement `documentRagStreaming` +4. Implement `promptStreaming` +5. Add JSDoc comments + +### Phase 3: BaseApi Convenience Methods (1 hour) +1. Add streaming methods to BaseApi +2. Update interface definitions +3. Update README with streaming examples + +### Phase 4: Testing (2 hours) +1. Add unit tests for streaming methods +2. Add integration tests against mock WebSocket +3. Test error handling mid-stream +4. Test timeout behavior +5. Test concurrent streaming requests + +### Phase 5: Documentation (1 hour) +1. Update README with streaming examples +2. Add streaming guide to docs/ +3. Update API reference + +**Total Estimated Time**: 7 hours + +## Testing Strategy + +### Unit Tests + +```typescript +describe("FlowApi streaming", () => { + it("should stream graph-rag chunks", async () => { + const chunks: Array<{ chunk: string; complete: boolean }> = []; + + flowApi.graphRagStreaming( + "test query", + (chunk, complete) => { + chunks.push({ chunk, complete }); + }, + (error) => fail(error), + ); + + // Simulate streaming chunks + mockWebSocket.simulateMessage({ chunk: "Hello", end_of_stream: false }); + mockWebSocket.simulateMessage({ chunk: " world", end_of_stream: false }); + mockWebSocket.simulateMessage({ chunk: "", end_of_stream: true }); + + expect(chunks).toEqual([ + { chunk: "Hello", complete: false }, + { chunk: " world", complete: false }, + { chunk: "", complete: true }, + ]); + }); + + it("should handle errors mid-stream", async () => { + let errorMsg = ""; + const chunks: string[] = []; + + flowApi.graphRagStreaming( + "test query", + (chunk, complete) => { + chunks.push(chunk); + }, + (error) => { + errorMsg = error; + }, + ); + + mockWebSocket.simulateMessage({ chunk: "Partial", end_of_stream: false }); + mockWebSocket.simulateMessage({ + error: { message: "LLM timeout" }, + end_of_stream: true, + }); + + expect(errorMsg).toBe("LLM timeout"); + expect(chunks).toEqual(["Partial"]); // Receiver gets chunks before error + }); +}); +``` + +### Integration Tests + +Test against actual TrustGraph backend (manual testing): +1. Start TrustGraph backend with streaming enabled +2. Test each streaming method with real queries +3. Verify chunks arrive in order +4. Verify end_of_stream handling +5. Test error scenarios (invalid query, timeout) + +## Migration Guide + +### For Users + +#### Graph RAG / Document RAG / Text Completion / Prompt + +**Before (non-streaming)**: +```typescript +const response = await flowApi.graphRag("What is machine learning?"); +console.log(response); // Full text after 10-30 seconds +``` + +**After (streaming)**: +```typescript +let accumulated = ""; + +flowApi.graphRagStreaming( + "What is machine learning?", + (chunk, complete) => { + accumulated += chunk; + updateDisplay(accumulated); + + if (complete) { + console.log("Final:", accumulated); + } + }, + (error) => { + console.error("Error:", error); + } +); +``` + +#### Agent (BREAKING CHANGE) + +**Before (old client - incorrect)**: +```typescript +flowApi.agent( + "What is machine learning?", + (thought) => console.log("Thinking:", thought), // Full thought received + (observation) => console.log("Observing:", observation), // Full observation received + (answer) => console.log("Answer:", answer), // Full answer received + (error) => console.error(error), +); +``` + +**After (updated to match backend)**: +```typescript +let currentThought = ""; +let currentObservation = ""; +let currentAnswer = ""; + +flowApi.agent( + "What is machine learning?", + (chunk, complete) => { + currentThought += chunk; + updateThinkingDisplay(currentThought); + if (complete) { + console.log("Thought complete:", currentThought); + currentThought = ""; // Reset for next thought + } + }, + (chunk, complete) => { + currentObservation += chunk; + updateObservationDisplay(currentObservation); + if (complete) { + console.log("Observation complete:", currentObservation); + currentObservation = ""; + } + }, + (chunk, complete) => { + currentAnswer += chunk; + updateAnswerDisplay(currentAnswer); + if (complete) { + console.log("Final answer:", currentAnswer); + } + }, + (error) => console.error(error), +); +``` + +### Gradual Adoption + +**For Graph RAG / Document RAG / Text Completion / Prompt**: +1. Continue using non-streaming APIs (no breaking changes) +2. Add streaming variants for user-facing chat interfaces first +3. Keep non-streaming for background tasks +4. Optionally add feature flag to toggle streaming on/off + +**For Agent (BREAKING CHANGE)**: +1. Existing Agent users MUST update their callbacks to handle (chunk, complete) signature +2. Add accumulation logic in callback handlers +3. Use `complete` flag to detect when to reset accumulator or take final action + +## Risks and Mitigations + +### Risk 1: BREAKING CHANGE for Agent API +**Concern**: Existing Agent users must update their code when they upgrade. + +**Mitigation**: +- Document the breaking change clearly in release notes +- Provide migration examples in this spec +- Consider: Add deprecation warning in previous version before breaking change +- Consider: Bump major version to signal breaking change +- The old API was incorrect anyway - this fixes a bug in the client + +### Risk 2: API Surface Growth +**Concern**: Adding 4 new methods per API class (FlowApi, BaseApi) increases maintenance burden. + +**Mitigation**: +- Methods share identical structure (only field name differs: chunk/response/text) +- Could extract common streaming handler if needed +- Backend already implements streaming, so no protocol risk + +### Risk 3: TypeScript Type Safety +**Concern**: `StreamingChunk` union type may be confusing (chunk vs response vs text). + +**Mitigation**: +- Each service method documents which field it uses +- Runtime code checks correct field +- Implementation is simple enough that field selection is obvious + +### Risk 4: State Management in User Code +**Concern**: Users must manually accumulate chunks if they need full text. + +**Mitigation**: +- This is intentional - client stays policy-free +- Higher-level abstractions (React hooks, etc.) can provide accumulation +- For users who don't need streaming behavior, non-streaming APIs remain unchanged + +## Future Enhancements + +### 1. Async Iterator API +Provide a modern streaming API using async iterators: + +```typescript +async *graphRagStream(text: string): AsyncGenerator { + // Wraps graphRagStreaming in async iterator +} + +// Usage: +for await (const chunk of flowApi.graphRagStream("query")) { + console.log(chunk); +} +``` + +### 2. Retry on Stream Interruption +Currently, retries only apply to initial request. Could add mid-stream retry: +- Detect connection drop mid-stream +- Resume from last chunk (if backend supports resumption) + +### 3. Client-Side Buffering +For very fast chunk arrival, buffer multiple chunks before calling receiver: +- Reduces callback frequency +- Could be opt-in via options parameter +- Note: This would add policy to the client, may be better in higher layers + +### 4. Stream Cancellation +Allow users to cancel in-flight streaming requests: +```typescript +const cancel = flowApi.graphRagStreaming(...); +// Later: +cancel(); +``` + +## Alternatives Considered + +### Alternative 1: Separate Callbacks for Chunk and Complete +Use three callbacks: onChunk, onComplete, onError: + +```typescript +graphRagStreaming( + text: string, + onChunk: (chunk: string, accumulated: string) => void, + onComplete: (fullText: string) => void, + onError: (error: string) => void, +) +``` + +**Rejected because**: +- Adds state management (accumulation) to the client layer +- Harder for implementations that need both signals at once +- More verbose callback signature + +### Alternative 2: Unified Streaming Flag on Existing Methods +Modify existing methods to detect streaming callbacks: + +```typescript +graphRag( + text: string, + options?: GraphRagOptions, + collection?: string, + receiver?: (chunk: string, complete: boolean) => void, +): Promise | void +``` + +**Rejected because**: +- Violates single responsibility principle +- Return type becomes conditional (Promise vs void) +- Hard to type correctly in TypeScript +- Confusing API (streaming vs non-streaming behavior implicit) + +### Alternative 3: Separate StreamingFlowApi Class +Create a parallel API class for streaming: + +```typescript +export class StreamingFlowApi { + graphRag(text: string, receiver: ..., onError: ...): void; + documentRag(text: string, receiver: ..., onError: ...): void; +} +``` + +**Rejected because**: +- Duplicates all configuration and state management +- Users must manage two API instances +- No clear benefit over method suffixes + +## Open Questions + +1. **Should we add streaming to Prompt service?** + - Prompt service is not currently in client (no PromptRequest/Response types) + - Could add it alongside streaming support + - **Decision**: Yes, add it for completeness (mentioned in backend docs) + +2. **Should we add TypeScript overloads?** + - Allow `graphRagStreaming(text, callbacks)` vs `graphRagStreaming(text, options, callbacks)` + - **Decision**: Use optional parameters (simpler implementation) + +## Conclusion + +This proposal adds streaming support to the TrustGraph client and fixes the Agent API to correctly implement the backend protocol: + +**Changes**: +1. **Fix Agent API** (BREAKING): Update callbacks to receive `(chunk, complete)` instead of full messages +2. Add `streaming?: boolean` flag to all request types +3. Add `AgentStreamingResponse` and `StreamingChunk` response types +4. Add `*Streaming` method variants to FlowApi and BaseApi for RAG/completion services +5. Use consistent two-callback pattern: `receiver(chunk, complete)` and `onError(message)` across all services + +The implementation is straightforward (~7-10 hours including Agent fix), stays minimal and focused, and provides a clean foundation for higher-level abstractions to build upon. + +**Key Design Principles**: +- **Policy-free**: No accumulation or buffering in client layer +- **Minimal callbacks**: Single receiver gets both chunk and completion signal +- **Protocol-correct**: Agent now properly implements backend's chunk_type/content/end_of_message protocol +- **Consistent**: Same pattern across all streaming services +- **Backward compatible**: Existing non-streaming APIs unchanged (except Agent which needs fixing) + +**Breaking Changes**: +- Agent API callbacks change from `(fullMessage: string)` to `(chunk: string, complete: boolean)` +- Requires major version bump + +**Recommendation**: Approve and implement in current sprint. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..462b81af --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,35 @@ +import js from "@eslint/js"; +import tseslint from "typescript-eslint"; +import globals from "globals"; + +export default tseslint.config( + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ["**/*.{ts,tsx}"], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + globals: { + ...globals.browser, + ...globals.es2021, + }, + }, + rules: { + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/no-explicit-any": "warn", + }, + }, + { + ignores: ["dist/**", "node_modules/**", "*.config.js"], + }, +); diff --git a/package.json b/package.json new file mode 100644 index 00000000..c47b1002 --- /dev/null +++ b/package.json @@ -0,0 +1,66 @@ +{ + "name": "@trustgraph/client", + "version": "1.6.0", + "description": "TypeScript client for TrustGraph", + "type": "module", + "main": "dist/index.esm.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "rollup -c", + "dev": "rollup -c -w", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "lint": "eslint src", + "typecheck": "tsc --noEmit", + "prettify": "prettier --write .", + "prepare": "npm run build" + }, + "keywords": [ + "trustgraph", + "websocket", + "typescript", + "client" + ], + "author": "KnowNext Limited", + "license": "Apache-2.0", + "devDependencies": { + "@eslint/js": "^9.37.0", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-typescript": "^11.1.6", + "@vitest/ui": "^3.2.4", + "eslint": "^9.39.3", + "globals": "^16.4.0", + "happy-dom": "^20.0.10", + "jiti": "^2.6.1", + "prettier": "^3.6.2", + "rollup": "^4.9.0", + "tslib": "^2.6.2", + "typescript": "^5.3.3", + "typescript-eslint": "^8.46.0", + "vitest": "^3.2.4" + }, + "directories": { + "doc": "docs" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/trustgraph-ai/trustgraph-client.git" + }, + "bugs": { + "url": "https://github.com/trustgraph-ai/trustgraph-client/issues" + }, + "homepage": "https://github.com/trustgraph-ai/trustgraph-client#readme" +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 00000000..ffa5e319 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,30 @@ +import resolve from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; +import typescript from "@rollup/plugin-typescript"; + +export default { + input: "src/index.ts", + output: [ + { + file: "dist/index.cjs", + format: "cjs", + sourcemap: true, + }, + { + file: "dist/index.esm.js", + format: "esm", + sourcemap: true, + }, + ], + external: ["react", "react-dom"], + plugins: [ + resolve(), + commonjs(), + typescript({ + tsconfig: "./tsconfig.json", + declaration: true, + declarationDir: "dist", + rootDir: "src", + }), + ], +}; diff --git a/src/__tests__/flows-api.test.ts b/src/__tests__/flows-api.test.ts new file mode 100644 index 00000000..e011af72 --- /dev/null +++ b/src/__tests__/flows-api.test.ts @@ -0,0 +1,221 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { FlowsApi } from "../socket/trustgraph-socket"; +import { FlowResponse } from "../models/messages"; + +describe("FlowsApi", () => { + let mockApi: { + makeRequest: ReturnType; + }; + let flowsApi: FlowsApi; + + beforeEach(() => { + mockApi = { + makeRequest: vi.fn(), + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + flowsApi = new FlowsApi(mockApi as any); + }); + + describe("startFlow", () => { + it("should call makeRequest with correct types and parameters", async () => { + const mockResponse: FlowResponse = { + flow: "started", + description: "Flow started successfully", + }; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + const result = await flowsApi.startFlow( + "test-flow-id", + "test-class", + "Test description", + ); + + expect(mockApi.makeRequest).toHaveBeenCalledWith( + "flow", + { + operation: "start-flow", + "flow-id": "test-flow-id", + "blueprint-name": "test-class", + description: "Test description", + }, + 30000, + ); + expect(result).toEqual(mockResponse); + }); + + it("should use FlowRequest and FlowResponse types", async () => { + const mockResponse: FlowResponse = {}; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + await flowsApi.startFlow("id", "class", "desc"); + + // Verify the call signature matches FlowRequest/FlowResponse types + const callArgs = mockApi.makeRequest.mock.calls[0]; + const request = callArgs[1]; + + // These properties should match FlowRequest interface + expect(request).toHaveProperty("operation"); + expect(request).toHaveProperty("flow-id"); + expect(request).toHaveProperty("blueprint-name"); + expect(request).toHaveProperty("description"); + }); + }); + + describe("stopFlow", () => { + it("should call makeRequest with correct types and parameters", async () => { + const mockResponse: FlowResponse = { + flow: "stopped", + description: "Flow stopped successfully", + }; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + const result = await flowsApi.stopFlow("test-flow-id"); + + expect(mockApi.makeRequest).toHaveBeenCalledWith( + "flow", + { + operation: "stop-flow", + "flow-id": "test-flow-id", + }, + 30000, + ); + expect(result).toEqual(mockResponse); + }); + + it("should use FlowRequest and FlowResponse types", async () => { + const mockResponse: FlowResponse = {}; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + await flowsApi.stopFlow("id"); + + // Verify the call signature matches FlowRequest/FlowResponse types + const callArgs = mockApi.makeRequest.mock.calls[0]; + const request = callArgs[1]; + + // These properties should match FlowRequest interface + expect(request).toHaveProperty("operation"); + expect(request).toHaveProperty("flow-id"); + }); + }); + + describe("getFlows", () => { + it("should return flow-ids array from response", async () => { + const mockResponse: FlowResponse = { + "flow-ids": ["flow1", "flow2", "flow3"], + }; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + const result = await flowsApi.getFlows(); + + expect(mockApi.makeRequest).toHaveBeenCalledWith( + "flow", + { + operation: "list-flows", + }, + 60000, + ); + expect(result).toEqual(["flow1", "flow2", "flow3"]); + }); + + it("should return empty array when flow-ids is undefined", async () => { + const mockResponse: FlowResponse = {}; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + const result = await flowsApi.getFlows(); + + expect(result).toEqual([]); + }); + + it("should handle response with flow-ids property correctly", async () => { + // This test ensures we're accessing the hyphenated property name correctly + const mockResponse = { + "flow-ids": ["test-flow"], + "other-property": "should-be-ignored", + }; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + const result = await flowsApi.getFlows(); + + expect(result).toEqual(["test-flow"]); + }); + }); + + describe("getFlowBlueprints", () => { + it("should return blueprint-names array from response", async () => { + const mockResponse: FlowResponse = { + "blueprint-names": ["class1", "class2"], + }; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + const result = await flowsApi.getFlowBlueprints(); + + expect(mockApi.makeRequest).toHaveBeenCalledWith( + "flow", + { + operation: "list-blueprints", + }, + 60000, + ); + expect(result).toEqual(["class1", "class2"]); + }); + + it("should handle response with blueprint-names property correctly", async () => { + // This test ensures we're accessing the hyphenated property name correctly + const mockResponse = { + "blueprint-names": ["test-class"], + "other-property": "should-be-ignored", + }; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + const result = await flowsApi.getFlowBlueprints(); + + expect(result).toEqual(["test-class"]); + }); + }); + + describe("getFlow", () => { + it("should call makeRequest with correct parameters and parse JSON", async () => { + const flowDefinition = { type: "flow", config: "test" }; + const mockResponse: FlowResponse = { + flow: JSON.stringify(flowDefinition), // Must be valid JSON string + description: "Test flow", + }; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + const result = await flowsApi.getFlow("test-flow-id"); + + expect(mockApi.makeRequest).toHaveBeenCalledWith( + "flow", + { + operation: "get-flow", + "flow-id": "test-flow-id", + }, + 60000, + ); + expect(result).toEqual(flowDefinition); // Result should be parsed JSON + }); + }); + + describe("getFlowBlueprint", () => { + it("should call makeRequest with correct parameters and parse JSON", async () => { + const blueprintDefinition = { type: "blueprint", name: "test-blueprint" }; + const mockResponse: FlowResponse = { + "blueprint-definition": JSON.stringify(blueprintDefinition), // Must be valid JSON string + description: "Test blueprint", + }; + mockApi.makeRequest.mockResolvedValue(mockResponse); + + const result = await flowsApi.getFlowBlueprint("test-class"); + + expect(mockApi.makeRequest).toHaveBeenCalledWith( + "flow", + { + operation: "get-blueprint", + "blueprint-name": "test-class", + }, + 60000, + ); + expect(result).toEqual(blueprintDefinition); // Result should be parsed JSON + }); + }); +}); diff --git a/src/__tests__/messages.test.ts b/src/__tests__/messages.test.ts new file mode 100644 index 00000000..65d96c9e --- /dev/null +++ b/src/__tests__/messages.test.ts @@ -0,0 +1,370 @@ +import { describe, it, expect } from "vitest"; +import type { + RequestMessage, + ApiResponse, + TextCompletionRequest, + TextCompletionResponse, + GraphRagRequest, + GraphRagResponse, + AgentRequest, + AgentResponse, + EmbeddingsRequest, + EmbeddingsResponse, + GraphEmbeddingsQueryRequest, + GraphEmbeddingsQueryResponse, + TriplesQueryRequest, + LoadDocumentRequest, + LoadTextRequest, + LibraryRequest, + LibraryResponse, + FlowRequest, + FlowResponse, + DocumentMetadata, + ProcessingMetadata, +} from "../models/messages"; + +describe("Message Types", () => { + describe("RequestMessage", () => { + it("should have correct structure", () => { + const message: RequestMessage = { + id: "test-id", + service: "test-service", + request: { test: "data" }, + }; + + expect(message.id).toBe("test-id"); + expect(message.service).toBe("test-service"); + expect(message.request).toEqual({ test: "data" }); + }); + }); + + describe("ApiResponse", () => { + it("should have correct structure", () => { + const response: ApiResponse = { + id: "test-id", + response: { result: "success" }, + }; + + expect(response.id).toBe("test-id"); + expect(response.response).toEqual({ result: "success" }); + }); + }); + + describe("TextCompletionRequest", () => { + it("should have correct structure", () => { + const request: TextCompletionRequest = { + system: "You are a helpful assistant", + prompt: "Hello, world!", + }; + + expect(request.system).toBe("You are a helpful assistant"); + expect(request.prompt).toBe("Hello, world!"); + }); + }); + + describe("TextCompletionResponse", () => { + it("should have correct structure", () => { + const response: TextCompletionResponse = { + response: "Hello! How can I help you today?", + }; + + expect(response.response).toBe("Hello! How can I help you today?"); + }); + }); + + describe("GraphRagRequest", () => { + it("should have correct structure with required query", () => { + const request: GraphRagRequest = { + query: "What is the capital of France?", + }; + + expect(request.query).toBe("What is the capital of France?"); + }); + + it("should have correct structure with optional parameters", () => { + const request: GraphRagRequest = { + query: "What is the capital of France?", + "entity-limit": 100, + "triple-limit": 50, + "max-subgraph-size": 2000, + "max-path-length": 3, + }; + + expect(request.query).toBe("What is the capital of France?"); + expect(request["entity-limit"]).toBe(100); + expect(request["triple-limit"]).toBe(50); + expect(request["max-subgraph-size"]).toBe(2000); + expect(request["max-path-length"]).toBe(3); + }); + }); + + describe("GraphRagResponse", () => { + it("should have correct structure", () => { + const response: GraphRagResponse = { + response: "The capital of France is Paris.", + }; + + expect(response.response).toBe("The capital of France is Paris."); + }); + }); + + describe("AgentRequest", () => { + it("should have correct structure", () => { + const request: AgentRequest = { + question: "What is the weather like today?", + }; + + expect(request.question).toBe("What is the weather like today?"); + }); + }); + + describe("AgentResponse", () => { + it("should have correct structure with all fields", () => { + const response: AgentResponse = { + thought: "I need to check the weather", + observation: "Weather API shows sunny conditions", + answer: "It is sunny today", + error: undefined, + }; + + expect(response.thought).toBe("I need to check the weather"); + expect(response.observation).toBe("Weather API shows sunny conditions"); + expect(response.answer).toBe("It is sunny today"); + expect(response.error).toBeUndefined(); + }); + + it("should handle error response", () => { + const response: AgentResponse = { + error: { type: "agent-error", message: "Weather service unavailable" }, + }; + + expect(response.error?.message).toBe("Weather service unavailable"); + expect(response.error?.type).toBe("agent-error"); + }); + }); + + describe("EmbeddingsRequest", () => { + it("should have correct structure", () => { + const request: EmbeddingsRequest = { + texts: ["This is a test sentence for embedding", "Another text"], + }; + + expect(request.texts).toEqual(["This is a test sentence for embedding", "Another text"]); + }); + }); + + describe("EmbeddingsResponse", () => { + it("should have correct structure", () => { + // vectors[text_index][dimension_index] - one vector per input text + const response: EmbeddingsResponse = { + vectors: [ + [0.1, 0.2, 0.3], // First text's vector + [0.4, 0.5, 0.6], // Second text's vector + ], + }; + + expect(response.vectors).toEqual([ + [0.1, 0.2, 0.3], + [0.4, 0.5, 0.6], + ]); + }); + }); + + describe("GraphEmbeddingsQueryRequest", () => { + it("should have correct structure", () => { + const request: GraphEmbeddingsQueryRequest = { + vector: [0.1, 0.2, 0.3], + limit: 10, + }; + + expect(request.vector).toEqual([0.1, 0.2, 0.3]); + expect(request.limit).toBe(10); + }); + }); + + describe("GraphEmbeddingsQueryResponse", () => { + it("should have correct structure", () => { + const response: GraphEmbeddingsQueryResponse = { + entities: [ + { entity: { t: "i", i: "http://example.org/entity1" }, score: 0.95 }, + { entity: { t: "i", i: "http://example.org/entity2" }, score: 0.87 }, + ], + }; + + expect(response.entities).toHaveLength(2); + expect(response.entities[0].score).toBe(0.95); + expect(response.entities[0].entity?.t).toBe("i"); + expect((response.entities[0].entity as { t: "i"; i: string }).i).toBe("http://example.org/entity1"); + expect(response.entities[1].score).toBe(0.87); + }); + }); + + describe("TriplesQueryRequest", () => { + it("should have correct structure with all fields", () => { + const request: TriplesQueryRequest = { + s: { t: "i", i: "http://example.org/subject" }, + p: { t: "i", i: "http://example.org/predicate" }, + o: { t: "l", v: "object value" }, + limit: 100, + }; + + expect((request.s as { t: "i"; i: string }).i).toBe("http://example.org/subject"); + expect((request.p as { t: "i"; i: string }).i).toBe("http://example.org/predicate"); + expect((request.o as { t: "l"; v: string }).v).toBe("object value"); + expect(request.limit).toBe(100); + }); + + it("should handle optional fields", () => { + const request: TriplesQueryRequest = { + limit: 50, + }; + + expect(request.s).toBeUndefined(); + expect(request.p).toBeUndefined(); + expect(request.o).toBeUndefined(); + expect(request.limit).toBe(50); + }); + }); + + describe("LoadDocumentRequest", () => { + it("should have correct structure", () => { + const request: LoadDocumentRequest = { + id: "doc-123", + data: "base64-encoded-document-data", + metadata: [ + { + s: { t: "i", i: "http://example.org/doc-123" }, + p: { t: "i", i: "http://example.org/title" }, + o: { t: "l", v: "Test Document" }, + }, + ], + }; + + expect(request.id).toBe("doc-123"); + expect(request.data).toBe("base64-encoded-document-data"); + expect(request.metadata).toHaveLength(1); + }); + }); + + describe("LoadTextRequest", () => { + it("should have correct structure", () => { + const request: LoadTextRequest = { + id: "text-123", + text: "This is some text to load", + charset: "utf-8", + metadata: [], + }; + + expect(request.id).toBe("text-123"); + expect(request.text).toBe("This is some text to load"); + expect(request.charset).toBe("utf-8"); + expect(request.metadata).toEqual([]); + }); + }); + + describe("DocumentMetadata", () => { + it("should have correct structure", () => { + const metadata: DocumentMetadata = { + id: "doc-123", + time: 1640995200000, + kind: "pdf", + title: "Test Document", + comments: "A test document", + metadata: [], + user: "test-user", + tags: ["test", "document"], + }; + + expect(metadata.id).toBe("doc-123"); + expect(metadata.time).toBe(1640995200000); + expect(metadata.kind).toBe("pdf"); + expect(metadata.title).toBe("Test Document"); + expect(metadata.comments).toBe("A test document"); + expect(metadata.user).toBe("test-user"); + expect(metadata.tags).toEqual(["test", "document"]); + }); + }); + + describe("ProcessingMetadata", () => { + it("should have correct structure", () => { + const metadata: ProcessingMetadata = { + id: "proc-123", + "document-id": "doc-123", + time: 1640995200000, + flow: "default-flow", + user: "test-user", + collection: "test-collection", + tags: ["processing", "test"], + }; + + expect(metadata.id).toBe("proc-123"); + expect(metadata["document-id"]).toBe("doc-123"); + expect(metadata.time).toBe(1640995200000); + expect(metadata.flow).toBe("default-flow"); + expect(metadata.user).toBe("test-user"); + expect(metadata.collection).toBe("test-collection"); + expect(metadata.tags).toEqual(["processing", "test"]); + }); + }); + + describe("LibraryRequest", () => { + it("should have correct structure", () => { + const request: LibraryRequest = { + operation: "list_documents", + user: "test-user", + collection: "test-collection", + }; + + expect(request.operation).toBe("list_documents"); + expect(request.user).toBe("test-user"); + expect(request.collection).toBe("test-collection"); + }); + }); + + describe("LibraryResponse", () => { + it("should have correct structure", () => { + const response: LibraryResponse = { + error: new Error(), + "document-metadatas": [ + { + id: "doc-1", + title: "Document 1", + time: 1640995200000, + }, + ], + }; + + expect(response.error).toBeInstanceOf(Error); + expect(response["document-metadatas"]).toHaveLength(1); + expect(response["document-metadatas"]![0].id).toBe("doc-1"); + }); + }); + + describe("FlowRequest", () => { + it("should have correct structure", () => { + const request: FlowRequest = { + operation: "get_flow", + "flow-id": "default-flow", + }; + + expect(request.operation).toBe("get_flow"); + expect(request["flow-id"]).toBe("default-flow"); + }); + }); + + describe("FlowResponse", () => { + it("should have correct structure", () => { + const response: FlowResponse = { + "flow-ids": ["flow-1", "flow-2"], + flow: "flow-definition", + description: "A test flow", + error: undefined, + }; + + expect(response["flow-ids"]).toEqual(["flow-1", "flow-2"]); + expect(response.flow).toBe("flow-definition"); + expect(response.description).toBe("A test flow"); + expect(response.error).toBeUndefined(); + }); + }); +}); diff --git a/src/__tests__/service-call-multi.test.ts b/src/__tests__/service-call-multi.test.ts new file mode 100644 index 00000000..c414c574 --- /dev/null +++ b/src/__tests__/service-call-multi.test.ts @@ -0,0 +1,285 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { ServiceCallMulti } from "../socket/service-call-multi"; + +// Mock WebSocket constants +vi.stubGlobal("WebSocket", { + OPEN: 1, + CONNECTING: 0, + CLOSING: 2, + CLOSED: 3, +}); + +// Mock Socket interface +const mockSocket = { + inflight: {} as Record, + ws: { + send: vi.fn(), + readyState: 1, // WebSocket.OPEN + }, + reopen: vi.fn(), +}; + +// Mock setTimeout and clearTimeout +const mockSetTimeout = vi.fn(); +const mockClearTimeout = vi.fn(); + +vi.stubGlobal("setTimeout", mockSetTimeout); +vi.stubGlobal("clearTimeout", mockClearTimeout); + +describe("ServiceCallMulti", () => { + let mockSuccess: ReturnType; + let mockError: ReturnType; + let mockReceiver: ReturnType; + let serviceCallMulti: ServiceCallMulti; + + beforeEach(() => { + vi.clearAllMocks(); + mockSuccess = vi.fn(); + mockError = vi.fn(); + mockReceiver = vi.fn(); + mockSocket.inflight = {} as Record; + mockSocket.ws = { + send: vi.fn(), + readyState: 1, // WebSocket.OPEN + }; + mockSocket.reopen.mockClear(); + + serviceCallMulti = new ServiceCallMulti( + "test-mid", + { id: "test-id", service: "test-service", request: { test: "data" } }, + mockSuccess, + mockError, + 5000, // 5 second timeout + 3, // 3 retries + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mockSocket as any, + mockReceiver, + ); + }); + + it("should initialize with correct properties", () => { + expect(serviceCallMulti.mid).toBe("test-mid"); + expect(serviceCallMulti.timeout).toBe(5000); + expect(serviceCallMulti.retries).toBe(3); + expect(serviceCallMulti.complete).toBe(false); + expect(serviceCallMulti.socket).toBe(mockSocket); + expect(serviceCallMulti.receiver).toBe(mockReceiver); + }); + + it("should register itself in socket inflight when started", () => { + serviceCallMulti.start(); + + expect(mockSocket.inflight["test-mid"]).toBe(serviceCallMulti); + }); + + it("should send message on successful attempt", () => { + serviceCallMulti.start(); + + expect(mockSocket.ws.send).toHaveBeenCalledWith( + JSON.stringify({ + id: "test-id", + service: "test-service", + request: { test: "data" }, + }), + ); + expect(mockSetTimeout).toHaveBeenCalled(); + }); + + it("should handle response when receiver returns true (completion)", () => { + mockReceiver.mockReturnValue(true); // Signal completion + const response = { result: "success" }; + + serviceCallMulti.start(); + serviceCallMulti.onReceived(response); + + expect(mockReceiver).toHaveBeenCalledWith(response); + expect(serviceCallMulti.complete).toBe(true); + expect(mockSuccess).toHaveBeenCalledWith(response); + expect(mockClearTimeout).toHaveBeenCalled(); + expect(mockSocket.inflight["test-mid"]).toBeUndefined(); + }); + + it("should handle response when receiver returns false (continue)", () => { + mockReceiver.mockReturnValue(false); // Signal to continue + const response = { partial: "data" }; + + serviceCallMulti.start(); + serviceCallMulti.onReceived(response); + + expect(mockReceiver).toHaveBeenCalledWith(response); + expect(serviceCallMulti.complete).toBe(false); + expect(mockSuccess).not.toHaveBeenCalled(); + expect(mockClearTimeout).not.toHaveBeenCalled(); + expect(mockSocket.inflight["test-mid"]).toBe(serviceCallMulti); + }); + + it("should handle timeout and retry", () => { + serviceCallMulti.start(); + + // Initial retries should be 3, but start() calls attempt() which decrements to 2 + expect(serviceCallMulti.retries).toBe(2); + + // Simulate timeout + serviceCallMulti.onTimeout(); + + expect(mockClearTimeout).toHaveBeenCalled(); + expect(serviceCallMulti.retries).toBe(1); // Should decrement from 2 to 1 + }); + + it("should exhaust retries and call error callback", () => { + // Set retries to 0 to force immediate failure + serviceCallMulti.retries = 0; + + serviceCallMulti.start(); + + expect(mockError).toHaveBeenCalledWith("Ran out of retries"); + expect(mockSocket.inflight["test-mid"]).toBeUndefined(); + }); + + it("should handle WebSocket send failure", () => { + mockSocket.ws.send.mockImplementation(() => { + throw new Error("Connection failed"); + }); + + serviceCallMulti.start(); + + expect(mockSocket.reopen).toHaveBeenCalled(); + + // With exponential backoff, the delay should be calculated as: + // SOCKET_RECONNECTION_TIMEOUT * Math.pow(2, 3 - retries) + random + // Since retries is decremented to 2 after start(), it's 3 - 2 = 1 + // So base delay is 2000 * 2^1 = 4000, plus random up to 1000 + // The delay should be between 4000 and 5000ms (capped at 30000) + const callArgs = mockSetTimeout.mock.calls[0]; + expect(callArgs[0]).toEqual(expect.any(Function)); + expect(callArgs[1]).toBeGreaterThanOrEqual(4000); + expect(callArgs[1]).toBeLessThanOrEqual(5000); + }); + + it("should handle missing WebSocket connection", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (mockSocket as any).ws = null; + + serviceCallMulti.start(); + + // Should trigger reopen and schedule with exponential backoff + expect(mockSocket.reopen).toHaveBeenCalled(); + + // Same calculation as above - base delay 4000ms + random up to 1000ms + const callArgs = mockSetTimeout.mock.calls[0]; + expect(callArgs[0]).toEqual(expect.any(Function)); + expect(callArgs[1]).toBeGreaterThanOrEqual(4000); + expect(callArgs[1]).toBeLessThanOrEqual(5000); + }); + + it("should not process response if already complete", () => { + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + serviceCallMulti.complete = true; + serviceCallMulti.onReceived({ result: "test" }); + + expect(consoleSpy).toHaveBeenCalledWith( + "test-mid", + "should not happen, request is already complete", + ); + + consoleSpy.mockRestore(); + }); + + it("should not timeout if already complete", () => { + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + serviceCallMulti.complete = true; + serviceCallMulti.onTimeout(); + + expect(consoleSpy).toHaveBeenCalledWith( + "test-mid", + "timeout should not happen, request is already complete", + ); + + consoleSpy.mockRestore(); + }); + + it("should not attempt if already complete", () => { + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + serviceCallMulti.complete = true; + serviceCallMulti.attempt(); + + expect(consoleSpy).toHaveBeenCalledWith( + "test-mid", + "attempt should not be called, request is already complete", + ); + + consoleSpy.mockRestore(); + }); + + it("should handle streaming responses correctly", () => { + mockReceiver + .mockReturnValueOnce(false) // First response - continue + .mockReturnValueOnce(false) // Second response - continue + .mockReturnValueOnce(true); // Third response - complete + + serviceCallMulti.start(); + + // First response + serviceCallMulti.onReceived({ chunk: 1 }); + expect(serviceCallMulti.complete).toBe(false); + expect(mockSuccess).not.toHaveBeenCalled(); + + // Second response + serviceCallMulti.onReceived({ chunk: 2 }); + expect(serviceCallMulti.complete).toBe(false); + expect(mockSuccess).not.toHaveBeenCalled(); + + // Third response (final) + serviceCallMulti.onReceived({ chunk: 3, final: true }); + expect(serviceCallMulti.complete).toBe(true); + expect(mockSuccess).toHaveBeenCalledWith({ chunk: 3, final: true }); + }); + + it("should handle receiver function errors gracefully", () => { + mockReceiver.mockImplementation(() => { + throw new Error("Receiver error"); + }); + + serviceCallMulti.start(); + + expect(() => { + serviceCallMulti.onReceived({ test: "data" }); + }).toThrow("Receiver error"); + }); + + it("should handle multiple timeout scenarios", () => { + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + serviceCallMulti.start(); + + // After start, retries should be 2 (decremented from 3) + expect(serviceCallMulti.retries).toBe(2); + + // First timeout + serviceCallMulti.onTimeout(); + expect(serviceCallMulti.retries).toBe(1); + + // Second timeout + serviceCallMulti.onTimeout(); + expect(serviceCallMulti.retries).toBe(0); + + consoleSpy.mockRestore(); + }); + + it("should clean up properly when receiver signals completion", () => { + mockReceiver.mockReturnValue(true); + + serviceCallMulti.start(); + + const response = { final: true }; + serviceCallMulti.onReceived(response); + + expect(serviceCallMulti.complete).toBe(true); + expect(mockClearTimeout).toHaveBeenCalled(); + expect(mockSocket.inflight["test-mid"]).toBeUndefined(); + expect(mockSuccess).toHaveBeenCalledWith(response); + }); +}); diff --git a/src/__tests__/service-call.test.ts b/src/__tests__/service-call.test.ts new file mode 100644 index 00000000..acd72111 --- /dev/null +++ b/src/__tests__/service-call.test.ts @@ -0,0 +1,239 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { ServiceCall } from "../socket/service-call"; + +// Mock WebSocket constants +vi.stubGlobal("WebSocket", { + OPEN: 1, + CONNECTING: 0, + CLOSING: 2, + CLOSED: 3, +}); + +// Mock Socket interface +const mockSocket = { + inflight: {} as Record, + ws: { + send: vi.fn(), + readyState: 1, // WebSocket.OPEN + }, + reopen: vi.fn(), +}; + +// Mock setTimeout and clearTimeout +const mockSetTimeout = vi.fn(); +const mockClearTimeout = vi.fn(); + +vi.stubGlobal("setTimeout", mockSetTimeout); +vi.stubGlobal("clearTimeout", mockClearTimeout); + +describe("ServiceCall", () => { + let mockSuccess: ReturnType; + let mockError: ReturnType; + let serviceCall: ServiceCall; + + beforeEach(() => { + vi.clearAllMocks(); + mockSuccess = vi.fn(); + mockError = vi.fn(); + mockSocket.inflight = {} as Record; + mockSocket.ws = { + send: vi.fn(), + readyState: 1, // WebSocket.OPEN + }; + mockSocket.reopen.mockClear(); + + serviceCall = new ServiceCall( + "test-mid", + { id: "test-id", service: "test-service", request: { test: "data" } }, + mockSuccess, + mockError, + 5000, // 5 second timeout + 3, // 3 retries + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mockSocket as any, + ); + }); + + it("should initialize with correct properties", () => { + expect(serviceCall.mid).toBe("test-mid"); + expect(serviceCall.timeout).toBe(5000); + expect(serviceCall.retries).toBe(3); + expect(serviceCall.complete).toBe(false); + expect(serviceCall.socket).toBe(mockSocket); + }); + + it("should register itself in socket inflight when started", () => { + serviceCall.start(); + + expect(mockSocket.inflight["test-mid"]).toBe(serviceCall); + }); + + it("should send message on successful attempt", () => { + serviceCall.start(); + + expect(mockSocket.ws.send).toHaveBeenCalledWith( + JSON.stringify({ + id: "test-id", + service: "test-service", + request: { test: "data" }, + }), + ); + expect(mockSetTimeout).toHaveBeenCalled(); + }); + + it("should handle successful response", () => { + const responseData = { result: "success" }; + const message = { response: responseData }; + + serviceCall.start(); + serviceCall.onReceived(message); + + expect(serviceCall.complete).toBe(true); + expect(mockSuccess).toHaveBeenCalledWith(responseData); + expect(mockClearTimeout).toHaveBeenCalled(); + expect(mockSocket.inflight["test-mid"]).toBeUndefined(); + }); + + it("should handle timeout and retry", () => { + serviceCall.start(); + + // Initial retries should be 3, but start() calls attempt() which decrements to 2 + expect(serviceCall.retries).toBe(2); + + // Simulate timeout + serviceCall.onTimeout(); + + expect(mockClearTimeout).toHaveBeenCalled(); + expect(serviceCall.retries).toBe(1); // Should decrement from 2 to 1 + }); + + it("should exhaust retries and call error callback", () => { + // Set retries to 0 to force immediate failure + serviceCall.retries = 0; + + serviceCall.start(); + + expect(mockError).toHaveBeenCalledWith("Ran out of retries"); + expect(mockSocket.inflight["test-mid"]).toBeUndefined(); + }); + + it("should handle WebSocket send failure", () => { + mockSocket.ws.send.mockImplementation(() => { + throw new Error("Connection failed"); + }); + + serviceCall.start(); + + // Should NOT call reopen anymore - BaseApi handles reconnection + expect(mockSocket.reopen).not.toHaveBeenCalled(); + + // With exponential backoff, the delay should be calculated as: + // SOCKET_RECONNECTION_TIMEOUT * Math.pow(2, 3 - retries) + random + // Since retries is decremented to 2 after start(), it's 3 - 2 = 1 + // So base delay is 2000 * 2^1 = 4000, plus random up to 1000 + // The delay should be between 4000 and 5000ms (capped at 30000) + const callArgs = mockSetTimeout.mock.calls[0]; + expect(callArgs[0]).toEqual(expect.any(Function)); + expect(callArgs[1]).toBeGreaterThanOrEqual(4000); + expect(callArgs[1]).toBeLessThanOrEqual(5000); + }); + + it("should handle missing WebSocket connection", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (mockSocket as any).ws = null; + + serviceCall.start(); + + // Should NOT trigger reopen - just wait for BaseApi to reconnect + expect(mockSocket.reopen).not.toHaveBeenCalled(); + + // Same calculation as above - base delay 4000ms + random up to 1000ms + const callArgs = mockSetTimeout.mock.calls[0]; + expect(callArgs[0]).toEqual(expect.any(Function)); + expect(callArgs[1]).toBeGreaterThanOrEqual(4000); + expect(callArgs[1]).toBeLessThanOrEqual(5000); + }); + + it("should not process response if already complete", () => { + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + serviceCall.complete = true; + serviceCall.onReceived({ result: "test" }); + + expect(consoleSpy).toHaveBeenCalledWith( + "test-mid", + "should not happen, request is already complete", + ); + + consoleSpy.mockRestore(); + }); + + it("should not timeout if already complete", () => { + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + serviceCall.complete = true; + serviceCall.onTimeout(); + + expect(consoleSpy).toHaveBeenCalledWith( + "test-mid", + "timeout should not happen, request is already complete", + ); + + consoleSpy.mockRestore(); + }); + + it("should not attempt if already complete", () => { + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + serviceCall.complete = true; + serviceCall.attempt(); + + expect(consoleSpy).toHaveBeenCalledWith( + "test-mid", + "attempt should not be called, request is already complete", + ); + + consoleSpy.mockRestore(); + }); + + it("should handle multiple retries correctly", () => { + mockSocket.ws.send.mockImplementation(() => { + throw new Error("Connection failed"); + }); + + serviceCall.start(); + + // Should have decremented retries and scheduled a retry + expect(serviceCall.retries).toBe(2); + // Should NOT call reopen - BaseApi handles reconnection + expect(mockSocket.reopen).not.toHaveBeenCalled(); + }); + + it("should clean up properly on successful response", () => { + serviceCall.start(); + + const responseData = { success: true }; + const message = { response: responseData }; + serviceCall.onReceived(message); + + expect(serviceCall.complete).toBe(true); + expect(mockClearTimeout).toHaveBeenCalled(); + expect(mockSocket.inflight["test-mid"]).toBeUndefined(); + expect(mockSuccess).toHaveBeenCalledWith(responseData); + }); + + it("should handle edge case of negative retries", () => { + serviceCall.retries = -1; + + serviceCall.attempt(); + + expect(mockError).toHaveBeenCalledWith("Ran out of retries"); + }); + + it("should bind timeout callbacks correctly", () => { + serviceCall.start(); + + // Verify that setTimeout was called with a bound function + expect(mockSetTimeout).toHaveBeenCalledWith(expect.any(Function), 5000); + }); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..c7b5f4b2 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,10 @@ +// @trustgraph/client +// TrustGraph TypeScript Client + +// Export models (data types) +export * from "./models/Triple"; +export * from "./models/messages"; +export * from "./models/namespaces"; + +// Export socket client +export * from "./socket/trustgraph-socket"; diff --git a/src/models/Triple.ts b/src/models/Triple.ts new file mode 100644 index 00000000..c9d7ca4c --- /dev/null +++ b/src/models/Triple.ts @@ -0,0 +1,40 @@ +// Term type discriminators matching the wire format +// i = IRI, b = BLANK node, l = LITERAL, t = TRIPLE (reified) +export type TermType = "i" | "b" | "l" | "t"; + +export interface IriTerm { + t: "i"; + i: string; +} + +export interface BlankTerm { + t: "b"; + d: string; +} + +export interface LiteralTerm { + t: "l"; + v: string; + dt?: string; // datatype + ln?: string; // language +} + +export interface TripleTerm { + t: "t"; + tr?: Triple; +} + +export type Term = IriTerm | BlankTerm | LiteralTerm | TripleTerm; + +export interface PartialTriple { + s?: Term; + p?: Term; + o?: Term; +} + +export interface Triple { + s: Term; + p: Term; + o: Term; + g?: string; // graph (renamed from direc to match backend) +} diff --git a/src/models/messages.ts b/src/models/messages.ts new file mode 100644 index 00000000..26198521 --- /dev/null +++ b/src/models/messages.ts @@ -0,0 +1,496 @@ +import { Triple, Term } from "./Triple"; + +// FIXME: Better types? +export type Request = object; +export type Response = object; +export type Error = object | string; + +export interface ResponseError { + type?: string; + message: string; +} + +export interface RequestMessage { + id: string; + service: string; + request: Request; + flow?: string; +} + +export interface ApiResponse { + id: string; + response: Response; +} + +export interface Metadata { + id?: string; + metadata?: Triple[]; + user?: string; + collection?: string; +} + +export interface EntityEmbeddings { + entity?: Term; + vectors?: number[][]; +} + +export interface GraphEmbeddings { + metadata?: Metadata; + entities?: EntityEmbeddings[]; +} + +export interface TextCompletionRequest { + system: string; + prompt: string; + streaming?: boolean; +} + +export interface TextCompletionResponse { + response: string; + // Streaming fields + end_of_stream?: boolean; + error?: { + message: string; + type?: string; + }; + // Token usage (appears in final message) + in_token?: number; + out_token?: number; + model?: string; +} + +export interface GraphRagRequest { + query: string; + user?: string; + collection?: string; + "entity-limit"?: number; // Default: 50 + "triple-limit"?: number; // Default: 30 + "max-subgraph-size"?: number; // Default: 1000 + "max-path-length"?: number; // Default: 2 + streaming?: boolean; +} + +export interface GraphRagResponse { + response: string; + // Streaming fields + chunk?: string; + end_of_stream?: boolean; + error?: { + message: string; + type?: string; + }; + // Token usage (appears in final message) + in_token?: number; + out_token?: number; + model?: string; + // Explainability fields + message_type?: "chunk" | "explain"; + explain_id?: string; + explain_graph?: string; // Named graph where explain data is stored (e.g., urn:graph:retrieval) + end_of_session?: boolean; +} + +export interface DocumentRagRequest { + query: string; + user?: string; + collection?: string; + "doc-limit"?: number; // Default: 20 + streaming?: boolean; +} + +export interface DocumentRagResponse { + response: string; + // Streaming fields + chunk?: string; + end_of_stream?: boolean; + error?: { + message: string; + type?: string; + }; + // Token usage (appears in final message) + in_token?: number; + out_token?: number; + model?: string; + // Explainability fields + message_type?: "chunk" | "explain"; + explain_id?: string; + explain_graph?: string; + end_of_session?: boolean; +} + +export interface AgentRequest { + question: string; + user?: string; + streaming?: boolean; +} + +export interface AgentResponse { + // Streaming response format (new protocol) + chunk_type?: "thought" | "action" | "observation" | "answer" | "final-answer" | "explain" | "error"; + content?: string; + end_of_message?: boolean; + end_of_dialog?: boolean; + + // Legacy fields for backward compatibility with non-streaming + thought?: string; + observation?: string; + answer?: string; + error?: ResponseError; + + // Token usage (appears in final message) + in_token?: number; + out_token?: number; + model?: string; + + // Explainability fields + message_type?: "chunk" | "explain"; + explain_id?: string; + explain_graph?: string; +} + +export interface EmbeddingsRequest { + texts: string[]; +} + +export interface EmbeddingsResponse { + vectors: number[][]; // One vector per input text +} + +export interface GraphEmbeddingsQueryRequest { + vector: number[]; // Single query vector + limit: number; + user?: string; + collection?: string; +} + +export interface EntityMatch { + entity: Term | null; + score: number; +} + +export interface GraphEmbeddingsQueryResponse { + entities: EntityMatch[]; +} + +export interface TriplesQueryRequest { + s?: Term; + p?: Term; + o?: Term; + g?: string; // Named graph URI filter (plain string, not Term) + limit: number; + user?: string; + collection?: string; +} + +export interface TriplesQueryResponse { + response: Triple[]; +} + +export interface RowsQueryRequest { + query: string; + user?: string; + collection?: string; + variables?: Record; + operation_name?: string; +} + +export interface RowsQueryResponse { + data?: Record; + errors?: Record[]; + extensions?: Record; + values?: unknown[]; +} + +export interface NlpQueryRequest { + question: string; + max_results?: number; +} + +export interface NlpQueryResponse { + graphql_query?: string; + variables?: Record; + detected_schemas?: Record[]; + confidence?: number; +} + +export interface StructuredQueryRequest { + question: string; + user?: string; + collection?: string; +} + +export interface StructuredQueryResponse { + data?: Record; + errors?: Record[]; +} + +export interface RowEmbeddingsQueryRequest { + vector: number[]; // Single query vector + schema_name: string; + user?: string; + collection?: string; + index_name?: string; + limit?: number; +} + +export interface RowEmbeddingsMatch { + index_name: string; + index_value: string[]; + text: string; + score: number; +} + +export interface RowEmbeddingsQueryResponse { + matches?: RowEmbeddingsMatch[]; + error?: { + message: string; + type?: string; + }; +} + +export interface LoadDocumentRequest { + id?: string; + data: string; + metadata?: Triple[]; +} + +export type LoadDocumentResponse = void; + +export interface LoadTextRequest { + id?: string; + text: string; + charset?: string; + metadata?: Triple[]; +} + +export type LoadTextResponse = void; + +export interface DocumentMetadata { + id?: string; + time?: number; + kind?: string; + title?: string; + comments?: string; + metadata?: Triple[]; + user?: string; + tags?: string[]; + "document-type"?: string; +} + +export interface ProcessingMetadata { + id?: string; + "document-id"?: string; + time?: number; + flow?: string; + user?: string; + collection?: string; + tags?: string[]; +} + +export interface LibraryRequest { + operation: string; + "document-id"?: string; + "processing-id"?: string; + "document-metadata"?: DocumentMetadata; + "processing-metadata"?: ProcessingMetadata; + content?: string; + user?: string; + collection?: string; + metadata?: Triple[]; + id?: string; + flow?: string; +} + +export interface LibraryResponse { + error: Error; + "document-metadata"?: DocumentMetadata; + content?: string; + "document-metadatas"?: DocumentMetadata[]; + "processing-metadata"?: ProcessingMetadata; +} + +export interface KnowledgeRequest { + operation: string; + user?: string; + id?: string; + flow?: string; + collection?: string; + triples?: Triple[]; + "graph-embeddings"?: GraphEmbeddings; +} + +export interface KnowledgeResponse { + error?: Error; + ids?: string[]; + eos?: boolean; + triples?: Triple[]; + "graph-embeddings"?: GraphEmbeddings; +} + +export interface FlowRequest { + operation: string; + "blueprint-name"?: string; + "blueprint-definition"?: string; + description?: string; + "flow-id"?: string; + parameters?: Record; + user?: string; +} + +export interface FlowResponse { + "blueprint-names"?: string[]; + "flow-ids"?: string[]; + ids?: string[]; + flow?: string; + "blueprint-definition"?: string; + description?: string; + error?: + | { + message?: string; + } + | Error; +} + +export interface PromptRequest { + id: string; + terms: Record; + streaming?: boolean; +} + +export interface PromptResponse { + text: string; + // Streaming fields + end_of_stream?: boolean; + error?: { + message: string; + type?: string; + }; + // Token usage (appears in final message) + in_token?: number; + out_token?: number; + model?: string; +} + +export type ConfigRequest = object; +export type ConfigResponse = object; + +// Chunked Upload Types + +export interface ChunkedUploadDocumentMetadata { + id: string; + time: number; + kind: string; + title: string; + comments?: string; + metadata?: Triple[]; + user: string; + collection?: string; + tags?: string[]; +} + +export interface BeginUploadRequest { + operation: "begin-upload"; + "document-metadata": ChunkedUploadDocumentMetadata; + "total-size": number; + "chunk-size"?: number; +} + +export interface BeginUploadResponse { + "upload-id": string; + "chunk-size": number; + "total-chunks": number; + error?: ResponseError; +} + +export interface UploadChunkRequest { + operation: "upload-chunk"; + "upload-id": string; + "chunk-index": number; + content: string; // base64-encoded + user: string; +} + +export interface UploadChunkResponse { + "upload-id": string; + "chunk-index": number; + "chunks-received": number; + "total-chunks": number; + "bytes-received": number; + "total-bytes": number; + error?: ResponseError; +} + +export interface CompleteUploadRequest { + operation: "complete-upload"; + "upload-id": string; + user: string; +} + +export interface CompleteUploadResponse { + "document-id": string; + "object-id": string; + error?: ResponseError; +} + +export interface GetUploadStatusRequest { + operation: "get-upload-status"; + "upload-id": string; + user: string; +} + +export interface GetUploadStatusResponse { + "upload-id": string; + "upload-state": "in-progress" | "completed" | "expired"; + "chunks-received": number; + "total-chunks": number; + "received-chunks": number[]; + "missing-chunks": number[]; + "bytes-received": number; + "total-bytes": number; + error?: ResponseError; +} + +export interface AbortUploadRequest { + operation: "abort-upload"; + "upload-id": string; + user: string; +} + +export interface AbortUploadResponse { + error?: ResponseError; +} + +export interface ListUploadsRequest { + operation: "list-uploads"; + user: string; +} + +export interface UploadSession { + "upload-id": string; + "document-id": string; + "document-metadata-json": string; + "total-size": number; + "chunk-size": number; + "total-chunks": number; + "chunks-received": number; + "created-at": string; +} + +export interface ListUploadsResponse { + "upload-sessions": UploadSession[]; + error?: ResponseError; +} + +export interface StreamDocumentRequest { + operation: "stream-document"; + "document-id": string; + "chunk-size"?: number; + user: string; +} + +export interface StreamDocumentResponse { + content: string; // base64-encoded chunk + "chunk-index": number; + "total-chunks": number; + error?: ResponseError; +} diff --git a/src/models/namespaces.ts b/src/models/namespaces.ts new file mode 100644 index 00000000..df75fc04 --- /dev/null +++ b/src/models/namespaces.ts @@ -0,0 +1,42 @@ +/** + * RDF namespace constants for TrustGraph + * Used for querying explainability data, provenance chains, and knowledge graph + */ + +// TrustGraph namespace +export const TG = "https://trustgraph.ai/ns/"; +export const TG_QUERY = TG + "query"; +export const TG_EDGE_COUNT = TG + "edgeCount"; +export const TG_SELECTED_EDGE = TG + "selectedEdge"; +export const TG_EDGE = TG + "edge"; +export const TG_REASONING = TG + "reasoning"; +export const TG_CONTENT = TG + "content"; +export const TG_REIFIES = TG + "reifies"; +export const TG_DOCUMENT = TG + "document"; + +// W3C PROV-O namespace +export const PROV = "http://www.w3.org/ns/prov#"; +export const PROV_STARTED_AT_TIME = PROV + "startedAtTime"; +export const PROV_WAS_DERIVED_FROM = PROV + "wasDerivedFrom"; +export const PROV_WAS_GENERATED_BY = PROV + "wasGeneratedBy"; +export const PROV_ACTIVITY = PROV + "Activity"; +export const PROV_ENTITY = PROV + "Entity"; + +// RDFS namespace +export const RDFS = "http://www.w3.org/2000/01/rdf-schema#"; +export const RDFS_LABEL = RDFS + "label"; + +// RDF namespace +export const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; +export const RDF_TYPE = RDF + "type"; + +// Schema.org namespace (used in document metadata) +export const SCHEMA = "https://schema.org/"; +export const SCHEMA_NAME = SCHEMA + "name"; +export const SCHEMA_DESCRIPTION = SCHEMA + "description"; +export const SCHEMA_AUTHOR = SCHEMA + "author"; +export const SCHEMA_KEYWORDS = SCHEMA + "keywords"; + +// SKOS namespace +export const SKOS = "http://www.w3.org/2004/02/skos/core#"; +export const SKOS_DEFINITION = SKOS + "definition"; diff --git a/src/socket/service-call-multi.ts b/src/socket/service-call-multi.ts new file mode 100644 index 00000000..16e3e6ff --- /dev/null +++ b/src/socket/service-call-multi.ts @@ -0,0 +1,171 @@ +import { RequestMessage } from "../models/messages"; + +// Constant defining the delay before attempting to reconnect a WebSocket +// (2 seconds) +export const SOCKET_RECONNECTION_TIMEOUT = 2000; + +// Forward declare Socket type to avoid circular dependency +// Using a minimal interface that matches what BaseApi provides +interface Socket { + ws?: WebSocket; + inflight: { [key: string]: ServiceCallMulti }; + reopen: () => void; + getNextId?: () => string; + user?: string; +} + +export class ServiceCallMulti { + constructor( + mid: string, + msg: RequestMessage, + success: (resp: unknown) => void, + error: (err: object | string) => void, + timeout: number, + retries: number, + socket: Socket, + receiver: (resp: unknown) => boolean, + ) { + this.mid = mid; + this.msg = msg; + this.success = success; + this.error = error; + this.timeout = timeout; + this.retries = retries; + this.socket = socket; + this.complete = false; + this.receiver = receiver; + } + + mid: string; + msg: RequestMessage; + success: (resp: unknown) => void; + error: (err: object | string) => void; + receiver: (resp: unknown) => boolean; + timeoutId?: ReturnType; + timeout: number; + retries: number; + socket: Socket; + complete: boolean; + + start() { + this.socket.inflight[this.mid] = this; + this.attempt(); + } + + onReceived(resp: object) { + if (this.complete == true) + console.log(this.mid, "should not happen, request is already complete"); + + const fin = this.receiver(resp); + + if (fin) { + this.complete = true; + + // console.log("Received for", this.mid); + clearTimeout(this.timeoutId); + this.timeoutId = undefined; + delete this.socket.inflight[this.mid]; + this.success(resp); + } + } + + /** + * Called when socket connects - immediately retry if we were waiting + */ + retryNow() { + if (this.complete) return; + + // Clear any pending backoff timer + clearTimeout(this.timeoutId); + this.timeoutId = undefined; + + // Restore retry count since we didn't actually fail + this.retries++; + + // Attempt immediately + this.attempt(); + } + + onTimeout() { + if (this.complete == true) + console.log( + this.mid, + "timeout should not happen, request is already complete", + ); + + console.log("Request", this.mid, "timed out"); + clearTimeout(this.timeoutId); + this.attempt(); + } + + attempt() { + // console.log("attempt:", this.mid); + + if (this.complete == true) + console.log( + this.mid, + "attempt should not be called, request is already complete", + ); + + this.retries--; + + if (this.retries < 0) { + console.log("Request", this.mid, "ran out of retries"); + + clearTimeout(this.timeoutId); + delete this.socket.inflight[this.mid]; + + this.error("Ran out of retries"); + return; // Exit early - no more attempts + } + + // Check if WebSocket connection is available and ready + if (this.socket.ws && this.socket.ws.readyState === WebSocket.OPEN) { + try { + this.socket.ws.send(JSON.stringify(this.msg)); + this.timeoutId = setTimeout(this.onTimeout.bind(this), this.timeout); + + return; + } catch (e) { + console.log("Error:", e); + console.log("Message send failure, retry..."); + + // Calculate backoff delay with jitter + const backoffDelay = Math.min( + SOCKET_RECONNECTION_TIMEOUT * Math.pow(2, 3 - this.retries) + + Math.random() * 1000, + 30000, // Max 30 seconds + ); + + this.timeoutId = setTimeout(this.attempt.bind(this), backoffDelay); + + console.log("Reopen..."); + // Attempt to reopen the WebSocket connection + this.socket.reopen(); + } + } else { + // No WebSocket connection available or not ready + // Check if socket is connecting + if ( + this.socket.ws && + this.socket.ws.readyState === WebSocket.CONNECTING + ) { + // Wait a bit longer for connection to establish + setTimeout(this.attempt.bind(this), 500); + } else { + // Socket is closed or closing, trigger reopen + console.log("Socket not ready, reopening..."); + this.socket.reopen(); + + // Calculate backoff delay + const backoffDelay = Math.min( + SOCKET_RECONNECTION_TIMEOUT * Math.pow(2, 3 - this.retries) + + Math.random() * 1000, + 30000, + ); + + setTimeout(this.attempt.bind(this), backoffDelay); + } + } + } +} diff --git a/src/socket/service-call.ts b/src/socket/service-call.ts new file mode 100644 index 00000000..4b5fe80b --- /dev/null +++ b/src/socket/service-call.ts @@ -0,0 +1,239 @@ +import { RequestMessage } from "../models/messages"; + +// Constant defining the delay before attempting to reconnect a WebSocket +// (2 seconds) +export const SOCKET_RECONNECTION_TIMEOUT = 2000; + +// Forward declare Socket type to avoid circular dependency +// Using a minimal interface that matches what BaseApi provides +interface Socket { + ws?: WebSocket; + inflight: { [key: string]: ServiceCall }; + reopen: () => void; + getNextId?: () => string; + user?: string; +} + +/** + * ServiceCall represents a single request/response cycle over a WebSocket + * connection with built-in retry logic, timeout handling, and completion + * tracking. + * + * This class manages the lifecycle of a service call including: + * - Sending the initial request + * - Handling timeouts and retries + * - Managing completion state + * - Cleaning up resources + */ +export class ServiceCall { + constructor( + mid: string, // Message ID - unique identifier for this request + msg: RequestMessage, // The actual message/request to send + success: (resp: unknown) => void, // Callback function called on + // successful response + error: (err: object | string) => void, // Callback function called on error/failure + timeout: number, // Timeout duration in milliseconds + retries: number, // Number of retry attempts allowed + socket: Socket, // WebSocket instance to send the message through + ) { + this.mid = mid; + this.msg = msg; + this.success = success; + this.error = error; + this.timeout = timeout; + this.retries = retries; + this.socket = socket; + this.complete = false; // Track if this request has completed + } + + // Properties + mid: string; // Message identifier + msg: RequestMessage; // The request message + success: (resp: unknown) => void; // Success callback + error: (err: object | string) => void; // Error callback + timeoutId?: ReturnType; // Reference to the active timeout timer + timeout: number; // Timeout duration in milliseconds + retries: number; // Remaining retry attempts + socket: Socket; // WebSocket connection reference + complete: boolean; // Flag indicating if request is complete + + /** + * Initiates the service call by registering it with the socket's inflight + * requests and making the first attempt to send the message + */ + start() { + // Register this request as "in-flight" so responses can be matched to it + this.socket.inflight[this.mid] = this; + // Make the first attempt to send the message + this.attempt(); + } + + /** + * Called when a response is received for this request + * Handles cleanup and calls the success or error callback based on response + * + * @param resp - The response object received from the server + */ + onReceived(resp: object) { + // Defensive check - this shouldn't happen but log if it does + if (this.complete == true) + console.log(this.mid, "should not happen, request is already complete"); + + // Mark as complete to prevent duplicate processing + this.complete = true; + + // Clean up timeout timer + clearTimeout(this.timeoutId); + this.timeoutId = undefined; + + // Remove from inflight requests tracker + delete this.socket.inflight[this.mid]; + + // Check if the response contains an error (error can be directly in resp or nested under response) + let errorToHandle: unknown = null; + + // Check for direct error in response + if (resp && typeof resp === "object" && "error" in resp) { + errorToHandle = (resp as Record).error; + } + // Check for nested error under response property + else if (resp && typeof resp === "object" && "response" in resp) { + const response = (resp as Record).response; + if (response && typeof response === "object" && "error" in response) { + errorToHandle = (response as Record).error; + } + } + + if (errorToHandle) { + // Response contains an error - call error callback + const errorObj = errorToHandle as Record; + const errorMessage = + (typeof errorObj.message === "string" ? errorObj.message : null) || + (typeof errorObj.type === "string" ? errorObj.type : null) || + "Unknown error"; + console.log( + "ServiceCall: API error detected in response:", + errorMessage, + "Full error:", + errorToHandle, + ); + this.error(new Error(errorMessage)); + return; + } + + // Extract the response field from the message object + // The resp parameter is the full message: {id, response, complete} + // We need to pass just the response field to the success callback + const responseData = (resp as { response?: unknown }).response; + this.success(responseData); + } + + /** + * Called when socket connects - immediately retry if we were waiting + */ + retryNow() { + if (this.complete) return; + + // Clear any pending backoff timer + clearTimeout(this.timeoutId); + this.timeoutId = undefined; + + // Restore retry count since we didn't actually fail + this.retries++; + + // Attempt immediately + this.attempt(); + } + + /** + * Called when the request times out + * Triggers another attempt if retries are available + */ + onTimeout() { + // Defensive check - this shouldn't happen but log if it does + if (this.complete == true) + console.log( + this.mid, + "timeout should not happen, request is already complete", + ); + + console.log("Request", this.mid, "timed out"); + + // Clear the current timeout + clearTimeout(this.timeoutId); + + // Try again (this will check retry count) + this.attempt(); + } + + /** + * Calculates exponential backoff delay with jitter + * @returns backoff delay in milliseconds + */ + calculateBackoff() { + return Math.min( + SOCKET_RECONNECTION_TIMEOUT * Math.pow(2, 3 - this.retries) + + Math.random() * 1000, + 30000, // Max 30 seconds + ); + } + + /** + * Core retry logic - attempts to send the message over the WebSocket + * Handles retries and waits for BaseApi to handle reconnection + */ + attempt() { + // Defensive check - this shouldn't be called on completed requests + if (this.complete == true) + console.log( + this.mid, + "attempt should not be called, request is already complete", + ); + + // Decrement retry counter + this.retries--; + + // Check if we've exhausted all retries + if (this.retries < 0) { + console.log("Request", this.mid, "ran out of retries"); + + // Clean up and call error callback + clearTimeout(this.timeoutId); + delete this.socket.inflight[this.mid]; + this.error("Ran out of retries"); + return; // Exit early - no more attempts + } + + // Check if WebSocket connection is available and ready + if (this.socket.ws && this.socket.ws.readyState === WebSocket.OPEN) { + try { + // Attempt to send the message as JSON + this.socket.ws.send(JSON.stringify(this.msg)); + + // Set up timeout for this attempt + this.timeoutId = setTimeout(this.onTimeout.bind(this), this.timeout); + + return; // Success - message sent, waiting for response or timeout + } catch (e) { + // Handle send failure - wait for BaseApi to handle reconnection + console.log("Error:", e); + console.log( + "Message send failure, waiting for socket reconnection...", + ); + + // Schedule retry with backoff - let BaseApi handle the reconnection + this.timeoutId = setTimeout( + this.attempt.bind(this), + this.calculateBackoff(), + ); + } + } else { + // No WebSocket connection available or not ready + // Let BaseApi handle reconnection, just wait and retry + console.log("Request", this.mid, "waiting for socket reconnection..."); + + // Use consistent backoff for all waiting scenarios + setTimeout(this.attempt.bind(this), this.calculateBackoff()); + } + } +} diff --git a/src/socket/trustgraph-socket.ts b/src/socket/trustgraph-socket.ts new file mode 100644 index 00000000..e3adf6b3 --- /dev/null +++ b/src/socket/trustgraph-socket.ts @@ -0,0 +1,2353 @@ +// Import core types and classes for the TrustGraph API +import { Triple, Term } from "../models/Triple"; +import { ServiceCallMulti } from "./service-call-multi"; +import { ServiceCall } from "./service-call"; + +// Import all message types for different services +import { + AgentRequest, + AgentResponse, + ConfigRequest, + ConfigResponse, + DocumentMetadata, + DocumentRagRequest, + DocumentRagResponse, + EmbeddingsRequest, + EmbeddingsResponse, + EntityMatch, + FlowRequest, + FlowResponse, + GraphEmbeddingsQueryRequest, + GraphEmbeddingsQueryResponse, + GraphRagRequest, + GraphRagResponse, + // KnowledgeRequest, + // KnowledgeResponse, + LibraryRequest, + LibraryResponse, + LoadDocumentRequest, + LoadDocumentResponse, + LoadTextRequest, + LoadTextResponse, + NlpQueryRequest, + NlpQueryResponse, + RowsQueryRequest, + RowsQueryResponse, + RowEmbeddingsQueryRequest, + RowEmbeddingsQueryResponse, + RowEmbeddingsMatch, + PromptRequest, + PromptResponse, + // ProcessingMetadata, + RequestMessage, + StructuredQueryRequest, + StructuredQueryResponse, + TextCompletionRequest, + TextCompletionResponse, + TriplesQueryRequest, + TriplesQueryResponse, + // Chunked upload types + ChunkedUploadDocumentMetadata, + BeginUploadRequest, + BeginUploadResponse, + UploadChunkRequest, + UploadChunkResponse, + CompleteUploadRequest, + CompleteUploadResponse, + GetUploadStatusRequest, + GetUploadStatusResponse, + AbortUploadRequest, + AbortUploadResponse, + ListUploadsRequest, + ListUploadsResponse, + UploadSession, + StreamDocumentRequest, + StreamDocumentResponse, + // EntityEmbeddings, + // Error, + // GraphEmbedding, + // Metadata, + // Request, + // Response, +} from "../models/messages"; + +// GraphRAG options interface for configurable parameters +export interface GraphRagOptions { + entityLimit?: number; + tripleLimit?: number; + maxSubgraphSize?: number; + pathLength?: number; +} + +// Metadata included in final streaming message +export interface StreamingMetadata { + in_token?: number; + out_token?: number; + model?: string; +} + +// Explainability event data +export interface ExplainEvent { + explainId: string; + explainGraph: string; // Named graph where explain data is stored (e.g., urn:graph:retrieval) +} + +// Configuration constants +const SOCKET_RECONNECTION_TIMEOUT = 2000; // 2 seconds between reconnection +// attempts +const SOCKET_URL = "/api/socket"; // WebSocket endpoint path + +/** + * Socket interface defining all available operations for the TrustGraph API + * This provides a unified interface for various AI/ML and knowledge graph + * operations + */ +export interface Socket { + close: () => void; + + // Text completion using AI models + textCompletion: (system: string, text: string) => Promise; + + // Graph-based Retrieval Augmented Generation + graphRag: (text: string, options?: GraphRagOptions) => Promise; + + // Agent interaction with streaming callbacks for different phases + // BREAKING CHANGE: Callbacks now receive (chunk, complete, metadata?) instead of full messages + agent: ( + question: string, + think: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + observe: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + answer: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + error: (e: string) => void, + onExplain?: (event: ExplainEvent) => void, + ) => void; + + // Streaming variants for RAG and completion services + graphRagStreaming: ( + text: string, + receiver: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + onError: (error: string) => void, + options?: GraphRagOptions, + collection?: string, + ) => void; + + documentRagStreaming: ( + text: string, + receiver: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + onError: (error: string) => void, + docLimit?: number, + collection?: string, + onExplain?: (event: ExplainEvent) => void, + ) => void; + + textCompletionStreaming: ( + system: string, + text: string, + receiver: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + onError: (error: string) => void, + ) => void; + + promptStreaming: ( + id: string, + terms: Record, + receiver: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + onError: (error: string) => void, + ) => void; + + // Generate embeddings for texts (batch) + embeddings: (texts: string[]) => Promise; + + // Query graph using embedding vector + graphEmbeddingsQuery: (vec: number[], limit: number) => Promise; + + // Query knowledge graph triples (subject-predicate-object) + triplesQuery: ( + s?: Term, // Subject (optional) + p?: Term, // Predicate (optional) + o?: Term, // Object (optional) + limit?: number, + collection?: string, + graph?: string, // Named graph URI filter + ) => Promise; + + // Load a document into the system + loadDocument: ( + document: string, // Base64-encoded document + id?: string, // Optional document ID + metadata?: Triple[], // Optional metadata as triples + ) => Promise; + + // Load plain text into the system + loadText: (text: string, id?: string, metadata?: Triple[]) => Promise; + + // Load a document into the library with full metadata + loadLibraryDocument: ( + document: string, + mimeType: string, + id?: string, + metadata?: Triple[], + ) => Promise; +} + +/** + * Generates a random message ID using cryptographically secure random values + * @param length - Number of random characters to generate + * @returns Random string of specified length + */ +function makeid(length: number) { + const array = new Uint32Array(length); + crypto.getRandomValues(array); + + const characters = "abcdefghijklmnopqrstuvwxyz1234567890"; + + return array.reduce( + (acc, current) => acc + characters[current % characters.length], + "", + ); +} + +/** + * BaseApi - Core WebSocket client for TrustGraph API + * Manages connection lifecycle, message routing, and provides base request + * functionality + */ +// Connection state interface for UI consumption +export interface ConnectionState { + status: + | "connecting" + | "connected" + | "reconnecting" + | "failed" + | "authenticated" + | "unauthenticated"; + hasApiKey: boolean; + reconnectAttempt?: number; + maxAttempts?: number; + nextRetryIn?: number; + lastError?: string; +} + +export class BaseApi { + ws?: WebSocket; // WebSocket connection instance + tag: string; // Unique client identifier + id: number; // Counter for generating unique message IDs + token?: string; // Optional authentication token + user: string; // User identifier for API requests + socketUrl: string; // WebSocket URL + inflight: { [key: string]: ServiceCall } = {}; // Track active requests by + // message ID + reconnectAttempts: number = 0; // Track reconnection attempts + maxReconnectAttempts: number = 10; // Maximum reconnection attempts + reconnectTimer?: number; // Timer for reconnection attempts + reconnectionState: "idle" | "reconnecting" | "failed" = "idle"; // Connection state + + // Connection state tracking for UI + private connectionStateListeners: ((state: ConnectionState) => void)[] = []; + private lastError?: string; + + constructor(user: string, token?: string, socketUrl?: string) { + this.tag = makeid(16); // Generate unique client tag + this.id = 1; // Start message ID counter + this.token = token; // Store authentication token + this.user = user; // Store user identifier + this.socketUrl = socketUrl || SOCKET_URL; // Use provided URL or default + + console.log( + "SOCKET: opening socket...", + token ? "with auth" : "without auth", + "user:", + user, + ); + this.openSocket(); // Establish WebSocket connection + console.log("SOCKET: socket opened"); + } + + /** + * Subscribe to connection state changes for UI updates + */ + onConnectionStateChange(listener: (state: ConnectionState) => void) { + this.connectionStateListeners.push(listener); + // Immediately send current state + listener(this.getConnectionState()); + + // Return unsubscribe function + return () => { + const index = this.connectionStateListeners.indexOf(listener); + if (index > -1) { + this.connectionStateListeners.splice(index, 1); + } + }; + } + + /** + * Get current connection state + */ + private getConnectionState(): ConnectionState { + const hasApiKey = !!this.token; + + // Determine status based on WebSocket state and reconnection state + let status: ConnectionState["status"]; + + if (!this.ws || this.ws.readyState === WebSocket.CLOSED) { + if (this.reconnectionState === "failed") { + status = "failed"; + } else if (this.reconnectionState === "reconnecting") { + status = "reconnecting"; + } else { + status = "connecting"; + } + } else if (this.ws.readyState === WebSocket.CONNECTING) { + status = "connecting"; + } else if (this.ws.readyState === WebSocket.OPEN) { + status = hasApiKey ? "authenticated" : "unauthenticated"; + } else { + status = "connecting"; + } + + const state: ConnectionState = { + status, + hasApiKey, + lastError: this.lastError, + }; + + // Add reconnection details if applicable + if (status === "reconnecting") { + state.reconnectAttempt = this.reconnectAttempts; + state.maxAttempts = this.maxReconnectAttempts; + } + + return state; + } + + /** + * Notify all listeners of connection state changes + */ + private notifyStateChange() { + const state = this.getConnectionState(); + this.connectionStateListeners.forEach((listener) => { + try { + listener(state); + } catch (error) { + console.error("Error in connection state listener:", error); + } + }); + } + + /** + * Establishes WebSocket connection and sets up event handlers + */ + openSocket() { + // Don't create multiple connections + if ( + this.ws && + (this.ws.readyState === WebSocket.CONNECTING || + this.ws.readyState === WebSocket.OPEN) + ) { + return; + } + + // Clean up old socket if exists + if (this.ws) { + this.ws.removeEventListener("message", this.onMessage); + this.ws.removeEventListener("close", this.onClose); + this.ws.removeEventListener("open", this.onOpen); + this.ws.removeEventListener("error", this.onError); + this.ws = undefined; + } + + try { + // Build WebSocket URL with optional token parameter + const wsUrl = this.token + ? `${this.socketUrl}?token=${this.token}` + : this.socketUrl; + console.log( + "SOCKET: connecting to", + wsUrl.replace(/token=[^&]*/, "token=***"), + ); + this.ws = new WebSocket(wsUrl); + } catch (e) { + console.error("[socket creation error]", e); + this.scheduleReconnect(); + return; + } + + // Bind event handlers to maintain proper 'this' context + this.onMessage = this.onMessage.bind(this); + this.onClose = this.onClose.bind(this); + this.onOpen = this.onOpen.bind(this); + this.onError = this.onError.bind(this); + + // Attach event listeners + this.ws.addEventListener("message", this.onMessage); + this.ws.addEventListener("close", this.onClose); + this.ws.addEventListener("open", this.onOpen); + this.ws.addEventListener("error", this.onError); + } + + // Handle incoming messages from server + onMessage(message: MessageEvent) { + if (!message.data) return; + + try { + const obj = JSON.parse(message.data); + + // Skip messages without ID (can't route them) + if (!obj.id) return; + + // Route response to the corresponding inflight request + if (this.inflight[obj.id]) { + // Pass the whole message object so receiver can access 'complete' flag + this.inflight[obj.id].onReceived(obj); + } + } catch (e) { + console.error("[socket message parse error]", e); + } + } + + // Handle connection closure - automatically attempt reconnection + onClose(event: CloseEvent) { + console.log("[socket close]", event.code, event.reason); + this.lastError = `Connection closed: ${event.reason || "Unknown reason"}`; + this.ws = undefined; + this.notifyStateChange(); + this.scheduleReconnect(); + } + + // Handle successful connection + onOpen() { + console.log("[socket open]"); + this.reconnectAttempts = 0; // Reset reconnection attempts on success + this.reconnectionState = "idle"; // Reset connection state + this.lastError = undefined; // Clear any previous errors + + // Clear any pending reconnect timer + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = undefined; + } + + // Notify UI of successful connection + this.notifyStateChange(); + + // Immediately retry any pending requests that were waiting for connection + for (const mid in this.inflight) { + this.inflight[mid].retryNow(); + } + } + + // Handle socket errors + onError(event: Event) { + console.error("[socket error]", event); + this.lastError = "Connection error occurred"; + this.notifyStateChange(); + } + + /** + * Schedules a reconnection attempt with exponential backoff + */ + scheduleReconnect() { + // Prevent concurrent reconnection attempts + if (this.reconnectionState === "reconnecting") { + console.log("[socket] Reconnection already in progress, skipping"); + return; + } + + // Don't schedule if already scheduled + if (this.reconnectTimer) return; + + this.reconnectionState = "reconnecting"; + this.reconnectAttempts++; + this.notifyStateChange(); // Notify UI of reconnection attempt + + if (this.reconnectAttempts > this.maxReconnectAttempts) { + console.error("[socket] Max reconnection attempts reached"); + this.reconnectionState = "failed"; + this.lastError = "Max reconnection attempts exceeded"; + this.notifyStateChange(); + // Notify all pending requests of the failure + for (const mid in this.inflight) { + this.inflight[mid].error(new Error("WebSocket connection failed")); + } + return; + } + + // Calculate exponential backoff with jitter + const backoffDelay = Math.min( + SOCKET_RECONNECTION_TIMEOUT * Math.pow(2, this.reconnectAttempts - 1) + + Math.random() * 1000, + 30000, // Max 30 seconds + ); + + console.log( + `[socket] Reconnecting in ${backoffDelay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`, + ); + + this.reconnectTimer = setTimeout(() => { + this.reconnectTimer = undefined; + this.reopen(); + }, backoffDelay) as unknown as number; + } + + /** + * Reopens the WebSocket connection (used after connection failures) + */ + reopen() { + console.log("[socket reopen]"); + // Check if we're already connected or connecting + if ( + this.ws && + (this.ws.readyState === WebSocket.OPEN || + this.ws.readyState === WebSocket.CONNECTING) + ) { + return; + } + this.openSocket(); + } + + /** + * Closes the WebSocket connection and cleans up + */ + close() { + // Clear reconnection timer + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = undefined; + } + + // Clean up WebSocket + if (this.ws) { + // Remove event listeners to prevent memory leaks + this.ws.removeEventListener("message", this.onMessage); + this.ws.removeEventListener("close", this.onClose); + this.ws.removeEventListener("open", this.onOpen); + this.ws.removeEventListener("error", this.onError); + + this.ws.close(); + this.ws = undefined; + } + + // Clear any remaining inflight requests + for (const mid in this.inflight) { + this.inflight[mid].error(new Error("Socket closed")); + } + this.inflight = {}; + } + + /** + * Generates the next unique message ID for requests + * Format: {clientTag}-{incrementingNumber} + */ + getNextId() { + const mid = this.tag + "-" + this.id.toString(); + this.id++; + return mid; + } + + /** + * Core method for making service requests over WebSocket + * @param service - Name of the service to call + * @param request - Request payload + * @param timeout - Request timeout in milliseconds (default: 10000) + * @param retries - Number of retry attempts (default: 3) + * @param flow - Optional flow identifier + * @returns Promise resolving to the service response + */ + makeRequest( + service: string, + request: RequestType, + timeout?: number, + retries?: number, + flow?: string, + ) { + const mid = this.getNextId(); + + // Set default values + if (timeout == undefined) timeout = 10000; + if (retries == undefined) retries = 3; + + // Construct the request message + const msg: RequestMessage = { + id: mid, + service: service, + request: request, + }; + + // Add flow identifier if provided + if (flow) msg.flow = flow; + + // Return a Promise that will be resolved/rejected by the ServiceCall + return new Promise((resolve, reject) => { + const call = new ServiceCall( + mid, + msg, + resolve as (resp: unknown) => void, + reject as (err: object | string) => void, + timeout, + retries, + this, + ); + + call.start(); + // Commented out debug logging: console.log("-->", msg); + }).then((obj) => { + // Commented out success logging: console.log("Success for", mid); + return obj as ResponseType; + }); + } + + /** + * Makes a request that can receive multiple responses (streaming) + * Used for operations that return data in chunks + */ + makeRequestMulti( + service: string, + request: RequestType, + receiver: (resp: unknown) => boolean, // Callback to handle each response chunk + timeout?: number, + retries?: number, + flow?: string, + ) { + const mid = this.getNextId(); + + // Set defaults + if (timeout == undefined) timeout = 10000; + if (retries == undefined) retries = 3; + + // Construct request message + const msg: RequestMessage = { + id: mid, + service: service, + request: request, + }; + + if (flow) msg.flow = flow; + + return new Promise((resolve, reject) => { + const call = new ServiceCallMulti( + mid, + msg, + resolve as (resp: unknown) => void, + reject as (err: object | string) => void, + timeout, + retries, + this as any, // eslint-disable-line @typescript-eslint/no-explicit-any + receiver, + ); + + call.start(); + }).then((obj) => { + return obj as ResponseType; + }); + } + + /** + * Convenience method for making flow-specific requests + * Defaults to "default" flow if none specified + */ + makeFlowRequest( + service: string, + request: RequestType, + timeout?: number, + retries?: number, + flow?: string, + ) { + if (!flow) flow = "default"; + + return this.makeRequest( + service, + request, + timeout, + retries, + flow, + ); + } + + // Factory methods for creating specialized API instances + librarian() { + return new LibrarianApi(this); + } + + flows() { + return new FlowsApi(this); + } + + flow(id: string) { + return new FlowApi(this, id); + } + + knowledge() { + return new KnowledgeApi(this); + } + + config() { + return new ConfigApi(this); + } + + collectionManagement() { + return new CollectionManagementApi(this); + } +} + +/** + * LibrarianApi - Manages document storage and retrieval + * Handles document lifecycle including upload, processing, and removal + */ +export class LibrarianApi { + api: BaseApi; + + constructor(api: BaseApi) { + this.api = api; + } + + /** + * Retrieves list of all documents in the system + */ + getDocuments() { + return this.api + .makeRequest( + "librarian", + { + operation: "list-documents", + user: this.api.user, + }, + 60000, // 60 second timeout for potentially large lists + ) + .then((r) => r["document-metadatas"] || []); + } + + /** + * Retrieves list of documents currently being processed + */ + getProcessing() { + return this.api + .makeRequest( + "librarian", + { + operation: "list-processing", + user: this.api.user, + }, + 60000, + ) + .then((r) => r["processing-metadata"] || []); + } + + /** + * Retrieves metadata for a single document by ID + * @param documentId - Document URI/ID to fetch + * @returns Document metadata including title, comments, tags, and RDF metadata + */ + getDocumentMetadata(documentId: string): Promise { + return this.api + .makeRequest( + "librarian", + { + operation: "get-document-metadata", + "document-id": documentId, + user: this.api.user, + }, + 30000, + ) + .then((r) => r["document-metadata"] || null); + } + + /** + * Uploads a document to the library with full metadata + * @param document - Base64-encoded document content + * @param id - Optional document identifier + * @param metadata - Optional metadata as triples + * @param mimeType - Document MIME type + * @param title - Document title + * @param comments - Additional comments + * @param tags - Document tags for categorization + */ + loadDocument( + document: string, // base64-encoded doc + mimeType: string, + title: string, + comments: string, + tags: string[], + id?: string, + metadata?: Triple[], + ) { + return this.api.makeRequest( + "librarian", + { + operation: "add-document", + "document-metadata": { + id: id, + time: Math.floor(Date.now() / 1000), // Unix timestamp + kind: mimeType, + title: title, + comments: comments, + metadata: metadata, + user: this.api.user, + tags: tags, + }, + content: document, + }, + 30000, // 30 second timeout for document upload + ); + } + + /** + * Removes a document from the library + */ + removeDocument(id: string, collection?: string) { + return this.api.makeRequest( + "librarian", + { + operation: "remove-document", + "document-id": id, + user: this.api.user, + collection: collection || "default", + }, + 30000, + ); + } + + /** + * Adds a document to the processing queue + * @param id - Processing job identifier + * @param doc_id - Document to process + * @param flow - Processing flow to use + * @param collection - Collection to add processed data to + * @param tags - Tags for the processing job + */ + addProcessing( + id: string, + doc_id: string, + flow: string, + collection?: string, + tags?: string[], + ) { + return this.api.makeRequest( + "librarian", + { + operation: "add-processing", + "processing-metadata": { + id: id, + "document-id": doc_id, + time: Math.floor(Date.now() / 1000), + flow: flow, + user: this.api.user, + collection: collection ? collection : "default", + tags: tags ? tags : [], + }, + }, + 30000, + ); + } + + // ========== Chunked Upload API ========== + + /** + * Initialize a chunked upload session for large documents (>2MB) + * @param metadata - Document metadata including id, title, kind (MIME type), etc. + * @param totalSize - Total size of the document in bytes + * @param chunkSize - Optional chunk size (default: 5MB) + * @returns Upload session info including upload-id and total-chunks + */ + beginUpload( + metadata: ChunkedUploadDocumentMetadata, + totalSize: number, + chunkSize?: number, + ): Promise { + return this.api + .makeRequest( + "librarian", + { + operation: "begin-upload", + "document-metadata": metadata, + "total-size": totalSize, + "chunk-size": chunkSize, + }, + 30000, + ) + .then((r) => { + if (r.error) { + throw new Error(r.error.message); + } + return r; + }); + } + + /** + * Upload a single chunk of a document + * Chunks can be uploaded in any order and in parallel + * @param uploadId - Upload session ID from beginUpload + * @param chunkIndex - Zero-based chunk index + * @param content - Base64-encoded chunk content + * @returns Progress info including chunks-received and bytes-received + */ + uploadChunk( + uploadId: string, + chunkIndex: number, + content: string, + ): Promise { + return this.api + .makeRequest( + "librarian", + { + operation: "upload-chunk", + "upload-id": uploadId, + "chunk-index": chunkIndex, + content: content, + user: this.api.user, + }, + 60000, // Longer timeout for chunk uploads + ) + .then((r) => { + if (r.error) { + throw new Error(r.error.message); + } + return r; + }); + } + + /** + * Finalize a chunked upload after all chunks are received + * Triggers document processing + * @param uploadId - Upload session ID from beginUpload + * @returns Document ID and object ID + */ + completeUpload(uploadId: string): Promise { + return this.api + .makeRequest( + "librarian", + { + operation: "complete-upload", + "upload-id": uploadId, + user: this.api.user, + }, + 30000, + ) + .then((r) => { + if (r.error) { + throw new Error(r.error.message); + } + return r; + }); + } + + /** + * Check upload progress (useful for resuming interrupted uploads) + * @param uploadId - Upload session ID + * @returns Status including received/missing chunks + */ + getUploadStatus(uploadId: string): Promise { + return this.api + .makeRequest( + "librarian", + { + operation: "get-upload-status", + "upload-id": uploadId, + user: this.api.user, + }, + 30000, + ) + .then((r) => { + if (r.error) { + throw new Error(r.error.message); + } + return r; + }); + } + + /** + * Cancel an in-progress upload and clean up + * @param uploadId - Upload session ID to abort + */ + abortUpload(uploadId: string): Promise { + return this.api + .makeRequest( + "librarian", + { + operation: "abort-upload", + "upload-id": uploadId, + user: this.api.user, + }, + 30000, + ) + .then((r) => { + if (r.error) { + throw new Error(r.error.message); + } + }); + } + + /** + * List pending upload sessions for the current user + * @returns Array of upload sessions with metadata and progress + */ + listUploads(): Promise { + return this.api + .makeRequest( + "librarian", + { + operation: "list-uploads", + user: this.api.user, + }, + 30000, + ) + .then((r) => { + if (r.error) { + throw new Error(r.error.message); + } + return r["upload-sessions"] || []; + }); + } + + /** + * Stream a document in chunks for retrieval (streaming response) + * Sends one request, receives multiple chunk responses via callback + * @param documentId - Document ID to retrieve + * @param onChunk - Callback for each chunk: (content, chunkIndex, totalChunks, complete) => void + * @param onError - Callback for errors + * @param chunkSize - Optional chunk size (default: 1MB) + */ + streamDocument( + documentId: string, + onChunk: (content: string, chunkIndex: number, totalChunks: number, complete: boolean) => void, + onError: (error: string) => void, + chunkSize?: number, + ): void { + const receiver = (message: unknown): boolean => { + const msg = message as { response?: StreamDocumentResponse; complete?: boolean; error?: string }; + + // Check for top-level error + if (msg.error) { + onError(msg.error); + return true; + } + + const resp = msg.response; + if (!resp) { + return !!msg.complete; + } + + // Check for response-level error + if (resp.error) { + onError(resp.error.message); + return true; + } + + const complete = !!msg.complete; + onChunk(resp.content, resp["chunk-index"], resp["total-chunks"], complete); + + return complete; + }; + + this.api.makeRequestMulti( + "librarian", + { + operation: "stream-document", + "document-id": documentId, + "chunk-size": chunkSize, + user: this.api.user, + }, + receiver, + 300000, // 5 minute timeout for full document stream + ); + } +} + +/** + * FlowsApi - Manages processing flows and configuration + * Flows define how documents and data are processed through the system + */ +export class FlowsApi { + api: BaseApi; + + constructor(api: BaseApi) { + this.api = api; + } + + /** + * Retrieves list of available flows + */ + getFlows() { + return this.api + .makeRequest( + "flow", + { + operation: "list-flows", + }, + 60000, + ) + .then((r) => r["flow-ids"] || []); + } + + /** + * Retrieves definition of a specific flow + */ + getFlow(id: string) { + return this.api + .makeRequest( + "flow", + { + operation: "get-flow", + "flow-id": id, + }, + 60000, + ) + .then((r) => JSON.parse(r.flow || "{}")); // Parse JSON flow definition + } + + // Configuration management methods + + /** + * Retrieves all configuration settings + */ + getConfigAll() { + return this.api.makeRequest( + "config", + { + operation: "config", + }, + 60000, + ); + } + + /** + * Retrieves specific configuration values by key + */ + getConfig(keys: { type: string; key: string }[]) { + return this.api.makeRequest( + "config", + { + operation: "get", + keys: keys, + }, + 60000, + ); + } + + /** + * Updates configuration values + */ + putConfig(values: { type: string; key: string; value: string }[]) { + return this.api.makeRequest( + "config", + { + operation: "put", + values: values, + }, + 60000, + ); + } + + /** + * Deletes configuration entries + */ + deleteConfig(keys: { type: string; key: string }) { + return this.api.makeRequest( + "config", + { + operation: "delete", + keys: keys, + }, + 30000, + ); + } + + // Prompt management - specialized config operations for AI prompts + + /** + * Retrieves list of available prompt templates + */ + getPrompts() { + return this.getConfigAll().then((r) => { + const config = r as Record< + string, + Record> + >; + return JSON.parse(config.config.prompt["template-index"]); + }); + } + + /** + * Retrieves a specific prompt template + */ + getPrompt(id: string) { + return this.getConfigAll().then((r) => { + const config = r as Record< + string, + Record> + >; + return JSON.parse(config.config.prompt[`template.${id}`]); + }); + } + + /** + * Retrieves the system prompt configuration + */ + getSystemPrompt() { + return this.getConfigAll().then((r) => { + const config = r as Record< + string, + Record> + >; + return JSON.parse(config.config.prompt.system); + }); + } + + // Flow blueprint management - templates for creating flows + + /** + * Retrieves list of available flow blueprints (templates) + */ + getFlowBlueprints() { + return this.api + .makeRequest( + "flow", + { + operation: "list-blueprints", + }, + 60000, + ) + .then((r) => r["blueprint-names"]); + } + + /** + * Retrieves definition of a specific flow blueprint + */ + getFlowBlueprint(name: string) { + return this.api + .makeRequest( + "flow", + { + operation: "get-blueprint", + "blueprint-name": name, + }, + 60000, + ) + .then((r) => JSON.parse(r["blueprint-definition"] || "{}")); + } + + /** + * Deletes a flow blueprint + */ + deleteFlowBlueprint(name: string) { + return this.api.makeRequest( + "flow", + { + operation: "delete-blueprint", + "blueprint-name": name, + }, + 30000, + ); + } + + // Flow lifecycle management + + /** + * Starts a new flow instance + */ + startFlow( + id: string, + blueprint_name: string, + description: string, + parameters?: Record, + ) { + const request: FlowRequest = { + operation: "start-flow", + "flow-id": id, + "blueprint-name": blueprint_name, + description: description, + }; + + // Only include parameters if provided and not empty + if (parameters && Object.keys(parameters).length > 0) { + request.parameters = parameters; + } + + return this.api + .makeRequest("flow", request, 30000) + .then((response) => { + if (response.error) { + let errorMessage = "Flow start failed"; + if ( + typeof response.error === "object" && + response.error && + "message" in response.error + ) { + errorMessage = + (response.error as { message?: string }).message || errorMessage; + } else if (typeof response.error === "string") { + errorMessage = response.error; + } + throw new Error(errorMessage); + } + return response; + }); + } + + /** + * Stops a running flow instance + */ + stopFlow(id: string) { + return this.api.makeRequest( + "flow", + { + operation: "stop-flow", + "flow-id": id, + }, + 30000, + ); + } +} + +/** + * FlowApi - Interface for interacting with a specific flow instance + * Provides flow-specific versions of core AI/ML operations + */ +export class FlowApi { + api: BaseApi; + flowId: string; + + constructor(api: BaseApi, flowId: string) { + this.api = api; + this.flowId = flowId; // All requests will be routed through this flow + } + + /** + * Performs text completion using AI models within this flow + */ + textCompletion(system: string, text: string): Promise { + return this.api + .makeRequest( + "text-completion", + { + system: system, // System prompt/instructions + prompt: text, // User prompt + }, + 30000, + undefined, // Use default retries + this.flowId, // Route through this flow + ) + .then((r) => r.response); + } + + /** + * Performs Graph RAG (Retrieval Augmented Generation) query + */ + graphRag(text: string, options?: GraphRagOptions, collection?: string) { + return this.api + .makeRequest( + "graph-rag", + { + query: text, + user: this.api.user, + collection: collection || "default", + "entity-limit": options?.entityLimit, + "triple-limit": options?.tripleLimit, + "max-subgraph-size": options?.maxSubgraphSize, + "max-path-length": options?.pathLength, + }, + 60000, // Longer timeout for complex graph operations + undefined, + this.flowId, + ) + .then((r) => r.response); + } + + /** + * Performs Document RAG (Retrieval Augmented Generation) query + */ + documentRag(text: string, docLimit?: number, collection?: string) { + return this.api + .makeRequest( + "document-rag", + { + query: text, + user: this.api.user, + collection: collection || "default", + "doc-limit": docLimit || 20, + }, + 60000, // Longer timeout for document operations + undefined, + this.flowId, + ) + .then((r) => r.response); + } + + /** + * Interacts with an AI agent that provides streaming responses + * BREAKING CHANGE: Callbacks now receive (chunk, complete, metadata?) instead of full messages + */ + agent( + question: string, + think: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + observe: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + answer: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + error: (s: string) => void, + onExplain?: (event: ExplainEvent) => void, + ) { + const receiver = (message: unknown) => { + const msg = message as { response?: AgentResponse; complete?: boolean; error?: string }; + + // Check for top-level error + if (msg.error) { + error(msg.error); + return true; + } + + const resp = msg.response || {}; + + // Check for errors in response + if (resp.chunk_type === "error" || resp.error) { + error(resp.error?.message || "Unknown agent error"); + return true; // End streaming on error + } + + // Handle explainability events (agent uses chunk_type="explain") + if ((resp.chunk_type === "explain" || resp.message_type === "explain") && resp.explain_id && resp.explain_graph) { + onExplain?.({ + explainId: resp.explain_id, + explainGraph: resp.explain_graph, + }); + return false; + } + + // Handle streaming chunks by chunk_type + const content = resp.content || ""; + const messageComplete = !!resp.end_of_message; + const dialogComplete = !!msg.complete; + + // Extract metadata from final message + const metadata: StreamingMetadata | undefined = dialogComplete && (resp.in_token || resp.out_token || resp.model) + ? { in_token: resp.in_token, out_token: resp.out_token, model: resp.model } + : undefined; + + switch (resp.chunk_type) { + case "thought": + think(content, messageComplete, metadata); + break; + case "observation": + observe(content, messageComplete, metadata); + break; + case "answer": + case "final-answer": + answer(content, messageComplete, metadata); + break; + case "action": + // Actions are typically not streamed incrementally, just logged + console.log("Agent action:", content); + break; + } + + return dialogComplete; // End when backend signals complete + }; + + return this.api + .makeRequestMulti( + "agent", + { + question: question, + user: this.api.user, + streaming: true, // Always use streaming mode + }, + receiver, + 120000, + 2, + this.flowId, + ) + .catch((err) => { + const errorMessage = + err instanceof Error ? err.message : err?.toString() || "Unknown error"; + error(`Agent request failed: ${errorMessage}`); + }); + } + + /** + * Performs Graph RAG query with streaming response + * @param text - Query text + * @param receiver - Called for each chunk with (chunk, complete) where complete=true on final chunk + * @param onError - Called on error + * @param options - Graph RAG options (including explainable flag) + * @param collection - Collection name + * @param onExplain - Optional callback for explainability events + */ + graphRagStreaming( + text: string, + receiver: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + onError: (error: string) => void, + options?: GraphRagOptions, + collection?: string, + onExplain?: (event: ExplainEvent) => void, + ): void { + const recv = (message: unknown): boolean => { + const msg = message as { response?: GraphRagResponse; complete?: boolean; error?: string }; + + // Check for top-level error + if (msg.error) { + onError(msg.error); + return true; + } + + const resp = (msg.response || {}) as GraphRagResponse; + + // Check for response-level error + if (resp.error) { + onError(resp.error.message); + return true; + } + + // Handle explainability events + if (resp.message_type === "explain" && resp.explain_id && resp.explain_graph) { + onExplain?.({ + explainId: resp.explain_id, + explainGraph: resp.explain_graph, + }); + // Don't return true - more messages may follow + return false; + } + + // Handle chunk messages (default behavior) + const chunk = resp.response || resp.chunk || ""; + const complete = !!resp.end_of_session || !!msg.complete; + + // Extract metadata from final message + const metadata: StreamingMetadata | undefined = complete && (resp.in_token || resp.out_token || resp.model) + ? { in_token: resp.in_token, out_token: resp.out_token, model: resp.model } + : undefined; + + receiver(chunk, complete, metadata); + + return complete; + }; + + this.api.makeRequestMulti( + "graph-rag", + { + query: text, + user: this.api.user, + collection: collection || "default", + "entity-limit": options?.entityLimit, + "triple-limit": options?.tripleLimit, + "max-subgraph-size": options?.maxSubgraphSize, + "max-path-length": options?.pathLength, + streaming: true, + }, + recv, + 60000, + undefined, + this.flowId, + ); + } + + /** + * Performs Document RAG query with streaming response + * @param text - Query text + * @param receiver - Called for each chunk with (chunk, complete) where complete=true on final chunk + * @param onError - Called on error + * @param docLimit - Maximum documents to retrieve + * @param collection - Collection name + */ + documentRagStreaming( + text: string, + receiver: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + onError: (error: string) => void, + docLimit?: number, + collection?: string, + onExplain?: (event: ExplainEvent) => void, + ): void { + const recv = (message: unknown): boolean => { + const msg = message as { response?: DocumentRagResponse; complete?: boolean; error?: string }; + + // Check for top-level error + if (msg.error) { + onError(msg.error); + return true; + } + + const resp = (msg.response || {}) as DocumentRagResponse; + + // Check for response-level error + if (resp.error) { + onError(resp.error.message); + return true; + } + + // Handle explainability events + if (resp.message_type === "explain" && resp.explain_id && resp.explain_graph) { + onExplain?.({ + explainId: resp.explain_id, + explainGraph: resp.explain_graph, + }); + return false; + } + + const chunk = resp.response || resp.chunk || ""; + const complete = !!resp.end_of_session || !!msg.complete; + + // Extract metadata from final message + const metadata: StreamingMetadata | undefined = complete && (resp.in_token || resp.out_token || resp.model) + ? { in_token: resp.in_token, out_token: resp.out_token, model: resp.model } + : undefined; + + receiver(chunk, complete, metadata); + + return complete; + }; + + this.api.makeRequestMulti( + "document-rag", + { + query: text, + user: this.api.user, + collection: collection || "default", + "doc-limit": docLimit, + streaming: true, + }, + recv, + 60000, + undefined, + this.flowId, + ); + } + + /** + * Performs text completion with streaming response + * @param system - System prompt + * @param text - User prompt + * @param receiver - Called for each chunk with (chunk, complete) where complete=true on final chunk + * @param onError - Called on error + */ + textCompletionStreaming( + system: string, + text: string, + receiver: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + onError: (error: string) => void, + ): void { + const recv = (message: unknown): boolean => { + const msg = message as { response?: TextCompletionResponse; complete?: boolean; error?: string }; + + // Check for top-level error + if (msg.error) { + onError(msg.error); + return true; + } + + const resp = (msg.response || {}) as TextCompletionResponse; + + // Check for response-level error + if (resp.error) { + onError(resp.error.message); + return true; + } + + // Text completion uses 'response' field for chunks + const chunk = resp.response || ""; + const complete = !!msg.complete; + + // Extract metadata from final message + const metadata: StreamingMetadata | undefined = complete && (resp.in_token || resp.out_token || resp.model) + ? { in_token: resp.in_token, out_token: resp.out_token, model: resp.model } + : undefined; + + receiver(chunk, complete, metadata); + + return complete; + }; + + this.api.makeRequestMulti( + "text-completion", + { + system: system, + prompt: text, + streaming: true, + }, + recv, + 30000, + undefined, + this.flowId, + ); + } + + /** + * Executes a prompt template with streaming response + * @param id - Prompt template ID + * @param terms - Template variables + * @param receiver - Called for each chunk with (chunk, complete) where complete=true on final chunk + * @param onError - Called on error + */ + promptStreaming( + id: string, + terms: Record, + receiver: (chunk: string, complete: boolean, metadata?: StreamingMetadata) => void, + onError: (error: string) => void, + ): void { + const recv = (message: unknown): boolean => { + const msg = message as { response?: PromptResponse; complete?: boolean; error?: string }; + + // Check for top-level error + if (msg.error) { + onError(msg.error); + return true; + } + + const resp = (msg.response || {}) as PromptResponse; + + // Check for response-level error + if (resp.error) { + onError(resp.error.message); + return true; + } + + // Prompt service uses 'text' field for chunks + const chunk = resp.text || ""; + const complete = !!msg.complete; + + // Extract metadata from final message + const metadata: StreamingMetadata | undefined = complete && (resp.in_token || resp.out_token || resp.model) + ? { in_token: resp.in_token, out_token: resp.out_token, model: resp.model } + : undefined; + + receiver(chunk, complete, metadata); + + return complete; + }; + + this.api.makeRequestMulti( + "prompt", + { + id: id, + terms: terms, + streaming: true, + }, + recv, + 30000, + undefined, + this.flowId, + ); + } + + /** + * Generates embeddings for multiple texts within this flow. + * Returns vectors[text_index][dimension_index] - one vector per input text. + */ + embeddings(texts: string[]) { + return this.api + .makeRequest( + "embeddings", + { + texts: texts, + }, + 30000, + undefined, + this.flowId, + ) + .then((r) => r.vectors); + } + + /** + * Queries the knowledge graph using a single embedding vector + */ + graphEmbeddingsQuery( + vec: number[], + limit: number | undefined, + collection?: string, + ) { + return this.api + .makeRequest( + "graph-embeddings", + { + vector: vec, + limit: limit ? limit : 20, // Default to 20 results + user: this.api.user, + collection: collection || "default", + }, + 30000, + undefined, + this.flowId, + ) + .then((r) => r.entities); + } + + /** + * Queries knowledge graph triples (subject-predicate-object relationships) + * All parameters are optional - omitted parameters act as wildcards + */ + triplesQuery( + s?: Term, + p?: Term, + o?: Term, + limit?: number, + collection?: string, + graph?: string, + ) { + return this.api + .makeRequest( + "triples", + { + s: s, // Subject + p: p, // Predicate + o: o, // Object + g: graph, // Named graph URI filter + limit: limit ? limit : 20, + user: this.api.user, + collection: collection || "default", + }, + 30000, + undefined, + this.flowId, + ) + .then((r) => r.response); + } + + /** + * Loads a document into this flow for processing + */ + loadDocument( + document: string, // base64-encoded document + id?: string, + metadata?: Triple[], + ) { + return this.api.makeRequest( + "document-load", + { + id: id, + metadata: metadata, + data: document, + }, + 30000, + undefined, + this.flowId, + ); + } + + /** + * Loads plain text into this flow for processing + */ + loadText( + text: string, // Text content + id?: string, + metadata?: Triple[], + charset?: string, // Character encoding + ) { + return this.api.makeRequest( + "text-load", + { + id: id, + metadata: metadata, + text: text, + charset: charset, + }, + 30000, + undefined, + this.flowId, + ); + } + + /** + * Executes a GraphQL query against structured row data + */ + rowsQuery( + query: string, + collection?: string, + variables?: Record, + operationName?: string, + ) { + return this.api + .makeRequest( + "rows", + { + query: query, + user: this.api.user, + collection: collection || "default", + variables: variables, + operation_name: operationName, + }, + 30000, + undefined, + this.flowId, + ) + .then((r) => { + // Return the GraphQL response structure directly + const result: Record = {}; + if (r.data !== undefined) result.data = r.data; + if (r.errors) result.errors = r.errors; + if (r.extensions) result.extensions = r.extensions; + return result; + }); + } + + /** + * Converts a natural language question to a GraphQL query + */ + nlpQuery(question: string, maxResults?: number) { + return this.api + .makeRequest( + "nlp-query", + { + question: question, + max_results: maxResults || 100, + }, + 30000, + undefined, + this.flowId, + ) + .then((r) => r); + } + + /** + * Executes a natural language question against structured data + * Combines NLP query conversion and GraphQL execution + */ + structuredQuery(question: string, collection?: string) { + return this.api + .makeRequest( + "structured-query", + { + question: question, + user: this.api.user, + collection: collection || "default", + }, + 30000, + undefined, + this.flowId, + ) + .then((r) => { + // Return the response structure directly + const result: Record = {}; + if (r.data !== undefined) result.data = r.data; + if (r.errors) result.errors = r.errors; + return result; + }); + } + + /** + * Performs semantic search on structured data indexes using embedding vectors + * @param vectors - Embedding vectors to search for + * @param schemaName - Name of the schema to search + * @param collection - Optional collection name + * @param indexName - Optional index name to filter results + * @param limit - Maximum number of results to return (default: 10) + */ + rowEmbeddingsQuery( + vector: number[], + schemaName: string, + collection?: string, + indexName?: string, + limit?: number, + ): Promise { + const request: RowEmbeddingsQueryRequest = { + vector: vector, + schema_name: schemaName, + user: this.api.user, + collection: collection || "default", + limit: limit || 10, + }; + + if (indexName) { + request.index_name = indexName; + } + + return this.api + .makeRequest( + "row-embeddings", + request, + 30000, + undefined, + this.flowId, + ) + .then((r) => { + if (r.error) { + throw new Error(r.error.message); + } + return r.matches || []; + }); + } +} + +/** + * ConfigApi - Dedicated configuration management interface + * Handles system configuration, prompts, and token cost tracking + */ +export class ConfigApi { + api: BaseApi; + + constructor(api: BaseApi) { + this.api = api; + } + + /** + * Retrieves complete configuration + */ + getConfigAll() { + return this.api.makeRequest( + "config", + { + operation: "config", + }, + 60000, + ); + } + + /** + * Retrieves specific configuration entries + */ + getConfig(keys: { type: string; key: string }[]) { + return this.api.makeRequest( + "config", + { + operation: "get", + keys: keys, + }, + 60000, + ); + } + + /** + * Updates configuration values + */ + putConfig(values: { type: string; key: string; value: string }[]) { + return this.api.makeRequest( + "config", + { + operation: "put", + values: values, + }, + 60000, + ); + } + + /** + * Deletes configuration entries + */ + deleteConfig(keys: { type: string; key: string }) { + return this.api.makeRequest( + "config", + { + operation: "delete", + keys: keys, + }, + 30000, + ); + } + + // Specialized prompt management methods + + /** + * Retrieves available prompt templates + */ + getPrompts() { + return this.getConfigAll().then((r) => { + const config = r as Record< + string, + Record> + >; + return JSON.parse(config.config.prompt["template-index"]); + }); + } + + /** + * Retrieves a specific prompt template + */ + getPrompt(id: string) { + return this.getConfigAll().then((r) => { + const config = r as Record< + string, + Record> + >; + return JSON.parse(config.config.prompt[`template.${id}`]); + }); + } + + /** + * Retrieves system prompt configuration + */ + getSystemPrompt() { + return this.getConfigAll().then((r) => { + const config = r as Record< + string, + Record> + >; + return JSON.parse(config.config.prompt.system); + }); + } + + /** + * Lists available configuration types + */ + list(type: string) { + return this.api + .makeRequest( + "config", + { + operation: "list", + type: type, + }, + 60000, + ) + .then((r) => r); + } + + /** + * Retrieves all key/values for a specific type + */ + getValues(type: string) { + return this.api + .makeRequest( + "config", + { + operation: "getvalues", + type: type, + }, + 60000, + ) + .then((r) => (r as RowsQueryResponse).values); + } + + /** + * Retrieves token cost information for different AI models + * Useful for cost tracking and optimization + */ + getTokenCosts() { + return this.api + .makeRequest( + "config", + { + operation: "getvalues", + type: "token-cost", + }, + 60000, + ) + .then((r) => { + // Parse JSON values and restructure data + const response = r as RowsQueryResponse; + return (response.values || []).map((x: unknown) => { + const item = x as Record; + return { key: item.key, value: JSON.parse(item.value) }; + }); + }) + .then((r) => + // Transform to more usable format + r.map((x: unknown) => { + const item = x as Record; + const value = item.value as Record; + return { + model: item.key, + input_price: value.input_price, // Cost per input token + output_price: value.output_price, // Cost per output token + }; + }), + ); + } +} + +/** + * KnowledgeApi - Manages knowledge graph cores and data + * Knowledge cores appear to be collections of processed knowledge graph data + */ +export class KnowledgeApi { + api: BaseApi; + + constructor(api: BaseApi) { + this.api = api; + } + + /** + * Retrieves list of available knowledge graph cores + */ + getKnowledgeCores() { + return this.api + .makeRequest( + "knowledge", + { + operation: "list-kg-cores", + user: this.api.user, + }, + 60000, + ) + .then((r) => r.ids || []); + } + + /** + * Deletes a knowledge graph core + */ + deleteKgCore(id: string, collection?: string) { + return this.api.makeRequest( + "knowledge", + { + operation: "delete-kg-core", + id: id, + user: this.api.user, + collection: collection || "default", + }, + 30000, + ); + } + + /** + * Deletes a knowledge graph core + */ + loadKgCore(id: string, flow: string, collection?: string) { + return this.api.makeRequest( + "knowledge", + { + operation: "load-kg-core", + id: id, + flow: flow, + user: this.api.user, + collection: collection || "default", + }, + 30000, + ); + } + + /** + * Retrieves a knowledge graph core with streaming data + * Uses multi-request pattern for large datasets + * @param receiver - Callback function to handle streaming data chunks + */ + getKgCore( + id: string, + collection: string | undefined, + receiver: (msg: unknown, eos: boolean) => void, + ) { + // Wrapper to handle end-of-stream detection + const recv = (msg: unknown) => { + const response = msg as Record; + if (response.eos) { + // End of stream - notify receiver and signal completion + receiver(msg, true); + return true; + } else { + // Regular message - continue streaming + receiver(msg, false); + return false; + } + }; + + return this.api.makeRequestMulti( + "knowledge", + { + operation: "get-kg-core", + id: id, + user: this.api.user, + collection: collection || "default", + }, + recv, // Stream handler + 30000, + ); + } +} + +/** + * CollectionManagementApi - Manages collections for organizing documents + * Provides operations for listing, creating, updating, and deleting collections + */ +export class CollectionManagementApi { + api: BaseApi; + + constructor(api: BaseApi) { + this.api = api; + } + + /** + * Lists all collections for the current user with optional tag filtering + * @param tagFilter - Optional array of tags to filter collections + * @returns Promise resolving to array of collection metadata + */ + listCollections(tagFilter?: string[]) { + const request: Record = { + operation: "list-collections", + user: this.api.user, + }; + + if (tagFilter && tagFilter.length > 0) { + request.tag_filter = tagFilter; + } + + return this.api + .makeRequest< + Record, + Record + >("collection-management", request, 30000) + .then((r) => r.collections || []); + } + + /** + * Creates or updates a collection for the current user + * @param collection - Collection ID (unique identifier) + * @param name - Display name for the collection + * @param description - Description of the collection + * @param tags - Array of tags for categorization + * @returns Promise resolving to updated collection metadata + */ + updateCollection( + collection: string, + name?: string, + description?: string, + tags?: string[], + ) { + const request: Record = { + operation: "update-collection", + user: this.api.user, + collection, + }; + + if (name !== undefined) { + request.name = name; + } + if (description !== undefined) { + request.description = description; + } + if (tags !== undefined) { + request.tags = tags; + } + + return this.api + .makeRequest< + Record, + Record + >("collection-management", request, 30000) + .then((r) => { + if ( + r.collections && + Array.isArray(r.collections) && + r.collections.length > 0 + ) { + return r.collections[0]; + } + throw new Error("Failed to update collection"); + }); + } + + /** + * Deletes a collection and all its data for the current user + * @param collection - Collection ID to delete + * @returns Promise resolving when deletion is complete + */ + deleteCollection(collection: string) { + return this.api.makeRequest< + Record, + Record + >( + "collection-management", + { + operation: "delete-collection", + user: this.api.user, + collection, + }, + 30000, + ); + } +} + +/** + * Factory function to create a new TrustGraph WebSocket connection + * This is the main entry point for using the TrustGraph API + * @param user - User identifier for API requests + * @param token - Optional authentication token for secure connections + * @param socketUrl - Optional WebSocket URL (defaults to /api/socket for browser, provide full URL for Node.js) + */ +export const createTrustGraphSocket = ( + user: string, + token?: string, + socketUrl?: string, +): BaseApi => { + return new BaseApi(user, token, socketUrl); +}; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..19bcb6bf --- /dev/null +++ b/src/types.ts @@ -0,0 +1,3 @@ +// Type definitions for TrustGraph client + +export {}; diff --git a/test-graphrag.js b/test-graphrag.js new file mode 100755 index 00000000..308fef40 --- /dev/null +++ b/test-graphrag.js @@ -0,0 +1,94 @@ +#!/usr/bin/env node + +/** + * Standalone test for GraphRAG streaming + * Tests the question "What is a cat?" using GraphRAG streaming mode + */ + +import { createTrustGraphSocket } from './dist/index.esm.js'; + +// Configuration +const USER = 'trustgraph'; +const SOCKET_URL = 'ws://localhost:8088/api/v1/socket'; +const QUESTION = 'What is a cat?'; + +console.log('GraphRAG Streaming Test'); +console.log('======================'); +console.log(`User: ${USER}`); +console.log(`Socket URL: ${SOCKET_URL}`); +console.log(`Question: "${QUESTION}"\n`); + +// Create socket connection +const socket = createTrustGraphSocket(USER, undefined, SOCKET_URL); + +// Wait for connection to establish +setTimeout(() => { + console.log('Starting GraphRAG query...\n'); + + let accumulated = ''; + let chunkCount = 0; + + // GraphRAG options + const options = { + entityLimit: 50, + tripleLimit: 30, + maxSubgraphSize: 1000, + pathLength: 2, + }; + + // Streaming receiver callback + const onChunk = (chunk, complete, metadata) => { + chunkCount++; + accumulated += chunk; + + if (chunk) { + process.stdout.write(chunk); + } + + if (complete) { + console.log('\n\n--- Streaming Complete ---'); + console.log(`Total chunks received: ${chunkCount}`); + console.log(`Total characters: ${accumulated.length}`); + + if (metadata) { + console.log('\nMetadata:'); + if (metadata.model) console.log(` Model: ${metadata.model}`); + if (metadata.in_token) console.log(` Input tokens: ${metadata.in_token}`); + if (metadata.out_token) console.log(` Output tokens: ${metadata.out_token}`); + } + + console.log('\n--- Full Response ---'); + console.log(accumulated); + + // Close socket and exit + socket.close(); + process.exit(0); + } + }; + + // Error callback + const onError = (error) => { + console.error('\n\nERROR:', error); + socket.close(); + process.exit(1); + }; + + // Execute GraphRAG streaming query + socket + .flow('default') + .graphRagStreaming( + QUESTION, + onChunk, + onError, + options, + 'default' // collection + ); + +}, 1000); // Wait 1 second for connection + +// Handle process termination +process.on('SIGINT', () => { + console.log('\n\nInterrupted. Closing socket...'); + socket.close(); + process.exit(0); +}); diff --git a/test-streaming.js b/test-streaming.js new file mode 100755 index 00000000..5ae6800a --- /dev/null +++ b/test-streaming.js @@ -0,0 +1,111 @@ +#!/usr/bin/env node + +/** + * Test script for TrustGraph streaming APIs + * Tests both streaming and non-streaming text completion + * + * Usage: + * node test-streaming.js + * + * Requirements: + * - TrustGraph backend running on http://localhost:8088 + * - Built client library in ./dist/ + */ + +import { createTrustGraphSocket } from './dist/index.esm.js'; + +const USER = "test-user"; +const SYSTEM_PROMPT = "You are a helpful AI assistant."; +const TEST_PROMPT = "Explain what streaming is in one paragraph."; +const SOCKET_URL = "ws://localhost:8888/api/socket"; + +console.log("=".repeat(80)); +console.log("TrustGraph Streaming API Test"); +console.log("=".repeat(80)); +console.log(`Connecting to: ${SOCKET_URL}`); +console.log(`User: ${USER}`); +console.log("=".repeat(80)); + +// Create client connection with explicit WebSocket URL for Node.js +const client = createTrustGraphSocket(USER, undefined, SOCKET_URL); + +// Wait a bit for connection to establish +await new Promise(resolve => setTimeout(resolve, 1000)); + +console.log("\n[1/2] Testing NON-STREAMING text completion..."); +console.log("-".repeat(80)); + +try { + const flowApi = client.flow("default"); + const response = await flowApi.textCompletion(SYSTEM_PROMPT, TEST_PROMPT); + + console.log("✓ Non-streaming response received:"); + console.log(response); +} catch (error) { + console.error("✗ Non-streaming failed:", error.message); +} + +console.log("\n[2/2] Testing STREAMING text completion..."); +console.log("-".repeat(80)); + +try { + const flowApi = client.flow("default"); + + let accumulated = ""; + let chunkCount = 0; + const startTime = Date.now(); + + await new Promise((resolve, reject) => { + flowApi.textCompletionStreaming( + SYSTEM_PROMPT, + TEST_PROMPT, + (chunk, complete, metadata) => { + chunkCount++; + accumulated += chunk; + + // Show progress indicator + if (chunk) { + process.stdout.write(chunk); + } + + if (complete) { + const duration = Date.now() - startTime; + console.log("\n"); + console.log("-".repeat(80)); + console.log(`✓ Streaming complete!`); + console.log(` Chunks received: ${chunkCount}`); + console.log(` Total length: ${accumulated.length} chars`); + console.log(` Duration: ${duration}ms`); + console.log(` First chunk: ~${(startTime - Date.now() + duration) / chunkCount}ms`); + + // Display token usage and model info if available + if (metadata) { + console.log("\n Metadata:"); + if (metadata.model) console.log(` Model: ${metadata.model}`); + if (metadata.in_token !== undefined) console.log(` Input tokens: ${metadata.in_token}`); + if (metadata.out_token !== undefined) console.log(` Output tokens: ${metadata.out_token}`); + if (metadata.in_token && metadata.out_token) { + console.log(` Total tokens: ${metadata.in_token + metadata.out_token}`); + } + } + + resolve(); + } + }, + (error) => { + console.error("\n✗ Streaming error:", error); + reject(new Error(error)); + } + ); + }); +} catch (error) { + console.error("✗ Streaming failed:", error.message); +} + +console.log("\n" + "=".repeat(80)); +console.log("Test complete!"); +console.log("=".repeat(80)); + +// Close connection +client.close(); +process.exit(0); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..f69c5d78 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM"], + "jsx": "react", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..1d10f07b --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "happy-dom", + }, +}); From a8390532f7bbabfeb4c6061940978db764dc704d Mon Sep 17 00:00:00 2001 From: elpresidank Date: Sun, 5 Apr 2026 21:08:02 -0500 Subject: [PATCH 002/151] Squashed 'ai-context/workbench-ui/' content from commit 32e36a5c git-subtree-dir: ai-context/workbench-ui git-subtree-split: 32e36a5c2131e429a7081cfaf67dabad3193cda3 --- .github/workflows/pull-request.yaml | 28 + .github/workflows/release.yaml | 70 + .gitignore | 29 + .prettierignore | 7 + .prettierrc | 3 + CODEBOT-INSTRUCTIONS.md | 836 ++ Containerfile | 30 + FLOW-CLASS-NOTES.md | 132 + LICENSE | 203 + Makefile | 33 + README.criteria | 31 + README.md | 66 + TEST_STRATEGY.md | 590 + docs/tech-specs/collections.md | 734 ++ docs/tech-specs/flow-class-definition.md | 156 + docs/tech-specs/flow-class-editor.md | 910 ++ .../flow-configurable-parameters-client.md | 1048 ++ docs/tech-specs/gateway-auth.md | 373 + docs/tech-specs/llm-models-editor.md | 185 + docs/tech-specs/ontology.md | 1029 ++ docs/tech-specs/schema.md | 150 + docs/tech-specs/settings.md | 232 + docs/tech-specs/socket-reliability.md | 289 + eslint.config.js | 29 + index.html | 16 + package-lock.json | 9485 +++++++++++++++++ package.json | 67 + palette.svg | 8 + public/tg.svg | 83 + pulumi/Pulumi.dev.yaml | 14 + pulumi/Pulumi.prod.yaml | 14 + pulumi/Pulumi.yaml | 3 + pulumi/index.ts | 348 + pulumi/package-lock.json | 3625 +++++++ pulumi/package.json | 12 + pulumi/tsconfig.json | 18 + requirements.txt | 3 + src/App.scss | 16 + src/App.tsx | 202 + src/api/authenticated-fetch.ts | 41 + src/components/Layout.tsx | 20 + src/components/Sidebar.tsx | 162 + src/components/agents/Controls.tsx | 38 + src/components/agents/EditDialog.tsx | 340 + .../agents/EditableArgumentsTable.tsx | 243 + src/components/agents/Tools.tsx | 35 + src/components/agents/ToolsTable.tsx | 32 + src/components/chat/ChatConversation.tsx | 28 + src/components/chat/ChatHelp.tsx | 34 + src/components/chat/ChatHistory.tsx | 63 + src/components/chat/ChatMessage.tsx | 95 + src/components/chat/ChatModeSelector.tsx | 50 + src/components/chat/InputArea.tsx | 61 + .../chat/__tests__/ChatMessage.test.tsx | 353 + src/components/color-mode-toggle.tsx | 17 + src/components/common/AltCard.tsx | 39 + src/components/common/BasicTable.tsx | 40 + src/components/common/Card.tsx | 34 + src/components/common/CenterSpinner.tsx | 28 + src/components/common/ChipInputField.tsx | 118 + src/components/common/ClickableTable.tsx | 40 + src/components/common/ConfirmDialog.tsx | 139 + src/components/common/ConnectionStatus.tsx | 117 + src/components/common/EntityList.tsx | 39 + src/components/common/ExternalDocs.tsx | 17 + src/components/common/FlowSelector.tsx | 255 + src/components/common/NumberField.tsx | 45 + src/components/common/OptionWithImage.tsx | 42 + src/components/common/PageHeader.tsx | 64 + src/components/common/Progress.tsx | 41 + .../common/ProgressSubmitButton.tsx | 33 + src/components/common/RecommendedBadge.tsx | 11 + src/components/common/SelectField.tsx | 92 + src/components/common/SelectOption.tsx | 34 + src/components/common/SelectOptionText.tsx | 23 + src/components/common/SelectableTable.tsx | 40 + src/components/common/SimplePage.tsx | 45 + src/components/common/Slider.tsx | 45 + src/components/common/StatusBadge.tsx | 35 + src/components/common/TableStates.tsx | 36 + src/components/common/TableWithStates.tsx | 55 + src/components/common/TextAreaField.tsx | 40 + src/components/common/TextField.tsx | 44 + src/components/common/UnauthedHeader.tsx | 25 + src/components/common/UserDisplay.tsx | 19 + .../common/__tests__/BasicTable.test.tsx | 408 + src/components/common/__tests__/Card.test.tsx | 327 + .../__tests__/ProgressSubmitButton.test.tsx | 461 + .../common/__tests__/TextField.test.tsx | 329 + src/components/entity/ElementNode.tsx | 20 + src/components/entity/EntityDetail.tsx | 115 + src/components/entity/EntityHelp.tsx | 38 + src/components/entity/EntityNode.tsx | 28 + src/components/entity/LiteralNode.tsx | 11 + src/components/entity/SelectedNode.tsx | 15 + .../flow-classes/FlowClassActions.tsx | 71 + .../flow-classes/FlowClassControls.tsx | 32 + .../flow-classes/FlowClassEditPanel.tsx | 206 + .../flow-classes/FlowClassEditorView.tsx | 827 ++ .../flow-classes/FlowClassTable.tsx | 125 + .../flow-editor/FlowClassEditor.tsx | 58 + src/components/flow-editor/index.ts | 1 + src/components/flows/Actions.tsx | 36 + src/components/flows/CreateDialog.tsx | 261 + src/components/flows/FlowControls.tsx | 45 + src/components/flows/Flows.tsx | 49 + src/components/flows/ParameterDisplay.tsx | 128 + src/components/flows/ParameterInputs.tsx | 535 + .../flows/__tests__/CreateDialog.test.tsx | 195 + src/components/graph/Graph.tsx | 232 + src/components/graph/GraphHelp.tsx | 39 + src/components/graph/NodeDetailsDrawer.tsx | 116 + src/components/graph/NodePropertiesTable.tsx | 37 + src/components/graph/RelationshipsTable.tsx | 60 + src/components/graph/__tests__/Graph.test.tsx | 544 + .../__tests__/NodeDetailsDrawer.test.tsx | 542 + src/components/kc/Actions.tsx | 52 + src/components/kc/Controls.tsx | 29 + src/components/kc/IdField.tsx | 27 + src/components/kc/KnowledgeCoreUpload.tsx | 87 + src/components/kc/KnowledgeCores.tsx | 131 + src/components/kc/LoadDialog.tsx | 94 + src/components/kc/UploadDialog.tsx | 141 + src/components/library/Actions.tsx | 44 + src/components/library/CollectionActions.tsx | 46 + src/components/library/CollectionControls.tsx | 26 + src/components/library/CollectionDialog.tsx | 111 + src/components/library/Collections.tsx | 129 + src/components/library/DocumentControls.tsx | 22 + src/components/library/Documents.tsx | 140 + src/components/library/SubmitDialog.tsx | 119 + src/components/llm-models/LLMModels.tsx | 43 + src/components/llm-models/ModelsTable.tsx | 172 + .../llm-models/ParameterTypeSelector.tsx | 59 + src/components/load/Comments.tsx | 27 + src/components/load/Content.tsx | 21 + src/components/load/FileUpload.tsx | 63 + src/components/load/Keywords.tsx | 20 + src/components/load/LoadHelp.tsx | 48 + src/components/load/Operation.tsx | 46 + src/components/load/ProcessedFiles.tsx | 24 + src/components/load/SelectedFiles.tsx | 39 + src/components/load/TextBuffer.tsx | 26 + src/components/load/Title.tsx | 27 + src/components/load/UploadDialog.tsx | 127 + src/components/load/Url.tsx | 29 + src/components/mcp-tools/Controls.tsx | 38 + src/components/mcp-tools/EditDialog.tsx | 152 + src/components/mcp-tools/McpTools.tsx | 35 + src/components/mcp-tools/McpToolsTable.tsx | 31 + .../ontologies/ArrayFieldEditor.tsx | 162 + src/components/ontologies/ClassEditor.tsx | 620 ++ src/components/ontologies/ClassTree.tsx | 421 + src/components/ontologies/ConceptBasicTab.tsx | 49 + .../ontologies/ConceptDetailView.tsx | 61 + src/components/ontologies/ConceptEditor.tsx | 170 + .../ontologies/ConceptEditorHeader.tsx | 32 + .../ontologies/ConceptMetadataTab.tsx | 73 + .../ontologies/ConceptRelationshipsTab.tsx | 78 + .../ontologies/CreateOntologyDialog.tsx | 283 + .../ontologies/EditOntologyDialog.tsx | 303 + src/components/ontologies/ExportDialog.tsx | 225 + src/components/ontologies/ImportDialog.tsx | 519 + src/components/ontologies/MetadataEditor.tsx | 299 + .../ontologies/MultiLanguageLabels.tsx | 189 + .../ontologies/NamespacePrefixEditor.tsx | 222 + src/components/ontologies/Ontologies.tsx | 33 + src/components/ontologies/OntologiesTable.tsx | 214 + .../ontologies/OntologyConceptsTab.tsx | 87 + src/components/ontologies/OntologyEditor.tsx | 781 ++ .../ontologies/OntologyEmptyStates.tsx | 61 + src/components/ontologies/OntologyExporter.ts | 571 + .../ontologies/OntologyImporter.tsx | 672 ++ .../ontologies/OntologyJsonPreviewTab.tsx | 25 + src/components/ontologies/OntologyManager.tsx | 357 + .../ontologies/OntologyManagerHeader.tsx | 97 + .../ontologies/OntologyMetadataTab.tsx | 62 + .../ontologies/OntologySchemeTab.tsx | 32 + src/components/ontologies/OntologyTree.tsx | 92 + .../ontologies/OntologyTreeEmpty.tsx | 27 + .../ontologies/OntologyTreeHeader.tsx | 29 + .../ontologies/OntologyTreeNode.tsx | 193 + .../ontologies/OntologyTreeSearch.tsx | 22 + .../ontologies/OntologyValidationTab.tsx | 525 + .../ontologies/OntologyValidator.ts | 482 + src/components/ontologies/PropertyEditor.tsx | 604 ++ src/components/ontologies/PropertyTree.tsx | 288 + src/components/ontologies/SKOSDialog.tsx | 414 + src/components/ontologies/ValidationPanel.tsx | 215 + .../ontologies/ValidationResults.tsx | 97 + src/components/ontologies/WelcomePanel.tsx | 211 + .../ontologies/XSDDatatypeSelector.tsx | 210 + .../__tests__/ConceptEditor.test.tsx | 719 ++ .../__tests__/OntologyManager.test.tsx | 751 ++ .../__tests__/OntologyValidationTab.test.tsx | 445 + .../ontologies/__tests__/SKOSDialog.test.tsx | 321 + src/components/processing/Processing.tsx | 25 + src/components/prompts/EditDialog.tsx | 188 + src/components/prompts/EditSystemPrompt.tsx | 67 + src/components/prompts/PromptControls.tsx | 38 + src/components/prompts/Prompts.tsx | 69 + src/components/prompts/PromptsTable.tsx | 38 + src/components/prompts/SystemPrompt.tsx | 17 + src/components/schemas/EditSchemaDialog.tsx | 219 + src/components/schemas/EnumValueManager.tsx | 91 + src/components/schemas/SchemaBasicInfo.tsx | 57 + src/components/schemas/SchemaControls.tsx | 25 + src/components/schemas/SchemaFieldEditor.tsx | 125 + src/components/schemas/SchemaFieldsList.tsx | 55 + .../schemas/SchemaIndexesSection.tsx | 99 + src/components/schemas/SchemaTableStates.tsx | 2 + .../schemas/SchemaValidationErrors.tsx | 31 + src/components/schemas/Schemas.tsx | 15 + src/components/schemas/SchemasTable.tsx | 55 + .../__tests__/EnumValueManager.test.tsx | 424 + .../__tests__/SchemaFieldEditor.test.tsx | 496 + .../schemas/__tests__/useSchemaForm.test.ts | 588 + src/components/schemas/useSchemaForm.ts | 178 + src/components/search/Results.tsx | 107 + src/components/search/Search.tsx | 91 + src/components/search/SearchHelp.tsx | 46 + src/components/search/SearchInput.tsx | 69 + .../settings/AuthenticationSection.tsx | 103 + .../settings/FeatureSwitchesSection.tsx | 231 + src/components/settings/GraphRagSection.tsx | 157 + src/components/settings/Settings.tsx | 174 + src/components/settings/UserSection.tsx | 87 + .../structured-query/GenerateGraphQLTab.tsx | 124 + .../structured-query/RunGraphQLTab.tsx | 228 + .../structured-query/StructuredQueryTab.tsx | 213 + src/components/token-cost/Controls.tsx | 39 + src/components/token-cost/EditDialog.tsx | 144 + src/components/token-cost/TokenCosts.tsx | 44 + src/components/ui/ToasterComponent.tsx | 37 + src/components/ui/color-mode.tsx | 108 + src/components/ui/graph-colors.tsx | 52 + src/components/ui/toaster.ts | 6 + src/data/service-map.json | 304 + src/defs/default-prompts.ts | 23 + src/global.d.ts | 1 + src/index.css | 3 + src/index.tsx | 82 + src/model/agent-tools-table.tsx | 45 + src/model/collection-table.tsx | 101 + src/model/document-table.tsx | 108 + src/model/flow-class-table.tsx | 160 + src/model/flow-table.tsx | 103 + src/model/knowledge-core-table.tsx | 68 + src/model/mcp-tools-table.tsx | 38 + src/model/node-properties-table.tsx | 32 + src/model/node-relationships-table.tsx | 61 + src/model/ontologies-table.tsx | 47 + src/model/processing-table.tsx | 59 + src/model/prompts-table.tsx | 49 + src/model/schemaTypes.ts | 47 + src/model/schemas-table.tsx | 84 + src/model/token-costs-table.tsx | 38 + src/pages/ChatPage.tsx | 21 + src/pages/EntityPage.tsx | 19 + src/pages/FlowClassesPage.tsx | 48 + src/pages/FlowsPage.tsx | 20 + src/pages/GraphPage.tsx | 19 + src/pages/KnowledgeCoresPage.tsx | 20 + src/pages/LLMModelsPage.tsx | 19 + src/pages/LibraryPage.tsx | 33 + src/pages/LoadPage.tsx | 154 + src/pages/McpToolsPage.tsx | 20 + src/pages/OntologiesPage.tsx | 19 + src/pages/ProcessingPage.tsx | 20 + src/pages/PromptsPage.tsx | 19 + src/pages/SchemasPage.tsx | 18 + src/pages/SearchPage.tsx | 21 + src/pages/SettingsPage.tsx | 19 + src/pages/StructuredQueryPage.tsx | 52 + src/pages/TokenCostPage.tsx | 20 + src/pages/ToolsPage.tsx | 20 + src/providers/ConnectionLoadingScreen.tsx | 22 + src/providers/SettingsLoadingBoundary.tsx | 39 + src/test/setup.ts | 90 + src/test/test-utils.tsx | 59 + src/theme.tsx | 458 + src/utils/__tests__/document-encoding.test.ts | 126 + src/utils/__tests__/row.test.ts | 173 + src/utils/__tests__/skos.test.ts | 333 + src/utils/__tests__/time-string.test.ts | 40 + src/utils/document-encoding.ts | 49 + src/utils/document-load.ts | 0 src/utils/export-formats.ts | 374 + src/utils/knowledge-graph-viz.ts | 167 + src/utils/knowledge-graph.ts | 333 + src/utils/ontology-qa.ts | 480 + src/utils/row.ts | 192 + src/utils/schema-validation.ts | 75 + src/utils/skos-validation.ts | 458 + src/utils/skos.ts | 613 ++ src/utils/time-string.ts | 4 + src/utils/vector-search.ts | 56 + src/vite-env.d.ts | 1 + tsconfig.app.json | 24 + tsconfig.json | 7 + tsconfig.node.json | 22 + vite.config.ts | 36 + vitest.config.ts | 17 + workbench-ui/README.md | 0 workbench-ui/scripts/service | 6 + workbench-ui/setup.py | 47 + workbench-ui/workbench/__init__.py | 4 + workbench-ui/workbench/__main__.py | 7 + workbench-ui/workbench/api.py | 246 + workbench-ui/workbench/service.py | 63 + 310 files changed, 56430 insertions(+) create mode 100644 .github/workflows/pull-request.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 CODEBOT-INSTRUCTIONS.md create mode 100644 Containerfile create mode 100644 FLOW-CLASS-NOTES.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.criteria create mode 100644 README.md create mode 100644 TEST_STRATEGY.md create mode 100644 docs/tech-specs/collections.md create mode 100644 docs/tech-specs/flow-class-definition.md create mode 100644 docs/tech-specs/flow-class-editor.md create mode 100644 docs/tech-specs/flow-configurable-parameters-client.md create mode 100644 docs/tech-specs/gateway-auth.md create mode 100644 docs/tech-specs/llm-models-editor.md create mode 100644 docs/tech-specs/ontology.md create mode 100644 docs/tech-specs/schema.md create mode 100644 docs/tech-specs/settings.md create mode 100644 docs/tech-specs/socket-reliability.md create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 palette.svg create mode 100644 public/tg.svg create mode 100644 pulumi/Pulumi.dev.yaml create mode 100644 pulumi/Pulumi.prod.yaml create mode 100644 pulumi/Pulumi.yaml create mode 100644 pulumi/index.ts create mode 100644 pulumi/package-lock.json create mode 100644 pulumi/package.json create mode 100644 pulumi/tsconfig.json create mode 100644 requirements.txt create mode 100644 src/App.scss create mode 100644 src/App.tsx create mode 100644 src/api/authenticated-fetch.ts create mode 100644 src/components/Layout.tsx create mode 100644 src/components/Sidebar.tsx create mode 100644 src/components/agents/Controls.tsx create mode 100644 src/components/agents/EditDialog.tsx create mode 100644 src/components/agents/EditableArgumentsTable.tsx create mode 100644 src/components/agents/Tools.tsx create mode 100644 src/components/agents/ToolsTable.tsx create mode 100644 src/components/chat/ChatConversation.tsx create mode 100644 src/components/chat/ChatHelp.tsx create mode 100644 src/components/chat/ChatHistory.tsx create mode 100644 src/components/chat/ChatMessage.tsx create mode 100644 src/components/chat/ChatModeSelector.tsx create mode 100644 src/components/chat/InputArea.tsx create mode 100644 src/components/chat/__tests__/ChatMessage.test.tsx create mode 100644 src/components/color-mode-toggle.tsx create mode 100644 src/components/common/AltCard.tsx create mode 100644 src/components/common/BasicTable.tsx create mode 100644 src/components/common/Card.tsx create mode 100644 src/components/common/CenterSpinner.tsx create mode 100644 src/components/common/ChipInputField.tsx create mode 100644 src/components/common/ClickableTable.tsx create mode 100644 src/components/common/ConfirmDialog.tsx create mode 100644 src/components/common/ConnectionStatus.tsx create mode 100644 src/components/common/EntityList.tsx create mode 100644 src/components/common/ExternalDocs.tsx create mode 100644 src/components/common/FlowSelector.tsx create mode 100644 src/components/common/NumberField.tsx create mode 100644 src/components/common/OptionWithImage.tsx create mode 100644 src/components/common/PageHeader.tsx create mode 100644 src/components/common/Progress.tsx create mode 100644 src/components/common/ProgressSubmitButton.tsx create mode 100644 src/components/common/RecommendedBadge.tsx create mode 100644 src/components/common/SelectField.tsx create mode 100644 src/components/common/SelectOption.tsx create mode 100644 src/components/common/SelectOptionText.tsx create mode 100644 src/components/common/SelectableTable.tsx create mode 100644 src/components/common/SimplePage.tsx create mode 100644 src/components/common/Slider.tsx create mode 100644 src/components/common/StatusBadge.tsx create mode 100644 src/components/common/TableStates.tsx create mode 100644 src/components/common/TableWithStates.tsx create mode 100644 src/components/common/TextAreaField.tsx create mode 100644 src/components/common/TextField.tsx create mode 100644 src/components/common/UnauthedHeader.tsx create mode 100644 src/components/common/UserDisplay.tsx create mode 100644 src/components/common/__tests__/BasicTable.test.tsx create mode 100644 src/components/common/__tests__/Card.test.tsx create mode 100644 src/components/common/__tests__/ProgressSubmitButton.test.tsx create mode 100644 src/components/common/__tests__/TextField.test.tsx create mode 100644 src/components/entity/ElementNode.tsx create mode 100644 src/components/entity/EntityDetail.tsx create mode 100644 src/components/entity/EntityHelp.tsx create mode 100644 src/components/entity/EntityNode.tsx create mode 100644 src/components/entity/LiteralNode.tsx create mode 100644 src/components/entity/SelectedNode.tsx create mode 100644 src/components/flow-classes/FlowClassActions.tsx create mode 100644 src/components/flow-classes/FlowClassControls.tsx create mode 100644 src/components/flow-classes/FlowClassEditPanel.tsx create mode 100644 src/components/flow-classes/FlowClassEditorView.tsx create mode 100644 src/components/flow-classes/FlowClassTable.tsx create mode 100644 src/components/flow-editor/FlowClassEditor.tsx create mode 100644 src/components/flow-editor/index.ts create mode 100644 src/components/flows/Actions.tsx create mode 100644 src/components/flows/CreateDialog.tsx create mode 100644 src/components/flows/FlowControls.tsx create mode 100644 src/components/flows/Flows.tsx create mode 100644 src/components/flows/ParameterDisplay.tsx create mode 100644 src/components/flows/ParameterInputs.tsx create mode 100644 src/components/flows/__tests__/CreateDialog.test.tsx create mode 100644 src/components/graph/Graph.tsx create mode 100644 src/components/graph/GraphHelp.tsx create mode 100644 src/components/graph/NodeDetailsDrawer.tsx create mode 100644 src/components/graph/NodePropertiesTable.tsx create mode 100644 src/components/graph/RelationshipsTable.tsx create mode 100644 src/components/graph/__tests__/Graph.test.tsx create mode 100644 src/components/graph/__tests__/NodeDetailsDrawer.test.tsx create mode 100644 src/components/kc/Actions.tsx create mode 100644 src/components/kc/Controls.tsx create mode 100644 src/components/kc/IdField.tsx create mode 100644 src/components/kc/KnowledgeCoreUpload.tsx create mode 100644 src/components/kc/KnowledgeCores.tsx create mode 100644 src/components/kc/LoadDialog.tsx create mode 100644 src/components/kc/UploadDialog.tsx create mode 100644 src/components/library/Actions.tsx create mode 100644 src/components/library/CollectionActions.tsx create mode 100644 src/components/library/CollectionControls.tsx create mode 100644 src/components/library/CollectionDialog.tsx create mode 100644 src/components/library/Collections.tsx create mode 100644 src/components/library/DocumentControls.tsx create mode 100644 src/components/library/Documents.tsx create mode 100644 src/components/library/SubmitDialog.tsx create mode 100644 src/components/llm-models/LLMModels.tsx create mode 100644 src/components/llm-models/ModelsTable.tsx create mode 100644 src/components/llm-models/ParameterTypeSelector.tsx create mode 100644 src/components/load/Comments.tsx create mode 100644 src/components/load/Content.tsx create mode 100644 src/components/load/FileUpload.tsx create mode 100644 src/components/load/Keywords.tsx create mode 100644 src/components/load/LoadHelp.tsx create mode 100644 src/components/load/Operation.tsx create mode 100644 src/components/load/ProcessedFiles.tsx create mode 100644 src/components/load/SelectedFiles.tsx create mode 100644 src/components/load/TextBuffer.tsx create mode 100644 src/components/load/Title.tsx create mode 100644 src/components/load/UploadDialog.tsx create mode 100644 src/components/load/Url.tsx create mode 100644 src/components/mcp-tools/Controls.tsx create mode 100644 src/components/mcp-tools/EditDialog.tsx create mode 100644 src/components/mcp-tools/McpTools.tsx create mode 100644 src/components/mcp-tools/McpToolsTable.tsx create mode 100644 src/components/ontologies/ArrayFieldEditor.tsx create mode 100644 src/components/ontologies/ClassEditor.tsx create mode 100644 src/components/ontologies/ClassTree.tsx create mode 100644 src/components/ontologies/ConceptBasicTab.tsx create mode 100644 src/components/ontologies/ConceptDetailView.tsx create mode 100644 src/components/ontologies/ConceptEditor.tsx create mode 100644 src/components/ontologies/ConceptEditorHeader.tsx create mode 100644 src/components/ontologies/ConceptMetadataTab.tsx create mode 100644 src/components/ontologies/ConceptRelationshipsTab.tsx create mode 100644 src/components/ontologies/CreateOntologyDialog.tsx create mode 100644 src/components/ontologies/EditOntologyDialog.tsx create mode 100644 src/components/ontologies/ExportDialog.tsx create mode 100644 src/components/ontologies/ImportDialog.tsx create mode 100644 src/components/ontologies/MetadataEditor.tsx create mode 100644 src/components/ontologies/MultiLanguageLabels.tsx create mode 100644 src/components/ontologies/NamespacePrefixEditor.tsx create mode 100644 src/components/ontologies/Ontologies.tsx create mode 100644 src/components/ontologies/OntologiesTable.tsx create mode 100644 src/components/ontologies/OntologyConceptsTab.tsx create mode 100644 src/components/ontologies/OntologyEditor.tsx create mode 100644 src/components/ontologies/OntologyEmptyStates.tsx create mode 100644 src/components/ontologies/OntologyExporter.ts create mode 100644 src/components/ontologies/OntologyImporter.tsx create mode 100644 src/components/ontologies/OntologyJsonPreviewTab.tsx create mode 100644 src/components/ontologies/OntologyManager.tsx create mode 100644 src/components/ontologies/OntologyManagerHeader.tsx create mode 100644 src/components/ontologies/OntologyMetadataTab.tsx create mode 100644 src/components/ontologies/OntologySchemeTab.tsx create mode 100644 src/components/ontologies/OntologyTree.tsx create mode 100644 src/components/ontologies/OntologyTreeEmpty.tsx create mode 100644 src/components/ontologies/OntologyTreeHeader.tsx create mode 100644 src/components/ontologies/OntologyTreeNode.tsx create mode 100644 src/components/ontologies/OntologyTreeSearch.tsx create mode 100644 src/components/ontologies/OntologyValidationTab.tsx create mode 100644 src/components/ontologies/OntologyValidator.ts create mode 100644 src/components/ontologies/PropertyEditor.tsx create mode 100644 src/components/ontologies/PropertyTree.tsx create mode 100644 src/components/ontologies/SKOSDialog.tsx create mode 100644 src/components/ontologies/ValidationPanel.tsx create mode 100644 src/components/ontologies/ValidationResults.tsx create mode 100644 src/components/ontologies/WelcomePanel.tsx create mode 100644 src/components/ontologies/XSDDatatypeSelector.tsx create mode 100644 src/components/ontologies/__tests__/ConceptEditor.test.tsx create mode 100644 src/components/ontologies/__tests__/OntologyManager.test.tsx create mode 100644 src/components/ontologies/__tests__/OntologyValidationTab.test.tsx create mode 100644 src/components/ontologies/__tests__/SKOSDialog.test.tsx create mode 100644 src/components/processing/Processing.tsx create mode 100644 src/components/prompts/EditDialog.tsx create mode 100644 src/components/prompts/EditSystemPrompt.tsx create mode 100644 src/components/prompts/PromptControls.tsx create mode 100644 src/components/prompts/Prompts.tsx create mode 100644 src/components/prompts/PromptsTable.tsx create mode 100644 src/components/prompts/SystemPrompt.tsx create mode 100644 src/components/schemas/EditSchemaDialog.tsx create mode 100644 src/components/schemas/EnumValueManager.tsx create mode 100644 src/components/schemas/SchemaBasicInfo.tsx create mode 100644 src/components/schemas/SchemaControls.tsx create mode 100644 src/components/schemas/SchemaFieldEditor.tsx create mode 100644 src/components/schemas/SchemaFieldsList.tsx create mode 100644 src/components/schemas/SchemaIndexesSection.tsx create mode 100644 src/components/schemas/SchemaTableStates.tsx create mode 100644 src/components/schemas/SchemaValidationErrors.tsx create mode 100644 src/components/schemas/Schemas.tsx create mode 100644 src/components/schemas/SchemasTable.tsx create mode 100644 src/components/schemas/__tests__/EnumValueManager.test.tsx create mode 100644 src/components/schemas/__tests__/SchemaFieldEditor.test.tsx create mode 100644 src/components/schemas/__tests__/useSchemaForm.test.ts create mode 100644 src/components/schemas/useSchemaForm.ts create mode 100644 src/components/search/Results.tsx create mode 100644 src/components/search/Search.tsx create mode 100644 src/components/search/SearchHelp.tsx create mode 100644 src/components/search/SearchInput.tsx create mode 100644 src/components/settings/AuthenticationSection.tsx create mode 100644 src/components/settings/FeatureSwitchesSection.tsx create mode 100644 src/components/settings/GraphRagSection.tsx create mode 100644 src/components/settings/Settings.tsx create mode 100644 src/components/settings/UserSection.tsx create mode 100644 src/components/structured-query/GenerateGraphQLTab.tsx create mode 100644 src/components/structured-query/RunGraphQLTab.tsx create mode 100644 src/components/structured-query/StructuredQueryTab.tsx create mode 100644 src/components/token-cost/Controls.tsx create mode 100644 src/components/token-cost/EditDialog.tsx create mode 100644 src/components/token-cost/TokenCosts.tsx create mode 100644 src/components/ui/ToasterComponent.tsx create mode 100644 src/components/ui/color-mode.tsx create mode 100644 src/components/ui/graph-colors.tsx create mode 100644 src/components/ui/toaster.ts create mode 100644 src/data/service-map.json create mode 100644 src/defs/default-prompts.ts create mode 100644 src/global.d.ts create mode 100644 src/index.css create mode 100644 src/index.tsx create mode 100644 src/model/agent-tools-table.tsx create mode 100644 src/model/collection-table.tsx create mode 100644 src/model/document-table.tsx create mode 100644 src/model/flow-class-table.tsx create mode 100644 src/model/flow-table.tsx create mode 100644 src/model/knowledge-core-table.tsx create mode 100644 src/model/mcp-tools-table.tsx create mode 100644 src/model/node-properties-table.tsx create mode 100644 src/model/node-relationships-table.tsx create mode 100644 src/model/ontologies-table.tsx create mode 100644 src/model/processing-table.tsx create mode 100644 src/model/prompts-table.tsx create mode 100644 src/model/schemaTypes.ts create mode 100644 src/model/schemas-table.tsx create mode 100644 src/model/token-costs-table.tsx create mode 100644 src/pages/ChatPage.tsx create mode 100644 src/pages/EntityPage.tsx create mode 100644 src/pages/FlowClassesPage.tsx create mode 100644 src/pages/FlowsPage.tsx create mode 100644 src/pages/GraphPage.tsx create mode 100644 src/pages/KnowledgeCoresPage.tsx create mode 100644 src/pages/LLMModelsPage.tsx create mode 100644 src/pages/LibraryPage.tsx create mode 100644 src/pages/LoadPage.tsx create mode 100644 src/pages/McpToolsPage.tsx create mode 100644 src/pages/OntologiesPage.tsx create mode 100644 src/pages/ProcessingPage.tsx create mode 100644 src/pages/PromptsPage.tsx create mode 100644 src/pages/SchemasPage.tsx create mode 100644 src/pages/SearchPage.tsx create mode 100644 src/pages/SettingsPage.tsx create mode 100644 src/pages/StructuredQueryPage.tsx create mode 100644 src/pages/TokenCostPage.tsx create mode 100644 src/pages/ToolsPage.tsx create mode 100644 src/providers/ConnectionLoadingScreen.tsx create mode 100644 src/providers/SettingsLoadingBoundary.tsx create mode 100644 src/test/setup.ts create mode 100644 src/test/test-utils.tsx create mode 100644 src/theme.tsx create mode 100644 src/utils/__tests__/document-encoding.test.ts create mode 100644 src/utils/__tests__/row.test.ts create mode 100644 src/utils/__tests__/skos.test.ts create mode 100644 src/utils/__tests__/time-string.test.ts create mode 100644 src/utils/document-encoding.ts create mode 100644 src/utils/document-load.ts create mode 100644 src/utils/export-formats.ts create mode 100644 src/utils/knowledge-graph-viz.ts create mode 100644 src/utils/knowledge-graph.ts create mode 100644 src/utils/ontology-qa.ts create mode 100644 src/utils/row.ts create mode 100644 src/utils/schema-validation.ts create mode 100644 src/utils/skos-validation.ts create mode 100644 src/utils/skos.ts create mode 100644 src/utils/time-string.ts create mode 100644 src/utils/vector-search.ts create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts create mode 100644 vitest.config.ts create mode 100644 workbench-ui/README.md create mode 100755 workbench-ui/scripts/service create mode 100644 workbench-ui/setup.py create mode 100644 workbench-ui/workbench/__init__.py create mode 100755 workbench-ui/workbench/__main__.py create mode 100644 workbench-ui/workbench/api.py create mode 100644 workbench-ui/workbench/service.py diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml new file mode 100644 index 00000000..b281c874 --- /dev/null +++ b/.github/workflows/pull-request.yaml @@ -0,0 +1,28 @@ + +name: Test pull request + +on: + pull_request: + +permissions: + contents: read + +jobs: + + container-push: + + name: Build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Typescript support + run: npm install + + - name: Run tests + run: npm test -- --run + + - name: Build + run: make service-package VERSION=0.0.0 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..d9cf463b --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,70 @@ + +name: Build + +on: + workflow_dispatch: + push: + tags: + - v* + +permissions: + contents: read + +jobs: + + deploy: + + name: Build everything + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + environment: + name: release + + steps: + + - name: Checkout + uses: actions/checkout@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a + with: + username: ${{ vars.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_SECRET }} + + - name: Get version + id: version + run: echo VERSION=$(git describe --exact-match --tags | sed 's/^v//') >> $GITHUB_OUTPUT + + - name: Install Typescript support + run: npm install + + - name: Run tests + run: npm test -- --run + + - name: Extract metadata for container + id: meta + uses: docker/metadata-action@v4 + with: + images: trustgraph/workbench-ui + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha + + - name: Build + run: make service-package VERSION=${{ steps.version.outputs.VERSION }} + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 + with: + context: . + file: ./Containerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..32183714 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*~ +workbench-ui/workbench/__pycache__/ +workbench-ui/workbench/ui/ +workbench-ui/workbench/version.py +*.egg-info/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..eac61925 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +*.json +*.yaml +*.md +pulumi +**/node_modules +env +dist diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..8b6b8803 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "printWidth": 79 +} diff --git a/CODEBOT-INSTRUCTIONS.md b/CODEBOT-INSTRUCTIONS.md new file mode 100644 index 00000000..24ce203a --- /dev/null +++ b/CODEBOT-INSTRUCTIONS.md @@ -0,0 +1,836 @@ +# UI Toolkits and Framework Notes + +## Change Management and API Stability + +**CRITICAL**: Components in `src/components/common/` are foundational to the entire application. DO NOT modify these components without explicit approval from the application design authority. Changes to common components have extensive downstream impact and can break multiple features across the application. + +### Change Impact Assessment +Before modifying any common component: +1. **Document all consumers** - Search the entire codebase for usage +2. **Assess breaking changes** - Any interface changes affect all consumers +3. **Test extensively** - Changes can break seemingly unrelated features +4. **Get approval** - Design authority must approve all common component changes + +### Lessons from SelectField Issues +Recent issues with SelectField demonstrate why common component changes are dangerous: +- **September 2025**: Changes to SelectField to support one feature (Ontology editor) broke document submission +- **Root cause**: Interface contract violations between array/string APIs +- **Impact**: Multiple components across different domains affected +- **Resolution required**: Systematic updates to 15+ components across the application + +**Key takeaway**: Changing common components to fix one feature often breaks others. Always prefer adapter patterns or feature-specific solutions over modifying shared infrastructure. + +## Directory Structure and Organization Rationale + +### Core Principles + +We follow a **domain-driven, flat structure** that avoids unnecessary nesting and keeps related code together: + +1. **Avoid generic aggregation directories** - No `src/hooks/`, `src/constants/`, `src/utils/` that become dumping grounds +2. **Colocate by domain** - Keep related code together in feature-specific directories +3. **Flat when possible** - Single files don't need their own subdirectories +4. **Clear separation of concerns** - Different types of logic go in appropriate places + +### Directory Layout + +``` +src/ +├── components/ # UI components organized by domain +│ ├── schemas/ # All schema-related UI components +│ │ ├── EditSchemaDialog.tsx # Main orchestrator component +│ │ ├── SchemaFieldEditor.tsx # Individual field editing +│ │ ├── SchemaFieldsList.tsx # Fields list management +│ │ ├── SchemaTableStates.tsx # Reusable table states +│ │ ├── useSchemaForm.ts # Form state logic (colocated) +│ │ └── ... +│ ├── taxonomies/ # All taxonomy-related UI components +│ └── common/ # Truly shared/generic components +├── model/ # Data models, types, and domain constants +│ ├── schemas-table.tsx # Schema data models +│ ├── schemaTypes.ts # Schema type constants +│ └── ... +├── state/ # Application state management +│ ├── schemas.ts # Schema API calls and state +│ └── ... +├── api/ # Direct API communication +└── utils/ # Pure utility functions (no React/UI) +``` + +### Rationale by Directory + +**`src/components/[domain]/`** +- Contains ALL UI components for a specific domain (schemas, taxonomies, etc.) +- Includes domain-specific hooks like `useSchemaForm.ts` +- **Why**: Keeps everything needed to work on a feature in one place +- **Avoid**: Generic `src/hooks/` that becomes a dumping ground + +**`src/model/`** +- Data types, interfaces, constants, and domain models +- **Why**: Centralized data definitions that can be imported anywhere +- **Example**: `schemaTypes.ts` contains `SCHEMA_TYPE_OPTIONS` and `DEFAULT_FIELD` + +**`src/state/`** +- High-level application state management +- React Query hooks for API calls and caching +- **Why**: Separates data fetching/caching from UI logic + +**`src/api/`** +- Direct API communication layer +- WebSocket management +- **Why**: Abstracts network concerns from business logic + +### Benefits of This Approach + +1. **Discoverability**: All schema-related code is in `src/components/schemas/` +2. **Maintainability**: Changes to schema features are localized +3. **Reusability**: Shared types in `src/model/` can be imported anywhere +4. **Scalability**: New domains get their own component directories +5. **Avoids Anti-patterns**: No generic directories that accumulate unrelated files + +### Example: Schema Feature Organization + +When working on schema-related features, everything you need is in one place: +- UI components: `src/components/schemas/` +- Data models: `src/model/schemas-table.tsx`, `src/model/schemaTypes.ts` +- API/state: `src/state/schemas.ts` + +This eliminates the need to hunt through multiple generic directories to understand or modify a feature. + +## Icon Library + +**CRITICAL**: Always use `lucide-react` for icons throughout the application. Do NOT use `react-icons` or any other icon library. + +```tsx +// ✅ Correct - Use lucide-react +import { Plus, Save, Trash2, Edit, Settings } from "lucide-react"; + +// ❌ Wrong - Don't use react-icons +import { FiPlus, FiSave } from "react-icons/fi"; +``` + +**Common icon mappings from react-icons to lucide-react:** +- `FiPlus` → `Plus` +- `FiX` → `X` +- `FiSave` → `Save` +- `FiTrash2` → `Trash2` +- `FiEdit/FiEdit3` → `Edit` +- `FiSettings` → `Settings` +- `FiDownload` → `Download` +- `FiUpload` → `Upload` +- `FiMove` → `Move` +- `FiMoreVertical` → `MoreVertical` +- `FiList` → `List` + +## Chakra UI Version + +**CRITICAL**: This project uses **Chakra UI v3**, NOT v2. Always check component APIs against v3 documentation. + +## Key Chakra v3 Migration Points + +### Modal → Dialog +```tsx +// ❌ Chakra v2 + + + + Title + Content + + + +// ✅ Chakra v3 + onOpenChange(x.open)}> + + + + + + Title + + Content + + + + +``` + +### Toast System +```tsx +// ❌ Chakra v2 +const toast = useToast(); +toast({ title: "Success", status: "success" }); + +// ✅ Chakra v3 +import { toaster } from "../ui/toaster"; +toaster.create({ title: "Success", status: "success" }); +``` + +### Form Components +```tsx +// ❌ Chakra v2 + + Label + + + +// ✅ Chakra v3 + + Label + + +``` + +### Tabs Structure +```tsx +// ❌ Chakra v2 + + + Tab 1 + + + Content + + + +// ✅ Chakra v3 + + + Tab 1 + + Content + +``` + +### Menu Components +```tsx +// ❌ Chakra v2 + + Button + + Item + + + +// ✅ Chakra v3 + + Button + + Item + + +``` + +### Props Changes +```tsx +// ❌ Chakra v2 props +colorScheme="blue" +isDisabled={true} + +// ✅ Chakra v3 props +colorPalette="blue" +disabled={true} +``` + +### Layout Components +```tsx +// ❌ Chakra v2 + + +// ✅ Chakra v3 + +``` + +### Spacing Props +```tsx +// ❌ Old pattern + + + +// ✅ Chakra v3 + + +``` + +### Button Icons +```tsx +// ❌ Old pattern + +} aria-label="Upload" /> + +// ✅ Chakra v3 + + +``` + +### Input Groups (Simplified) +```tsx +// ❌ Chakra v2 + + 🔍 + + + +// ✅ Chakra v3 (simplified approach) + +``` + +### Avatar Structure +```tsx +// ❌ Chakra v2 + + +// ✅ Chakra v3 + + + +``` + +### Alert Component +```tsx +// ❌ Chakra v2 + + + Error message + + +// ✅ Chakra v3 + + + + Error message + + +``` + +**Alert Status Options:** +- `status="error"` - Red error alerts +- `status="warning"` - Orange warning alerts +- `status="success"` - Green success alerts +- `status="info"` - Blue info alerts + +**Alert with Title:** +```tsx + + + + Warning Title + Warning description text + + +``` + +### Progress Component +```tsx +// ❌ Chakra v2 + + +// ✅ Chakra v3 + + + + + + + +``` + +**Progress with custom styling:** +```tsx + + + + + +``` + +## Layout Components Still Work + +**Important**: VStack, HStack, Box, Grid, GridItem, Text, Button, Input, etc. still work the same way in v3. The confusion around VStack/HStack causing "invalid component type" errors is usually due to **circular import dependencies**, not Chakra version issues. + +## Common Debugging Steps + +1. **Check imports**: Ensure all Chakra components are imported from `@chakra-ui/react` +2. **Verify component structure**: Use the v3 nested component patterns (Component.Root, Component.Trigger, etc.) +3. **Check props**: Use `colorPalette` instead of `colorScheme`, `disabled` instead of `isDisabled` +4. **Circular imports**: If getting "invalid component type" errors with basic components like VStack, check for circular import dependencies + +## Migration Verification Checklist + +When migrating components to Chakra v3: +- [ ] Replace `` with `` +- [ ] Replace `` with `` +- [ ] Wrap text in `...` +- [ ] Replace `` with `` +- [ ] Use `` structure for cards +- [ ] Replace `` with `` +- [ ] Replace `spacing` props with `gap` props +- [ ] Replace `colorScheme` with `colorPalette` +- [ ] Replace `isDisabled` with `disabled` +- [ ] Test build after changes +- [ ] Verify visual styling is preserved +- [ ] Be systematic: search for old patterns, document the fix, then apply consistently + +## Project-Specific Patterns + +### Notifications +**CRITICAL: NEVER use the toaster directly.** The `toaster` from `@chakra-ui/react` or `../ui/toaster` must NOT be imported or used directly. Always use the `useNotification` hook: + +```tsx +// ❌ NEVER do this - toaster is forbidden +import { toaster } from "../ui/toaster"; +import { toaster } from "@chakra-ui/react"; +toaster.create({ title: "Success", status: "success" }); + +// ✅ ALWAYS do this instead +import { useNotification } from "../../state/notify"; + +const notify = useNotification(); +notify.success("Operation completed successfully"); +notify.error("Something went wrong"); +notify.info("FYI: This is informational"); +``` + +**Why toaster is forbidden:** +- Direct toaster usage bypasses the project's notification standards +- The `useNotification` hook provides consistent error prefixing and styling +- It maintains a unified notification interface across the application +- Direct toaster usage can cause inconsistent user experience + +### Common Components +**ALWAYS** prefer using pre-built components from `src/components/common/` instead of raw Chakra components. These components handle Chakra v3 APIs correctly and reduce boilerplate: + +```tsx +// ❌ Don't use raw Chakra components + + Name + setValue(e.target.value)} /> + + +// ✅ Use common components instead + +``` + +**Available Common Components:** +- `TextField` - Text input with label and validation +- `TextAreaField` - Multi-line text input +- `SelectField` - Dropdown select with rich options +- `BasicTable` - Pre-configured Tanstack Table +- `Card` - Consistent card layout with title/description +- `ProgressSubmitButton` - Submit button with loading state +- `PageHeader` - Standard page header layout +- `StatusBadge` - Consistent status indicators +- `CenterSpinner` - Loading spinner +- `ChipInputField` - Tag/chip input field +- `NumberField` - Numeric input with validation +- `Slider` - Range slider component + +#### SelectField Usage +**CRITICAL**: SelectField expects array values for selection and MUST include description fields for dropdown display: + +```tsx +// ✅ Correct usage +import SelectField from "../common/SelectField"; +import SelectOptionText from "../common/SelectOptionText"; + + + Option 1 + + ) + }, + { + value: 'option2', + label: 'Option 2', + description: ( + + Option 2 + + ) + } + ]} + value={selectedValues} // array - current selection (empty array for no selection) + onValueChange={(values) => setSelectedValues(values)} // receives array +/> +``` + +**Important Notes:** +- Pass an empty array `[]` for no selection, not an empty string +- The `value` prop should always be an array +- The `onValueChange` callback receives an array +- For single selection, extract the first element: `values.length > 0 ? values[0] : null` +- **REQUIRED**: The `description` field MUST be provided using `SelectOptionText` or `SelectOption` components +- **Missing descriptions will result in empty dropdown options** + +**Example with single selection extraction:** +```tsx +const [selectedValues, setSelectedValues] = useState([]); + +// Get the selected value (for single select behavior) +const selectedValue = selectedValues.length > 0 ? selectedValues[0] : null; + +// Handle submission +const handleSubmit = () => { + if (selectedValue) { + onSubmit(selectedValue); + } +}; +``` + +**Common Mistake - Missing Descriptions:** +```tsx +// ❌ WRONG - Will show empty dropdown options +items={[ + {value: 'option1', label: 'Option 1'}, // Missing description! + {value: 'option2', label: 'Option 2'} // Missing description! +]} + +// ✅ CORRECT - Includes required descriptions +items={[ + { + value: 'option1', + label: 'Option 1', + description: Option 1 + }, + { + value: 'option2', + label: 'Option 2', + description: Option 2 + } +]} +``` + +### Theming and Colors +**ALWAYS** use semantic color tokens instead of direct color palettes. The theme provides semantic tokens that automatically handle light/dark mode: + +```tsx +// ❌ Don't use direct color palettes +colorPalette="blue" +bg="gray.100" +color="deepPlum.700" + +// ✅ Use semantic tokens instead +colorPalette="primary" +bg="bg.muted" +color="primary.fg" +``` + +**Available Semantic Color Palettes:** +- `primary` - Main brand color (airForceBlue) +- `accent` - Secondary brand color (deepPlum) +- `observing` - For observation callouts (warmNeutral) +- `thinking` - For thinking callouts (deepPlum variants) +- `insightful` - For answer callouts (neutralGreen) + +**Semantic Token Structure:** +Each palette has these variants: +- `.solid` - Strong, high contrast (buttons, badges) +- `.contrast` - Text on solid backgrounds +- `.fg` - Foreground text color +- `.muted` - Subtle backgrounds +- `.subtle` - Light backgrounds +- `.emphasized` - Medium emphasis backgrounds +- `.focusRing` - Focus indicators + +**Background/Text Tokens:** +- `background` - Main page background +- `text` - Main text color +- `bg.muted` - Subtle background areas +- `fg.muted` - Muted text + +### Page Structure +**ALWAYS** use consistent page structure with PageHeader: + +```tsx +// ❌ Don't embed headings in components +export const MyComponent = () => { + return ( + + My Page Title + + + ); +}; + +// ✅ Use PageHeader at the page level +// In pages/MyPage.tsx: +import PageHeader from "../components/common/PageHeader"; +import MyComponent from "../components/MyComponent"; + +const MyPage = () => { + return ( + <> + } + title="Page Title" + description="Brief description of what this page does" + /> + + + ); +}; + +// In components/MyComponent.tsx (no heading): +export const MyComponent = () => { + return ( + + + + ); +}; +``` + +**Page Structure Rules:** +1. Page components go in `src/pages/` directory +2. Always use `PageHeader` component for consistent headers +3. Page title and description should be at page level, not component level +4. Components should not contain their own page-level headings +5. Use appropriate lucide-react icons for the page icon + +### Progress Management and Loading States + +**CRITICAL**: Always use the `useActivity` hook for loading states instead of managing spinners manually. This provides consistent loading indicators across the application. + +```tsx +// ❌ Don't manage loading states manually +const [isLoading, setIsLoading] = useState(false); +const handleSubmit = async () => { + setIsLoading(true); + try { + await submitData(); + } finally { + setIsLoading(false); + } +}; + +// ✅ Use useActivity hook instead +import { useActivity } from "../../state/activity"; + +const submitMutation = useMutation({ + mutationFn: submitData, +}); + +// Automatically shows/hides loading indicator +useActivity(submitMutation.isPending, "Submitting data"); +``` + +**Progress System Components:** + +1. **`useProgressStateStore`** - Zustand store that manages global activity tracking + - `activity: Set` - Active operations being tracked + - `error: string` - Current error state + - `addActivity(name)` - Add a loading operation + - `removeActivity(name)` - Remove a loading operation + - `setError(message)` - Set/clear error state + +2. **`useActivity(isActive, description)`** - React hook for automatic activity management + - `isActive: boolean` - Whether the activity is currently running + - `description: string` - User-friendly description of the activity + - Automatically adds/removes activities based on the boolean condition + - Handles cleanup when component unmounts or dependencies change + +**Usage Patterns:** + +```tsx +// ✅ With React Query mutations +const updateMutation = useMutation({ mutationFn: updateData }); +useActivity(updateMutation.isPending, "Updating settings"); + +// ✅ With React Query queries +const dataQuery = useQuery({ queryKey: ['data'], queryFn: fetchData }); +useActivity(dataQuery.isLoading, "Loading data"); + +// ✅ Multiple activities for complex operations +useActivity(settingsQuery.isLoading, "Loading settings"); +useActivity(updateSettingsMutation.isPending, "Saving settings"); +useActivity(resetSettingsMutation.isPending, "Resetting settings"); + +// ✅ Manual activity management (when useActivity isn't sufficient) +const addActivity = useProgressStateStore((state) => state.addActivity); +const removeActivity = useProgressStateStore((state) => state.removeActivity); + +const handleComplexOperation = async () => { + const activityId = "Processing complex operation"; + addActivity(activityId); + try { + await step1(); + await step2(); + await step3(); + } finally { + removeActivity(activityId); + } +}; +``` + +**Benefits of the Progress System:** +- **Consistent UX**: All loading states are managed centrally +- **Automatic cleanup**: Activities are removed when operations complete or components unmount +- **Deduplication**: Multiple identical activity names are automatically deduplicated +- **Global visibility**: The UI can show a global loading indicator when any activities are active +- **Error handling**: Centralized error state management +- **Zero boilerplate**: Just call `useActivity()` with a boolean and description + +**Integration with TanStack Query:** +The progress system integrates perfectly with TanStack Query's loading states: + +```tsx +// All these patterns work seamlessly together +export const useSettings = () => { + const settingsQuery = useQuery({ + queryKey: ["settings"], + queryFn: fetchSettings, + }); + + const updateMutation = useMutation({ + mutationFn: updateSettings, + }); + + // Automatic activity tracking + useActivity(settingsQuery.isLoading, "Loading settings"); + useActivity(updateMutation.isPending, "Saving settings"); + + return { + settings: settingsQuery.data, + isLoading: settingsQuery.isLoading, + updateSettings: updateMutation.mutate, + isSaving: updateMutation.isPending, + }; +}; +``` + +### Table Components + +**CRITICAL**: Always use TanStack Table with our standardized table components instead of manually implementing Chakra Table structures. + +**Standard Table Pattern:** + +1. **Create Model File** (`src/model/[feature]-table.tsx`): +```tsx +import { createColumnHelper } from "@tanstack/react-table"; + +export type MyData = { + id: string; + name: string; + description: string; +}; + +export const columnHelper = createColumnHelper(); + +export const columns = [ + columnHelper.accessor("id", { + header: "ID", + cell: (info) => info.getValue(), + }), + columnHelper.accessor("name", { + header: "Name", + cell: (info) => info.getValue(), + }), + columnHelper.accessor("description", { + header: "Description", + cell: (info) => info.getValue(), + }), +]; +``` + +2. **Use Common Table Components**: +```tsx +// ❌ Don't manually implement table structure + + + + Name + + + + {data.map(row => ( + + {row.name} + + ))} + + + +// ✅ Use standardized components and models +import { BasicTable } from "../common/BasicTable"; +import { columns, MyData } from "../../model/my-data-table"; + +const table = useReactTable({ + data: myData as MyData[], + columns, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), +}); + +return ; +``` + +**Available Table Components:** +- `BasicTable` - Standard table display +- `ClickableTable` - Table with row click handlers +- `SelectableTable` - Table with row selection checkboxes + +**Standard Column Patterns:** +```tsx +// Selection column (for SelectableTable) +columnHelper.display({ + id: "select", + header: ({ table }) => ( + + + + + ), + cell: ({ row }) => ( + + + + + ), +}); + +// Data columns with custom formatting +columnHelper.accessor("timestamp", { + header: "Created", + cell: (info) => timeString(info.getValue()), +}); + +// Tags/badges column +columnHelper.accessor("tags", { + header: "Tags", + cell: (info) => + info.getValue()?.map((tag) => ( + + {tag} + + )), +}); +``` + +**Benefits of Standardized Tables:** +- ✅ Consistent behavior and styling across the application +- ✅ Built-in sorting, selection, and interaction patterns +- ✅ Type safety with column definitions +- ✅ Easier testing and maintenance +- ✅ Better performance with TanStack optimizations +- ✅ Automatic loading state integration with progress system + +### Other Patterns +- Use Tanstack Query for state management with existing socket-based config API +- Follow kebab-case naming conventions for IDs and URLs +- Always prefer TanStack Table models over manual Chakra Table implementation \ No newline at end of file diff --git a/Containerfile b/Containerfile new file mode 100644 index 00000000..90b3b6e1 --- /dev/null +++ b/Containerfile @@ -0,0 +1,30 @@ + +FROM alpine:3.21 AS build + +RUN apk add --update --no-cache --no-progress make g++ gcc linux-headers + +RUN apk add --update --no-cache --no-progress python3 py3-pip py3-wheel \ + python3-dev + +RUN mkdir /root/wheels + +COPY workbench-ui /root/workbench-ui/ + +RUN (cd /root/workbench-ui && pip wheel -w /root/wheels --no-deps .) + +FROM alpine:3.21 + +ENV PIP_BREAK_SYSTEM_PACKAGES=1 + +COPY --from=build /root/wheels /root/wheels + +RUN apk add --update --no-cache --no-progress python3 py3-pip \ + py3-aiohttp + +RUN pip install /root/wheels/* && \ + pip cache purge && \ + rm -rf /root/wheels + +CMD service +EXPOSE 8888 + diff --git a/FLOW-CLASS-NOTES.md b/FLOW-CLASS-NOTES.md new file mode 100644 index 00000000..85df7c1b --- /dev/null +++ b/FLOW-CLASS-NOTES.md @@ -0,0 +1,132 @@ +# Flow Class Architecture Notes + +## Overview + +Flow Classes define distributed service mesh architectures for TrustGraph dataflow processing. They specify how processors connect through message queues to form complete data processing pipelines. + +## Core Concepts + +### Service Mesh Graph + +Flow Classes describe a **service mesh graph** where: +- **Processors** are nodes that provide and consume services +- **Queues** are the edges that connect processors +- **Queue names** determine connectivity - matching names create connections +- **Template variables** control queue multiplexing strategy + +### Service Providers vs Consumers + +**Service Providers** implement services by listening to special queue names: +- `input` - receives work/data to process +- `request` - handles request/response patterns (receives requests) +- `response` - sends back responses in request/response patterns + +**Service Consumers** use services through all other queue names: +- Any queue name NOT `input`/`request`/`response` +- Represents dependencies on external services +- Send messages TO these queues as clients + +### Queue Multiplexing Strategy + +**Class Processors** (`{class}` template): +- Use **shared queues** across all flow instances of that class +- One `service-name-{class}` queue serves ALL flows +- Higher throughput, shared resources +- Example: `user-auth-{class}` becomes `user-auth-nlp-chat` + +**Flow Processors** (`{id}` template): +- Use **dedicated queues** per individual flow instance +- Each flow gets its own `service-name-{id}` queue +- Isolated processing, per-flow state +- Example: `document-store-{id}` becomes `document-store-flow123` + +## Flow Class Structure + +```typescript +interface FlowClassDefinition { + id: string; + class: { + [processorName: string]: { + [queueName: string]: string; // Queue pattern with templates + }; + }; + flow: { + [processorName: string]: { + [queueName: string]: string; // Queue pattern with templates + }; + }; + interfaces: { + [interfaceName: string]: string | { + request: string; + response: string; + }; + }; + description?: string; + tags?: string[]; +} +``` + +## Service Graph Connections + +Connections form when: +1. **Processor A** has queue "service-x" (as consumer) +2. **Processor B** implements "service-x" via `input`/`request`/`response` (as provider) +3. This creates edge: A → B + +Example: +``` +nlp-processor: + input: "nlp-service-{class}" # Provides nlp-service + +chat-handler: + nlp: "nlp-service-{class}" # Consumes nlp-service +``` +Result: `chat-handler` → `nlp-processor` + +## External API Layer + +**Interfaces** define the **external API contract** - how external clients access internal services: + +- **Public Service Contract**: External clients call interface endpoints +- **Internal Routing**: Interfaces map to internal queue services +- **Implementation Hiding**: Internal processor topology is hidden from clients + +Interface Types: +- **Simple**: `"api-endpoint": "internal-service-name"` +- **Request/Response**: + ```json + { + "user-api": { + "request": "user-request-{class}", + "response": "user-response-{class}" + } + } + ``` + +## Architecture Layers + +``` +┌─────────────────────────────────┐ +│ External Clients │ +│ (REST, GraphQL, etc.) │ +└─────────────────┬───────────────┘ + │ +┌─────────────────▼───────────────┐ +│ Interfaces │ +│ (Public API Contract) │ +└─────────────────┬───────────────┘ + │ +┌─────────────────▼───────────────┐ +│ Internal Service Mesh │ +│ (Class + Flow Processors) │ +│ Connected via Queue Names │ +└─────────────────────────────────┘ +``` + +## Key Insights + +1. **Class vs Flow is about queue sharing**, not different processor types +2. **Queue names create the service graph** - they're the connection points +3. **Interfaces expose internal services** to external clients +4. **Template variables control multiplexing** - shared vs dedicated queues +5. **Service mesh is unified** - class and flow processors all participate in the same graph \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..6b0b1270 --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..487c7e12 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ + +PACKAGE_VERSION=0.0.0 +VERSION=0.0.8 + +all: service-package container + +ui: + npm run build + rm -rf workbench-ui/workbench/ui/ + cp -r dist/ workbench-ui/workbench/ui/ +# cp public/*.png workbench-ui/workbench/ui/ + cp public/*.svg workbench-ui/workbench/ui/ + +service-package: ui update-package-versions + cd workbench-ui && python3 setup.py sdist --dist-dir ../pkgs/ + +update-package-versions: + echo __version__ = \"${PACKAGE_VERSION}\" > workbench-ui/workbench/version.py + +CONTAINER=docker.io/trustgraph/workbench-ui +DOCKER=podman + +container: + ${DOCKER} build -f Containerfile -t ${CONTAINER}:${VERSION} \ + --format docker + +push: + ${DOCKER} push ${CONTAINER}:${VERSION} + +docker-hub-login: + cat docker-token.txt | \ + docker login -u trustgraph --password-stdin registry-1.docker.io + diff --git a/README.criteria b/README.criteria new file mode 100644 index 00000000..07b9550c --- /dev/null +++ b/README.criteria @@ -0,0 +1,31 @@ +● Service connections: Same connection type (kind=service) where consumer's {connection-name}-request/{connection-name}-response + queues = provider's request/response queues + + Flow connections: Same connection type (kind=flow) where they share the exact same queue value + + Passive connections: Same connection type (kind=passive) where consumer's single queue value = provider's response queue value + + + + +Interfaces... + + 1. Look up the interface name in the service map's interfaces definitions + 2. Check the kind: + + 2. If kind = "flow": + - The flow class specifies a single queue string + - Example: "entity-contexts-load": "persistent://tg/flow/entity-contexts-load:{id}" + - To connect: find any processor that produces/consumes this exact queue + + If kind = "service": + - The flow class specifies request/response queues + - Example: "agent": {"request": "non-persistent://tg/request/agent:{id}", "response": "non-persistent://tg/response/agent:{id}"} + - To connect: find any processor whose request/response queues match these + 3. The interface definition provides: + - Documentation (description) + - Validation (ensuring correct structure based on kind) + - Visibility hints (for UI purposes) + + So the interfaces are essentially the "public API" of a flow class - they declare which standard connection points this flow class + exposes, and other components can connect to these standardized interfaces without knowing the internal implementation details. diff --git a/README.md b/README.md new file mode 100644 index 00000000..44fde108 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ + +# Test Suite for TrustGraph + +## Setup for Python + +``` +pip3 -m venv env +. env/bin/activate +pip3 install -r requirements.txt +``` + +## Dev mode + +``` +npm install +npm run dev +``` + +This runs the application in Vite at http://localhost:5173. +Note that UI bit works, but the generation part isn't running. + +## Run it all locally + +This builds the UI and the Python package: +``` +make service-package +``` + +Then run the Python package which serves the generator and UI: + +``` +export PYTHONPATH=workbench-ui +workbench-ui/scripts/service +``` + +Generation should work + +## Run it in a container + +Build the container: +``` +make service-package VERSION=0.0.0 +``` + +and run it + +``` +podman run -i -t -p 8080:8080 localhost/workbench-ui:0.0.0 +``` + +Go to http://localhost:8080 + +## Release it + +Deployment is Github actions, automatic to Docker Hub. Deployment kicks in +automatically on anything with a version tag. Version tags should be of +form v1.2.3. Convention is to have a branch name something like +`release/vX.Y` for version tags of the form `vX.Y.Z`. So, +version `v0.1.10` would be release on branch `release/v0.1`. + +On release, container images are pushed to docker hub. + +To release with TrustGraph, change the version number of the container +in the trustgraph repo, `templates/values/images.jsonnet` and also +in the config portal repo, same filename, `templates/values/images.jsonnet`. + diff --git a/TEST_STRATEGY.md b/TEST_STRATEGY.md new file mode 100644 index 00000000..66cd4afb --- /dev/null +++ b/TEST_STRATEGY.md @@ -0,0 +1,590 @@ +# Test Strategy for TrustGraph UI + +## Overview + +This document outlines the comprehensive testing strategy for the TrustGraph UI application, a React-based knowledge graph visualization and chat interface built with TypeScript, Vite, and Chakra UI. + +## Technology Stack + +- **Frontend**: React 18, TypeScript, Vite +- **UI Framework**: Chakra UI v3 +- **State Management**: Zustand +- **Data Fetching**: TanStack Query (React Query) +- **Routing**: React Router v7 +- **Visualization**: React Force Graph (3D), Three.js +- **WebSocket**: Custom socket implementation for real-time communication +- **Build Tools**: Vite, ESLint, Prettier + +## Testing Approach + +### 1. Unit Testing + +#### Framework Recommendation +- **Jest** + **React Testing Library** for component testing +- **Vitest** (recommended for Vite projects) as Jest alternative +- **@testing-library/jest-dom** for DOM assertions + +#### What to Test +- **State Management (Zustand stores)**: + - `src/state/chat.ts` - Chat state management + - `src/state/session.ts` - Session state + - `src/state/workbench.ts` - Workbench state + - `src/state/graph-query.ts` - Graph query state + - All other state modules in `src/state/` + +- **Utility Functions**: + - `src/utils/knowledge-graph.ts` - Graph manipulation utilities + - `src/utils/document-encoding.ts` - Document encoding/decoding + - `src/utils/vector-search.ts` - Vector search utilities + - `src/utils/time-string.ts` - Time formatting utilities + +- **API Layer**: + - `src/api/trustgraph/socket.ts` - WebSocket connection + - `src/api/trustgraph/service-call.ts` - Service call utilities + - `src/api/trustgraph/messages.ts` - Message handling + +#### Test Structure +``` +tests/ +├── unit/ +│ ├── components/ +│ ├── state/ +│ ├── utils/ +│ ├── api/ +│ └── model/ +├── integration/ +├── e2e/ +└── fixtures/ +``` + +### 2. Component Testing + +#### Core Components to Test +- **Chat Components**: + - `src/components/chat/ChatConversation.tsx` + - `src/components/chat/ChatMessage.tsx` + - `src/components/chat/InputArea.tsx` + - `src/components/chat/ChatModeSelector.tsx` + +- **Graph Components**: + - `src/components/graph/Graph.tsx` + - `src/components/entity/EntityDetail.tsx` + - `src/components/entity/EntityNode.tsx` + +- **Common Components**: + - `src/components/common/BasicTable.tsx` + - `src/components/common/SelectableTable.tsx` + - `src/components/common/TextField.tsx` + - `src/components/common/Card.tsx` + +- **Layout Components**: + - `src/components/Layout.tsx` + - `src/components/Sidebar.tsx` + +#### Testing Strategies +- **Snapshot Testing** for UI consistency +- **User Interaction Testing** (click, input, navigation) +- **Props Testing** and component behavior +- **Error Boundary Testing** +- **Accessibility Testing** (screen reader, keyboard navigation) + +### 3. Integration Testing + +#### Areas to Test +- **WebSocket Integration**: Test real-time communication with backend +- **State Persistence**: Test Zustand store persistence +- **Route Navigation**: Test React Router integration +- **API Integration**: Test service calls and data flow +- **Component Interaction**: Test parent-child component communication + +#### Mock Strategy +- Mock WebSocket connections for consistent testing +- Mock external APIs and services +- Mock file upload/download operations +- Mock 3D graph rendering for performance + +### 4. End-to-End Testing + +#### Framework Recommendation +- **Playwright** or **Cypress** for cross-browser testing + +#### Test Scenarios +1. **User Authentication Flow** +2. **Chat Interface**: + - Send messages in different modes (graph-rag, agent, basic-llm) + - Receive and display responses + - Chat history persistence + +3. **Knowledge Graph Visualization**: + - Load and display graph data + - Node interaction and navigation + - Graph filtering and search + +4. **Document Management**: + - Upload documents + - Process and index documents + - Search through document library + +5. **Flow Management**: + - Create and edit processing flows + - Execute flows with different parameters + +6. **Agent Tools**: + - Configure MCP tools + - Test agent interactions + - Tool execution and results + +### 5. Performance Testing + +#### Areas to Monitor +- **Bundle Size**: Monitor JavaScript bundle size +- **3D Graph Rendering**: Test performance with large graphs +- **Memory Usage**: Monitor memory leaks in long-running sessions +- **WebSocket Performance**: Test real-time communication under load + +#### Tools +- **Lighthouse** for web performance metrics +- **Bundle Analyzer** for bundle size optimization +- **React DevTools Profiler** for component performance + +### 6. Accessibility Testing + +#### Requirements +- **WCAG 2.1 AA** compliance +- **Screen Reader** compatibility +- **Keyboard Navigation** support +- **Color Contrast** validation + +#### Tools +- **axe-core** for automated accessibility testing +- **React Testing Library** accessibility queries +- **Manual testing** with screen readers + +## Testing Infrastructure + +### Setup Requirements +```bash +# Install testing dependencies +npm install --save-dev \ + vitest \ + @testing-library/react \ + @testing-library/jest-dom \ + @testing-library/user-event \ + jsdom \ + @vitest/ui \ + playwright +``` + +### Configuration Files +- `vitest.config.ts` - Vitest configuration +- `playwright.config.ts` - E2E test configuration +- `test-setup.ts` - Global test setup + +### CI/CD Integration +- **GitHub Actions** workflow for automated testing +- **Pre-commit hooks** for running tests before commits +- **Coverage reporting** with minimum thresholds +- **Visual regression testing** for UI components + +## Test Data Management + +### Mock Data Strategy +- **Fixtures**: Static test data for consistent testing +- **Factory Functions**: Generate test data programmatically +- **MSW (Mock Service Worker)**: Mock API responses +- **WebSocket Mocking**: Mock real-time communication + +### Data Categories +- **Graph Data**: Nodes, edges, and relationships +- **Chat Messages**: Various message types and formats +- **User Sessions**: Authentication and session data +- **Document Metadata**: File information and processing status + +## Coverage Goals + +### Minimum Coverage Targets +- **Unit Tests**: 80% line coverage +- **Integration Tests**: Cover all major user flows +- **E2E Tests**: Cover critical business paths +- **Component Tests**: 90% of UI components + +### Exclusions +- Third-party library code +- Generated type definitions +- Development-only code + +## Testing Best Practices + +### General Guidelines +1. **Test behavior, not implementation** +2. **Use descriptive test names** +3. **Keep tests independent and isolated** +4. **Test error conditions and edge cases** +5. **Maintain test data consistency** + +### React-Specific Guidelines +1. **Test user interactions, not internal state** +2. **Use React Testing Library queries effectively** +3. **Test accessibility features** +4. **Mock external dependencies** +5. **Test component composition** + +## Monitoring and Maintenance + +### Test Health +- **Flaky Test Detection**: Monitor and fix unstable tests +- **Test Performance**: Keep test execution time reasonable +- **Coverage Trends**: Monitor coverage over time +- **Test Maintenance**: Regular review and cleanup + +### Quality Gates +- **All tests must pass** before merging +- **Coverage thresholds** must be maintained +- **Performance budgets** must not be exceeded +- **Accessibility standards** must be met + +## Domain-Specific Testing Strategy + +### Agents Module (`src/components/agents/`) + +#### Components to Test: +- **EditDialog.tsx**: Agent configuration dialog + - Form validation for agent settings + - Tool selection and configuration + - Modal open/close behavior +- **ToolsTable.tsx**: Display agent tools + - Table rendering with tool data + - Tool selection/filtering + - Action buttons (edit, delete, enable/disable) + +#### Testing Approach: +```tsx +// Example test structure +describe('Agent EditDialog', () => { + it('validates required fields', () => { + // Test form validation + }); + + it('handles tool selection', () => { + // Test multi-select tool interface + }); + + it('saves agent configuration', () => { + // Test API calls and state updates + }); +}); +``` + +#### Mock Requirements: +- Agent configuration API calls +- Available tools data +- Agent execution results + +--- + +### Graph Module (`src/components/graph/`) + +#### Components to Test: +- **Graph.tsx**: 3D force graph visualization + - Graph rendering with mock data + - Node selection and highlighting + - Performance with large datasets (>1000 nodes) +- **NodeDetailsDrawer.tsx**: Entity detail panel + - Property display formatting + - Relationship navigation + - Edit mode functionality + +#### Testing Challenges: +- **3D Rendering**: Mock Three.js and react-force-graph +- **Performance**: Test with large graph datasets +- **Interactions**: Node selection, drag, zoom behaviors + +#### Testing Approach: +```tsx +describe('Graph Component', () => { + beforeEach(() => { + // Mock 3D rendering libraries + vi.mock('react-force-graph-3d'); + }); + + it('renders nodes and links', () => { + // Test graph data rendering + }); + + it('handles node selection', () => { + // Test node click events + }); + + it('performs well with large datasets', () => { + // Performance testing with 1000+ nodes + }); +}); +``` + +--- + +### MCP Tools Module (`src/components/mcp-tools/`) + +#### Components to Test: +- **EditDialog.tsx**: MCP tool configuration + - Tool parameter validation + - API endpoint configuration + - Authentication settings +- **McpToolsTable.tsx**: Tools management interface + - Tool status indicators + - Bulk operations (enable/disable multiple tools) + - Tool execution history + +#### Test Data Requirements: +```tsx +// Mock MCP tool configurations +const mockMcpTool = { + id: 'test-tool-1', + name: 'File System Tool', + endpoint: 'http://localhost:3001', + enabled: true, + parameters: { + path: '/tmp', + permissions: 'read-write' + } +}; +``` + +--- + +### Prompts Module (`src/components/prompts/`) + +#### Components to Test: +- **EditDialog.tsx**: Prompt template editor + - Template syntax validation + - Variable substitution preview + - JSON schema validation for structured outputs +- **PromptsTable.tsx**: Prompt management + - Template versioning + - Usage statistics + - Import/export functionality + +#### Key Test Scenarios: +- Template variable validation: `{{variable}}` syntax +- JSON schema validation for structured prompts +- Prompt execution with different input types + +--- + +### Schemas Module (`src/components/schemas/`) - Recently Modularized + +#### Components to Test (New Modular Structure): +- **SchemaFieldEditor.tsx**: Individual field configuration + - Field type selection and validation + - Enum value management + - Required/optional field toggles +- **useSchemaForm.ts**: Form state management hook + - Field addition/removal + - Form validation logic + - Form reset functionality +- **EnumValueManager.tsx**: Enum value editing + - Add/remove enum values + - Duplicate value prevention + - Input validation + +#### Testing Approach for Modular Components: +```tsx +describe('Schema Field Editor', () => { + it('handles field type changes', () => { + // Test type dropdown and dependent field updates + }); + + it('manages enum values correctly', () => { + // Test enum value addition/removal + }); + + it('validates field configurations', () => { + // Test field validation rules + }); +}); + +describe('useSchemaForm hook', () => { + it('manages form state correctly', () => { + // Test hook state management + }); + + it('handles field operations', () => { + // Test add/remove/update field operations + }); +}); +``` + +--- + +### Taxonomies Module (`src/components/taxonomies/`) - Extensive Functionality + +#### Priority Components to Test: +- **TaxonomyManager.tsx**: Main taxonomy editor + - SKOS concept hierarchy management + - Concept relationships (broader/narrower/related) + - Bulk concept operations +- **ConceptEditor.tsx**: Individual concept editing + - Concept metadata validation + - Relationship consistency checking + - Auto-save functionality +- **TaxonomyValidationTab.tsx**: SKOS validation + - Validation rule execution + - Error reporting and suggestions + - Auto-fix functionality +- **SKOSDialog.tsx**: Import/export functionality + - SKOS RDF/XML parsing + - Format conversion (RDF ↔ Turtle ↔ JSON) + - File upload/download + +#### Complex Test Scenarios: +```tsx +describe('Taxonomy Validation', () => { + it('detects circular references', () => { + const invalidTaxonomy = { + concepts: { + 'A': { broader: 'B' }, + 'B': { broader: 'A' } // Circular reference + } + }; + // Test validation catches this error + }); + + it('suggests auto-fixes', () => { + // Test quality improvement suggestions + }); +}); + +describe('SKOS Import/Export', () => { + it('parses valid SKOS RDF/XML', () => { + // Test XML parsing and conversion + }); + + it('handles parsing errors gracefully', () => { + // Test error handling for invalid SKOS + }); +}); +``` + +#### Mock Requirements: +- SKOS validation rules +- File upload/download operations +- Large taxonomy datasets (100+ concepts) + +--- + +## Implementation Roadmap (Updated) + +### Phase 1: Foundation & Utilities (Weeks 1-2) +- **Complete**: Set up testing framework (Vitest already configured) +- **Complete**: Utility function tests (SKOS, time-string, encoding) +- **Complete**: Basic common component tests +- **New**: Test modularized schema components + +### Phase 2: Domain-Specific Components (Weeks 3-4) +- **Agents Module**: Test agent configuration and tools management +- **Prompts Module**: Test prompt editing and template validation +- **MCP Tools Module**: Test tool configuration and execution +- **Graph Module**: Test visualization components (with mocked 3D) + +### Phase 3: Complex Domain Logic (Weeks 5-6) +- **Taxonomies Module**: Test SKOS validation, concept editing, import/export +- **Schemas Module**: Test advanced schema validation and form logic +- **Integration Tests**: Test cross-module interactions + +### Phase 4: Advanced Testing (Weeks 7-8) +- **Performance Testing**: Large dataset handling (1000+ graph nodes, 100+ taxonomy concepts) +- **E2E Workflows**: Complete user journeys across modules +- **Accessibility**: WCAG compliance for all form components +- **Visual Regression**: Ensure UI consistency across refactoring + +--- + +## Testing Priority Matrix + +### High Priority (Week 3-4) +1. **Taxonomies**: Complex SKOS logic, validation, import/export +2. **Schemas**: Recently modularized, needs comprehensive coverage +3. **Graph**: Core visualization functionality +4. **Agents**: Critical for AI functionality + +### Medium Priority (Week 5-6) +1. **Prompts**: Template management and validation +2. **MCP Tools**: Tool configuration and execution +3. **Integration**: Cross-module data flow + +### Lower Priority (Week 7-8) +1. **Performance**: Large dataset handling +2. **Accessibility**: WCAG compliance +3. **Visual**: UI consistency and regression testing + +--- + +## Mock Data Strategy (Updated) + +### Taxonomy Test Data: +```tsx +const mockTaxonomy = { + concepts: { + 'animals': { + prefLabel: 'Animals', + narrower: ['mammals', 'birds'], + topConcept: true + }, + 'mammals': { + prefLabel: 'Mammals', + broader: 'animals', + narrower: ['cats', 'dogs'] + } + }, + scheme: { + uri: 'http://example.org/taxonomy', + hasTopConcept: ['animals'] + } +}; +``` + +### Schema Test Data: +```tsx +const mockSchema = { + name: 'Customer Record', + fields: [ + { + name: 'customer_id', + type: 'string', + required: true, + primary_key: true + }, + { + name: 'status', + type: 'enum', + enum: ['active', 'inactive', 'pending'] + } + ] +}; +``` + +### Agent Test Data: +```tsx +const mockAgent = { + id: 'research-agent', + name: 'Research Assistant', + description: 'Helps with research tasks', + tools: ['web-search', 'document-reader'], + model: 'gpt-4', + temperature: 0.7 +}; +``` + +## Success Metrics + +- **Test Coverage**: Achieve and maintain 80%+ coverage +- **Test Reliability**: < 1% flaky test rate +- **Test Performance**: Test suite completes in < 5 minutes +- **Bug Detection**: Catch 90%+ of bugs before production +- **Developer Experience**: Tests provide clear feedback and are easy to maintain + +## Conclusion + +This testing strategy provides comprehensive coverage for the TrustGraph UI application, ensuring reliability, performance, and maintainability. The phased implementation approach allows for gradual adoption while maintaining development velocity. + +Regular review and updates of this strategy will ensure it remains effective as the application evolves and new features are added. \ No newline at end of file diff --git a/docs/tech-specs/collections.md b/docs/tech-specs/collections.md new file mode 100644 index 00000000..9c82a794 --- /dev/null +++ b/docs/tech-specs/collections.md @@ -0,0 +1,734 @@ +# Collections Support for TrustGraph UI + +## Overview + +This document specifies the implementation of collections support in the TrustGraph UI. Collections provide a way to organize and manage groups of documents with metadata including name, description, and tags. The feature adds collection management capabilities to the Library page through a tabbed interface. + +## API Integration + +### Backend API Summary + +The TrustGraph API provides three collection operations via the `collection-management` endpoint: + +#### 1. List Collections +- **Operation**: `list-collections` +- **Request**: + ```json + { + "operation": "list-collections", + "user": "username", + "tag_filter": ["tag1", "tag2"] // optional + } + ``` +- **Response**: Array of collection metadata objects +- **Fields**: user, collection, name, description, tags, created_at, updated_at + +#### 2. Update Collection (also creates) +- **Operation**: `update-collection` +- **Request**: + ```json + { + "operation": "update-collection", + "user": "username", + "collection": "collection-id", + "name": "Display Name", // optional + "description": "Description", // optional + "tags": ["tag1", "tag2"] // optional + } + ``` +- **Response**: Single collection metadata object in `collections` array +- **Note**: Creates collection if it doesn't exist; updates if it does + +#### 3. Delete Collection +- **Operation**: `delete-collection` +- **Request**: + ```json + { + "operation": "delete-collection", + "user": "username", + "collection": "collection-id" + } + ``` +- **Response**: Empty object `{}` + +### Collection Metadata Structure + +```typescript +interface CollectionMetadata { + user: string; + collection: string; // Collection ID (unique identifier) + name: string; // Display name + description: string; // Description text + tags: string[]; // Array of tags + created_at: string; // ISO timestamp + updated_at: string; // ISO timestamp +} +``` + +## Implementation Plan + +### Phase 1: Socket Layer Integration + +**File**: `src/api/trustgraph/trustgraph-socket.ts` + +Add collection management methods to the socket interface: + +```typescript +// Add to Socket interface +export interface Socket { + // ... existing methods ... + + // Collection management + collectionManagement: () => CollectionManagement; +} + +// New CollectionManagement interface +export interface CollectionManagement { + listCollections: ( + user: string, + tagFilter?: string[] + ) => Promise; + + updateCollection: ( + user: string, + collection: string, + name?: string, + description?: string, + tags?: string[] + ) => Promise; + + deleteCollection: ( + user: string, + collection: string + ) => Promise; +} + +// Implementation in BaseApi class +class BaseApi { + // ... existing methods ... + + collectionManagement(): CollectionManagement { + return { + listCollections: async (user, tagFilter) => { + const request = { + operation: "list-collections", + user, + ...(tagFilter && { tag_filter: tagFilter }), + }; + const response = await this.request("collection-management", request); + return response.collections || []; + }, + + updateCollection: async (user, collection, name, description, tags) => { + const request = { + operation: "update-collection", + user, + collection, + ...(name !== undefined && { name }), + ...(description !== undefined && { description }), + ...(tags !== undefined && { tags }), + }; + const response = await this.request("collection-management", request); + return response.collections[0]; + }, + + deleteCollection: async (user, collection) => { + const request = { + operation: "delete-collection", + user, + collection, + }; + await this.request("collection-management", request); + }, + }; + } +} +``` + +### Phase 2: State Management Hook + +**File**: `src/state/collections.ts` (new file) + +Create a React Query-based state management hook following the library.ts pattern: + +```typescript +import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query"; +import { useSocket, useConnectionState } from "../api/trustgraph/socket"; +import { useNotification } from "./notify"; +import { useActivity } from "./activity"; +import { useSettings } from "./settings"; + +export interface CollectionMetadata { + user: string; + collection: string; + name: string; + description: string; + tags: string[]; + created_at: string; + updated_at: string; +} + +export const useCollections = () => { + const socket = useSocket(); + const connectionState = useConnectionState(); + const queryClient = useQueryClient(); + const notify = useNotification(); + const { settings } = useSettings(); + + const isSocketReady = + connectionState?.status === "authenticated" || + connectionState?.status === "unauthenticated"; + + // Query for fetching all collections + const collectionsQuery = useQuery({ + queryKey: ["collections", settings.user], + enabled: isSocketReady && !!settings.user, + queryFn: () => { + return socket.collectionManagement().listCollections(settings.user); + }, + }); + + // Mutation for creating/updating a collection + const updateCollectionMutation = useMutation({ + mutationFn: ({ collection, name, description, tags, onSuccess }) => { + return socket + .collectionManagement() + .updateCollection( + settings.user, + collection, + name, + description, + tags + ) + .then(() => { + if (onSuccess) onSuccess(); + }); + }, + onError: (err) => { + console.log("Error:", err); + notify.error(err.toString()); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["collections"] }); + notify.success("Collection saved successfully"); + }, + }); + + // Mutation for deleting collections + const deleteCollectionsMutation = useMutation({ + mutationFn: ({ collections, onSuccess }) => { + return Promise.all( + collections.map((collection) => + socket + .collectionManagement() + .deleteCollection(settings.user, collection) + ) + ).then(() => { + if (onSuccess) onSuccess(); + }); + }, + onError: (err) => { + console.log("Error:", err); + notify.error(err.toString()); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["collections"] }); + notify.success("Collections deleted successfully"); + }, + }); + + // Activity indicators + useActivity(collectionsQuery.isLoading, "Loading collections"); + useActivity(updateCollectionMutation.isPending, "Saving collection"); + useActivity(deleteCollectionsMutation.isPending, "Deleting collections"); + + return { + // Collection data and query state + collections: collectionsQuery.data || [], + isLoading: collectionsQuery.isLoading, + isError: collectionsQuery.isError, + error: collectionsQuery.error, + + // Update/create collection operations + updateCollection: updateCollectionMutation.mutate, + isUpdating: updateCollectionMutation.isPending, + updateError: updateCollectionMutation.error, + + // Delete collection operations + deleteCollections: deleteCollectionsMutation.mutate, + isDeleting: deleteCollectionsMutation.isPending, + deleteError: deleteCollectionsMutation.error, + + // Manual refetch + refetch: collectionsQuery.refetch, + }; +}; +``` + +### Phase 3: Data Model + +**File**: `src/model/collection-table.tsx` (new file) + +Define table columns for collections display: + +```typescript +import { createColumnHelper } from "@tanstack/react-table"; +import { Tag } from "@chakra-ui/react"; +import { Checkbox } from "../components/ui/checkbox"; +import { selectionState } from "../components/common/SelectableTable"; +import { CollectionMetadata } from "../state/collections"; + +export const columnHelper = createColumnHelper(); + +export const columns = [ + // Selection column + columnHelper.display({ + id: "select", + header: ({ table }) => ( + + + + + ), + cell: ({ row }) => ( + + + + + ), + }), + + // Collection ID column + columnHelper.accessor("collection", { + header: "Collection ID", + cell: (info) => info.getValue(), + }), + + // Name column + columnHelper.accessor("name", { + header: "Name", + cell: (info) => info.getValue(), + }), + + // Description column + columnHelper.accessor("description", { + header: "Description", + cell: (info) => info.getValue(), + }), + + // Tags column + columnHelper.accessor("tags", { + header: "Tags", + cell: (info) => + info.getValue()?.map((tag) => ( + + {tag} + + )), + }), + + // Created column + columnHelper.accessor("created_at", { + header: "Created", + cell: (info) => new Date(info.getValue()).toLocaleString(), + }), + + // Updated column + columnHelper.accessor("updated_at", { + header: "Updated", + cell: (info) => new Date(info.getValue()).toLocaleString(), + }), +]; +``` + +### Phase 4: UI Components + +#### 4.1 Collections Component + +**File**: `src/components/library/Collections.tsx` (new file) + +Main collections management component following the Documents.tsx pattern: + +```typescript +import React, { useState } from "react"; +import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; + +import { columns } from "../../model/collection-table"; +import { useCollections } from "../../state/collections"; +import { useNotification } from "../../state/notify"; + +import CollectionActions from "./CollectionActions"; +import CollectionDialog from "./CollectionDialog"; +import SelectableTable from "../common/SelectableTable"; +import CollectionControls from "./CollectionControls"; + +const Collections = () => { + const [dialogOpen, setDialogOpen] = useState(false); + const [editingCollection, setEditingCollection] = useState(null); + + const notify = useNotification(); + const collectionsState = useCollections(); + + const collections = collectionsState.collections || []; + + const table = useReactTable({ + data: collections, + columns: columns, + getCoreRowModel: getCoreRowModel(), + }); + + const selected = table.getSelectedRowModel().rows.map((x) => x.original.collection); + + const onCreateNew = () => { + setEditingCollection(null); + setDialogOpen(true); + }; + + const onEdit = () => { + if (selected.length !== 1) { + notify.info("Please select exactly one collection to edit"); + return; + } + const collection = collections.find(c => c.collection === selected[0]); + setEditingCollection(collection); + setDialogOpen(true); + }; + + const onDelete = () => { + collectionsState.deleteCollections({ + collections: selected, + onSuccess: () => { + table.setRowSelection({}); + }, + }); + }; + + const onSaveCollection = (collection, name, description, tags) => { + collectionsState.updateCollection({ + collection, + name, + description, + tags, + onSuccess: () => { + setDialogOpen(false); + table.setRowSelection({}); + }, + }); + }; + + return ( + <> + + + + + + + + + ); +}; + +export default Collections; +``` + +#### 4.2 Collection Actions Bar + +**File**: `src/components/library/CollectionActions.tsx` (new file) + +Action buttons for bulk operations on selected collections: + +```typescript +import React from "react"; +import { HStack, Button, Text } from "@chakra-ui/react"; +import { Edit, Trash2 } from "lucide-react"; + +interface CollectionActionsProps { + selectedCount: number; + onEdit: () => void; + onDelete: () => void; +} + +const CollectionActions = ({ selectedCount, onEdit, onDelete }: CollectionActionsProps) => { + if (selectedCount === 0) return null; + + return ( + + + {selectedCount} collection{selectedCount !== 1 ? "s" : ""} selected + + + + + ); +}; + +export default CollectionActions; +``` + +#### 4.3 Collection Dialog + +**File**: `src/components/library/CollectionDialog.tsx` (new file) + +Dialog for creating/editing collections: + +```typescript +import React, { useState, useEffect } from "react"; +import { Dialog } from "@chakra-ui/react"; +import { Portal } from "@chakra-ui/react"; + +import TextField from "../common/TextField"; +import TextAreaField from "../common/TextAreaField"; +import ChipInputField from "../common/ChipInputField"; +import ProgressSubmitButton from "../common/ProgressSubmitButton"; + +interface CollectionDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSave: (collection: string, name: string, description: string, tags: string[]) => void; + editingCollection?: any; +} + +const CollectionDialog = ({ + open, + onOpenChange, + onSave, + editingCollection, +}: CollectionDialogProps) => { + const [collection, setCollection] = useState(""); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [tags, setTags] = useState([]); + + useEffect(() => { + if (editingCollection) { + setCollection(editingCollection.collection); + setName(editingCollection.name); + setDescription(editingCollection.description); + setTags(editingCollection.tags || []); + } else { + setCollection(""); + setName(""); + setDescription(""); + setTags([]); + } + }, [editingCollection, open]); + + const handleSubmit = () => { + onSave(collection, name, description, tags); + }; + + const isValid = collection.trim() !== "" && name.trim() !== ""; + + return ( + onOpenChange(e.open)}> + + + + + + + {editingCollection ? "Edit Collection" : "Create Collection"} + + + + + + + + + + + + {editingCollection ? "Update" : "Create"} + + + + + + + ); +}; + +export default CollectionDialog; +``` + +#### 4.4 Collection Controls + +**File**: `src/components/library/CollectionControls.tsx` (new file) + +Control buttons for collection operations: + +```typescript +import React from "react"; +import { HStack, Button } from "@chakra-ui/react"; +import { Plus } from "lucide-react"; + +interface CollectionControlsProps { + onCreate: () => void; +} + +const CollectionControls = ({ onCreate }: CollectionControlsProps) => { + return ( + + + + ); +}; + +export default CollectionControls; +``` + +### Phase 5: Update Library Page with Tabs + +**File**: `src/pages/LibraryPage.tsx` + +Update the library page to use tabs for Documents and Collections: + +```typescript +import React from "react"; +import { LibraryBig } from "lucide-react"; +import { Tabs } from "@chakra-ui/react"; + +import PageHeader from "../components/common/PageHeader"; +import Documents from "../components/library/Documents"; +import Collections from "../components/library/Collections"; + +const LibraryPage = () => { + return ( + <> + } + title="Library" + description="Managing documents and collections" + /> + + + Documents + Collections + + + + + + + + + + ); +}; + +export default LibraryPage; +``` + +## Type Definitions + +**File**: `src/api/trustgraph/messages.ts` + +Add collection-related message types: + +```typescript +// Collection management request +export interface CollectionRequest extends RequestMessage { + operation: "list-collections" | "update-collection" | "delete-collection"; + user: string; + collection?: string; + name?: string; + description?: string; + tags?: string[]; + tag_filter?: string[]; +} + +// Collection management response +export interface CollectionResponse { + collections?: Array<{ + user: string; + collection: string; + name: string; + description: string; + tags: string[]; + created_at: string; + updated_at: string; + }>; +} +``` + +## Testing Checklist + +- [ ] List collections displays all collections for the user +- [ ] Tag filter works when listing collections +- [ ] Create new collection with ID, name, description, and tags +- [ ] Edit existing collection (ID is disabled, other fields editable) +- [ ] Delete single collection +- [ ] Delete multiple collections +- [ ] Selection state persists correctly +- [ ] Loading indicators show during operations +- [ ] Error notifications display for failures +- [ ] Success notifications display for completed operations +- [ ] Tab switching preserves state +- [ ] Collections table sorts correctly +- [ ] Empty state displays when no collections exist +- [ ] Validation prevents creating collections with empty ID or name + +## Future Enhancements + +1. **Collection Assignment**: Allow assigning documents to collections from the Documents tab +2. **Collection Filtering**: Filter documents by collection +3. **Bulk Collection Operations**: Move multiple documents between collections +4. **Collection Statistics**: Show document count per collection +5. **Collection Search**: Search collections by name, description, or tags +6. **Collection Export**: Export collection metadata + +## Migration Notes + +- No breaking changes to existing functionality +- Documents tab functionality remains unchanged +- New collections functionality is additive +- Follows established patterns from library.ts and Documents.tsx +- Uses consistent Chakra v3 components and patterns diff --git a/docs/tech-specs/flow-class-definition.md b/docs/tech-specs/flow-class-definition.md new file mode 100644 index 00000000..5469144e --- /dev/null +++ b/docs/tech-specs/flow-class-definition.md @@ -0,0 +1,156 @@ +# Flow Class Definition Specification + +## Overview + +A flow class defines a complete dataflow pattern template in the TrustGraph system. When instantiated, it creates an interconnected network of processors that handle data ingestion, processing, storage, and querying as a unified system. + +## Structure + +A flow class definition consists of four main sections: + +### 1. Class Section +Defines shared service processors that are instantiated once per flow class. These processors handle requests from all flow instances of this class. + +```json +"class": { + "service-name:{class}": { + "request": "queue-pattern:{class}", + "response": "queue-pattern:{class}" + } +} +``` + +**Characteristics:** +- Shared across all flow instances of the same class +- Typically expensive or stateless services (LLMs, embedding models) +- Use `{class}` template variable for queue naming +- Examples: `embeddings:{class}`, `text-completion:{class}`, `graph-rag:{class}` + +### 2. Flow Section +Defines flow-specific processors that are instantiated for each individual flow instance. Each flow gets its own isolated set of these processors. + +```json +"flow": { + "processor-name:{id}": { + "input": "queue-pattern:{id}", + "output": "queue-pattern:{id}" + } +} +``` + +**Characteristics:** +- Unique instance per flow +- Handle flow-specific data and state +- Use `{id}` template variable for queue naming +- Examples: `chunker:{id}`, `pdf-decoder:{id}`, `kg-extract-relationships:{id}` + +### 3. Interfaces Section +Defines the entry points and interaction contracts for the flow. These form the API surface for external systems and internal component communication. + +Interfaces can take two forms: + +**Fire-and-Forget Pattern** (single queue): +```json +"interfaces": { + "document-load": "persistent://tg/flow/document-load:{id}", + "triples-store": "persistent://tg/flow/triples-store:{id}" +} +``` + +**Request/Response Pattern** (object with request/response fields): +```json +"interfaces": { + "embeddings": { + "request": "non-persistent://tg/request/embeddings:{class}", + "response": "non-persistent://tg/response/embeddings:{class}" + } +} +``` + +**Types of Interfaces:** +- **Entry Points**: Where external systems inject data (`document-load`, `agent`) +- **Service Interfaces**: Request/response patterns for services (`embeddings`, `text-completion`) +- **Data Interfaces**: Fire-and-forget data flow connection points (`triples-store`, `entity-contexts-load`) + +### 4. Metadata +Additional information about the flow class: + +```json +"description": "Human-readable description", +"tags": ["capability-1", "capability-2"] +``` + +## Template Variables + +### {id} +- Replaced with the unique flow instance identifier +- Creates isolated resources for each flow +- Example: `flow-123`, `customer-A-flow` + +### {class} +- Replaced with the flow class name +- Creates shared resources across flows of the same class +- Example: `standard-rag`, `enterprise-rag` + +## Queue Patterns (Pulsar) + +Flow classes use Apache Pulsar for messaging. Queue names follow the Pulsar format: +``` +://// +``` + +### Components: +- **persistence**: `persistent` or `non-persistent` (Pulsar persistence mode) +- **tenant**: `tg` for TrustGraph-supplied flow class definitions +- **namespace**: Indicates the messaging pattern + - `flow`: Fire-and-forget services + - `request`: Request portion of request/response services + - `response`: Response portion of request/response services +- **topic**: The specific queue/topic name with template variables + +### Persistent Queues +- Pattern: `persistent://tg/flow/:{id}` +- Used for fire-and-forget services and durable data flow +- Data persists in Pulsar storage across restarts +- Example: `persistent://tg/flow/chunk-load:{id}` + +### Non-Persistent Queues +- Pattern: `non-persistent://tg/request/:{class}` or `non-persistent://tg/response/:{class}` +- Used for request/response messaging patterns +- Ephemeral, not persisted to disk by Pulsar +- Lower latency, suitable for RPC-style communication +- Example: `non-persistent://tg/request/embeddings:{class}` + +## Dataflow Architecture + +The flow class creates a unified dataflow where: + +1. **Document Processing Pipeline**: Flows from ingestion through transformation to storage +2. **Query Services**: Integrated processors that query the same data stores and services +3. **Shared Services**: Centralized processors that all flows can utilize +4. **Storage Writers**: Persist processed data to appropriate stores + +All processors (both `{id}` and `{class}`) work together as a cohesive dataflow graph, not as separate systems. + +## Example Flow Instantiation + +Given: +- Flow Instance ID: `customer-A-flow` +- Flow Class: `standard-rag` + +Template expansions: +- `persistent://tg/flow/chunk-load:{id}` → `persistent://tg/flow/chunk-load:customer-A-flow` +- `non-persistent://tg/request/embeddings:{class}` → `non-persistent://tg/request/embeddings:standard-rag` + +This creates: +- Isolated document processing pipeline for `customer-A-flow` +- Shared embedding service for all `standard-rag` flows +- Complete dataflow from document ingestion through querying + +## Benefits + +1. **Resource Efficiency**: Expensive services are shared across flows +2. **Flow Isolation**: Each flow has its own data processing pipeline +3. **Scalability**: Can instantiate multiple flows from the same template +4. **Modularity**: Clear separation between shared and flow-specific components +5. **Unified Architecture**: Query and processing are part of the same dataflow \ No newline at end of file diff --git a/docs/tech-specs/flow-class-editor.md b/docs/tech-specs/flow-class-editor.md new file mode 100644 index 00000000..1238432a --- /dev/null +++ b/docs/tech-specs/flow-class-editor.md @@ -0,0 +1,910 @@ +# Flow Class Visual Editor Technical Specification + +## Overview + +A React-based visual editor for creating and modifying TrustGraph flow class definitions using a node-and-edge graph interface. Built with React Flow, this component allows users to visually design dataflow patterns by dragging processors onto a canvas and connecting them with queues. + +## Core Technologies + +- **React Flow** - Node-based editor framework +- **TypeScript** - Type safety for flow definitions +- **Chakra UI v3** - UI components and theming +- **Zustand** - Editor state management +- **Zod** - Schema validation for flow class structure + +## Component Architecture + +### Directory Structure +``` +src/components/flow-editor/ +├── FlowClassEditor.tsx # Main editor component +├── nodes/ # Custom node components +│ ├── ClassProcessorNode.tsx # Shared service nodes +│ ├── FlowProcessorNode.tsx # Flow-specific nodes +│ └── InterfaceNode.tsx # Entry/exit point nodes +├── edges/ # Custom edge components +│ ├── PersistentQueueEdge.tsx # Persistent queue connections +│ └── RequestResponseEdge.tsx # Request/response pairs +├── panels/ # Editor UI panels +│ ├── NodePalette.tsx # Drag-and-drop processor library +│ ├── PropertiesPanel.tsx # Node/edge configuration +│ └── ValidationPanel.tsx # Real-time validation feedback +├── hooks/ +│ ├── useFlowValidation.ts # Validation logic +│ ├── useFlowExport.ts # JSON export/import +│ └── useAutoLayout.ts # Automatic graph layout +└── types/ + └── flowEditorTypes.ts # TypeScript definitions +``` + +## Visual Design + +### Node Types + +#### 1. Class Processor Node ({class}) +```tsx +{ + type: 'classProcessor', + data: { + processorName: string, // e.g., "embeddings" + queues: { + request?: string, // Queue pattern + response?: string, // Queue pattern + [key: string]: string // Additional queues + } + }, + style: { + background: 'accent.subtle', // Shared service color + border: '2px solid accent.solid', + icon: // Lucide icon indicating shared + } +} +``` + +#### 2. Flow Processor Node ({id}) +```tsx +{ + type: 'flowProcessor', + data: { + processorName: string, // e.g., "chunker" + queues: { + input?: string, // Input queue + output?: string, // Output queue + [key: string]: string // Additional queues + } + }, + style: { + background: 'primary.subtle', // Flow-specific color + border: '2px solid primary.solid', + icon: // Lucide icon for isolated + } +} +``` + +#### 3. Interface Node +```tsx +{ + type: 'interfaceNode', + data: { + interfaceName: string, // e.g., "document-load" + interfaceType: 'fire-and-forget' | 'request-response', + queuePattern?: string, // For fire-and-forget + request?: string, // For request-response + response?: string // For request-response + }, + style: { + background: 'bg.muted', + border: '2px dashed border.muted', + icon: // Entry/exit point indicator + } +} +``` + +### Edge Types + +#### 1. Persistent Queue Edge +- **Visual**: Solid line with arrow +- **Color**: Based on namespace (flow: green, request: blue, response: purple) +- **Label**: Queue name displayed on hover +- **Validation**: Source/target compatibility checking + +#### 2. Non-Persistent Queue Edge +- **Visual**: Dashed line with arrow +- **Color**: Lighter variant of namespace colors +- **Label**: Queue name with non-persistent indicator +- **Validation**: Request/response pairing validation + +## User Edit Operations + +### 1. Processor Management + +#### Add Processor +- **Drag from palette**: Drag processor type from categorized library +- **Double-click canvas**: Quick-add with processor type selector +- **Context menu**: Right-click → Add Processor → Select type +- **Keyboard shortcut**: `A` key opens add processor dialog + +#### Configure Processor +- **Rename**: Click processor name to edit inline +- **Change type**: Toggle between `{class}` and `{id}` via properties panel +- **Queue management**: + ```tsx + // Add new queue to processor + addQueue(processorId, { + name: "custom-queue", + direction: "input" | "output" | "bidirectional", + pattern: "persistent://tg/flow/custom:{id}" + }); + + // Remove queue + removeQueue(processorId, queueName); + + // Edit queue pattern + updateQueue(processorId, queueName, newPattern); + ``` + +#### Delete Processor +- **Single**: Select + Delete key +- **Multiple**: Multi-select + Delete key +- **Context menu**: Right-click → Delete +- **Validation**: Warn if processor has connections + +### 2. Connection Management + +#### Create Connection +- **Drag connection**: From output handle to input handle +- **Validation rules**: + - Persistence compatibility (persistent ↔ persistent preferred) + - Namespace compatibility (flow/request/response) + - Template variable consistency ({class} ↔ {class}, {id} ↔ {id}) + - No self-connections + - No duplicate connections + +#### Configure Connection +- **Auto-naming**: Generate queue name from source/target processors +- **Custom naming**: Override auto-generated queue name +- **Persistence mode**: Toggle persistent/non-persistent +- **Queue pattern template**: + ```tsx + generateQueuePattern({ + persistence: "persistent" | "non-persistent", + tenant: "tg", + namespace: "flow" | "request" | "response", + topic: "document-embeddings", + template: "{id}" | "{class}" + }); + // Result: "persistent://tg/flow/document-embeddings:{id}" + ``` + +#### Delete Connection +- **Click to select** + Delete key +- **Context menu** on edge +- **Disconnect handle**: Drag connection away from handle + +### 3. Interface Operations + +#### Add Interface +- **Entry points**: Document load, text input, etc. +- **Exit points**: Response outputs, storage endpoints +- **Service interfaces**: Request/response pairs + +#### Configure Interface Type +```tsx +// Fire-and-forget pattern +interface FireAndForgetInterface { + type: "fire-and-forget"; + queue: string; // Single queue pattern +} + +// Request/response pattern +interface RequestResponseInterface { + type: "request-response"; + request: string; // Request queue pattern + response: string; // Response queue pattern +} +``` + +### 4. Bulk Operations + +#### Multi-select Actions +- **Box select**: Click and drag to select multiple nodes +- **Shift-click**: Add to selection +- **Cmd-click**: Toggle selection +- **Select all**: Cmd+A + +#### Group Operations +- **Move together**: Drag any selected node moves all +- **Delete together**: Delete key removes all selected +- **Duplicate**: Cmd+D duplicates selection +- **Copy/paste**: Cmd+C/Cmd+V for cross-flow copying + +### 5. Layout Operations + +#### Auto-layout +```tsx +const layoutStrategies = { + hierarchical: { + direction: "LR" | "TB", // Left-right or top-bottom + nodeSpacing: 150, + levelSpacing: 200 + }, + force: { + strength: -1000, + distance: 150 + }, + circular: { + radius: 300, + startAngle: 0 + } +}; +``` + +#### Manual Arrangement +- **Snap to grid**: Optional grid snapping (toggle with G key) +- **Alignment tools**: Align selected nodes (top/bottom/left/right/center) +- **Distribution**: Distribute nodes evenly (horizontal/vertical) + +### 6. Template Operations + +#### Apply Template +```tsx +const templates = { + "document-rag": { + description: "Document processing with RAG", + processors: [ + { type: "pdf-decoder", id: "{id}" }, + { type: "chunker", id: "{id}" }, + { type: "embeddings", id: "{class}" }, + { type: "de-write", id: "{id}" } + ], + connections: [ + { from: "pdf-decoder.output", to: "chunker.input" }, + { from: "chunker.output", to: "embeddings.input" }, + { from: "embeddings.output", to: "de-write.input" } + ] + } +}; +``` + +#### Create Template +- Select nodes/edges → Right-click → "Save as template" +- Provide template name and description +- Template saved to library for reuse + +### 7. Validation Operations + +#### Real-time Validation +```tsx +interface ValidationRule { + id: string; + severity: "error" | "warning" | "info"; + check: (flowClass: FlowClass) => ValidationResult; +} + +const validationRules = [ + { + id: "no-orphans", + severity: "warning", + check: (flow) => findOrphanedNodes(flow) + }, + { + id: "queue-consistency", + severity: "error", + check: (flow) => validateQueuePatterns(flow) + }, + { + id: "template-consistency", + severity: "error", + check: (flow) => validateTemplateVariables(flow) + } +]; +``` + +#### Fix Suggestions +- **Auto-fix**: One-click fixes for common issues +- **Quick actions**: Context-aware suggestions +- **Validation overlay**: Visual indicators on invalid elements + +### 8. History Management + +#### Undo/Redo Stack +```tsx +interface HistoryAction { + type: "add" | "delete" | "update" | "connect" | "disconnect"; + before: FlowState; + after: FlowState; + timestamp: number; +} + +const historyStack: HistoryAction[] = []; +const redoStack: HistoryAction[] = []; + +// Track all operations +const executeOperation = (operation: Operation) => { + const before = getCurrentState(); + performOperation(operation); + const after = getCurrentState(); + + historyStack.push({ + type: operation.type, + before, + after, + timestamp: Date.now() + }); + + redoStack.length = 0; // Clear redo on new operation +}; +``` + +### 9. Import/Export Operations + +#### Import Flow Class +- **From JSON file**: Upload or paste JSON +- **From Config API**: Select from existing flow classes +- **Validation**: Verify structure before import +- **Merge options**: Replace or merge with existing + +#### Export Flow Class +- **To JSON**: Download as .json file +- **To Config API**: Save directly to backend +- **To clipboard**: Copy JSON for sharing +- **Format options**: Minified or pretty-printed + +### 10. Metadata Operations + +#### Edit Flow Properties +```tsx +interface FlowMetadata { + id: string; // Kebab-case identifier + name: string; // Human-readable name + description: string; // Detailed description + tags: string[]; // Categorization tags + version: string; // Semantic version + author: string; // Creator identity + created: Date; // Creation timestamp + modified: Date; // Last modification +} +``` + +#### Tag Management +- **Add tags**: Type or select from existing +- **Remove tags**: Click X on tag chips +- **Tag suggestions**: Based on processors used +- **Tag categories**: System tags vs user tags + +## Core Features + +### 1. Auto-Layout +```tsx +const handleAutoLayout = () => { + const layoutedElements = getLayoutedElements(nodes, edges, { + direction: 'LR', // Left to right + nodeSpacing: 150, + levelSpacing: 200, + animate: true + }); + setNodes(layoutedElements.nodes); + setEdges(layoutedElements.edges); +}; +``` + +### 2. Import/Export +```tsx +// Export to flow class JSON +const exportFlowClass = () => { + const flowClass = { + class: extractClassProcessors(nodes), + flow: extractFlowProcessors(nodes), + interfaces: extractInterfaces(nodes), + description: metadata.description, + tags: metadata.tags + }; + return JSON.stringify(flowClass, null, 2); +}; + +// Import from JSON +const importFlowClass = (json: string) => { + const flowClass = JSON.parse(json); + const { nodes, edges } = convertToReactFlow(flowClass); + setNodes(nodes); + setEdges(edges); +}; +``` + +### 3. Connection Validation +```tsx +const isValidConnection = (connection: Connection) => { + const sourceNode = getNode(connection.source); + const targetNode = getNode(connection.target); + + // Validate queue compatibility + if (!areQueuesCompatible(sourceNode, targetNode)) { + return false; + } + + // Prevent circular dependencies + if (createsCircularDependency(connection)) { + return false; + } + + return true; +}; +``` + +### 4. Smart Templates +Pre-built flow patterns users can instantiate: +- **Document RAG Pipeline**: PDF → Chunker → Embeddings → Storage +- **Graph RAG Pipeline**: Knowledge extraction → Graph embeddings → Query +- **Simple Q&A**: Prompt → LLM → Response +- **Custom Template**: User-defined reusable patterns + +## State Management + +```tsx +interface FlowEditorState { + // React Flow state + nodes: Node[]; + edges: Edge[]; + + // Editor state + selectedElement: Node | Edge | null; + validationErrors: ValidationError[]; + isDirty: boolean; + + // Metadata + flowClassName: string; + description: string; + tags: string[]; + + // Actions + addNode: (type: NodeType, position: XYPosition) => void; + updateNode: (nodeId: string, data: NodeData) => void; + deleteNode: (nodeId: string) => void; + addEdge: (edge: Edge) => void; + deleteEdge: (edgeId: string) => void; + validateFlow: () => ValidationResult; + exportFlow: () => FlowClassDefinition; + importFlow: (definition: FlowClassDefinition) => void; +} +``` + +## Visual Indicators + +### Node States +- **Normal**: Default appearance +- **Selected**: Blue glow/border +- **Invalid**: Red border with error icon +- **Connecting**: Pulse animation on handles +- **Hover**: Slight scale increase + +### Edge States +- **Normal**: Default appearance +- **Selected**: Highlighted with thicker stroke +- **Invalid**: Red dashed line +- **Animated**: Flow animation for active connections + +### Queue Handle Types +- **Input**: Left side of node, inward arrow +- **Output**: Right side of node, outward arrow +- **Bidirectional**: Both sides, for request/response + +## Keyboard Shortcuts + +- `Delete` - Delete selected elements +- `Cmd+Z` - Undo +- `Cmd+Shift+Z` - Redo +- `Cmd+S` - Save flow class +- `Cmd+O` - Open flow class +- `Cmd+E` - Export to JSON +- `Space` - Pan mode +- `Cmd+A` - Select all nodes +- `Cmd+D` - Duplicate selected nodes + +## Integration Points + +### Config API Integration + +The flow class editor uses the existing Config API for all flow class operations: + +#### State Management Hook +```tsx +// src/state/flow-classes.ts +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useSocket } from "./socket"; + +export const useFlowClasses = () => { + const socket = useSocket(); + + return useQuery({ + queryKey: ["flow-classes"], + queryFn: async () => { + const response = await socket.request({ + operation: "get-config", + path: "flow-classes" + }); + return response.configuration as FlowClassDefinition[]; + } + }); +}; + +export const useFlowClass = (flowClassId: string) => { + const socket = useSocket(); + + return useQuery({ + queryKey: ["flow-class", flowClassId], + queryFn: async () => { + const response = await socket.request({ + operation: "get-config", + path: `flow-classes/${flowClassId}` + }); + return response.configuration as FlowClassDefinition; + } + }); +}; + +export const useUpdateFlowClass = () => { + const socket = useSocket(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ id, flowClass }: { + id: string; + flowClass: FlowClassDefinition + }) => { + return await socket.request({ + operation: "set-config", + path: `flow-classes/${id}`, + configuration: flowClass + }); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries(["flow-class", variables.id]); + queryClient.invalidateQueries(["flow-classes"]); + } + }); +}; + +export const useDeleteFlowClass = () => { + const socket = useSocket(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (id: string) => { + return await socket.request({ + operation: "delete-config", + path: `flow-classes/${id}` + }); + }, + onSuccess: () => { + queryClient.invalidateQueries(["flow-classes"]); + } + }); +}; +``` + +#### Editor Component Integration +```tsx +// src/components/flow-editor/FlowClassEditor.tsx +import { useFlowClass, useUpdateFlowClass } from "../../state/flow-classes"; +import { useActivity } from "../../state/activity"; +import { useNotification } from "../../state/notify"; + +export const FlowClassEditor = ({ flowClassId }: { flowClassId?: string }) => { + const notify = useNotification(); + + // Load existing flow class if ID provided + const { data: flowClass, isLoading } = useFlowClass(flowClassId); + const updateMutation = useUpdateFlowClass(); + + // Track loading state + useActivity(isLoading, "Loading flow class"); + useActivity(updateMutation.isPending, "Saving flow class"); + + // Initialize React Flow with loaded data + useEffect(() => { + if (flowClass) { + const { nodes, edges } = convertFlowClassToReactFlow(flowClass); + setNodes(nodes); + setEdges(edges); + setMetadata({ + description: flowClass.description, + tags: flowClass.tags + }); + } + }, [flowClass]); + + // Save handler + const handleSave = async () => { + const flowClassData = exportFlowClass(); + + try { + await updateMutation.mutateAsync({ + id: flowClassId || generateFlowClassId(), + flowClass: flowClassData + }); + notify.success("Flow class saved successfully"); + } catch (error) { + notify.error("Failed to save flow class"); + } + }; + + return ( + + ); +}; +``` + +#### Flow Class List Integration +```tsx +// src/components/flow-editor/FlowClassList.tsx +import { useFlowClasses, useDeleteFlowClass } from "../../state/flow-classes"; + +export const FlowClassList = () => { + const { data: flowClasses, isLoading } = useFlowClasses(); + const deleteMutation = useDeleteFlowClass(); + + useActivity(isLoading, "Loading flow classes"); + useActivity(deleteMutation.isPending, "Deleting flow class"); + + return ( + + {flowClasses?.map(flowClass => ( + + + {flowClass.description} + + + + + ))} + + ); +}; +``` + +#### Config API Request/Response Format +```typescript +// Request to get all flow classes +{ + operation: "get-config", + path: "flow-classes" +} + +// Response +{ + configuration: [ + { + id: "document-rag-flow", + class: { /* class processors */ }, + flow: { /* flow processors */ }, + interfaces: { /* interfaces */ }, + description: "Document RAG pipeline", + tags: ["rag", "documents"] + } + ] +} + +// Request to update flow class +{ + operation: "set-config", + path: "flow-classes/document-rag-flow", + configuration: { + class: { /* updated class processors */ }, + flow: { /* updated flow processors */ }, + interfaces: { /* updated interfaces */ }, + description: "Updated description", + tags: ["rag", "documents", "v2"] + } +} +``` + +### Real-time Updates via WebSocket + +The editor subscribes to configuration changes to handle external updates: + +```tsx +useEffect(() => { + const subscription = socket.subscribe( + `config/flow-classes/${flowClassId}`, + (update) => { + // Handle external updates to the flow class + if (update.source !== currentSessionId) { + notify.warning("Flow class updated externally. Refreshing..."); + queryClient.invalidateQueries(["flow-class", flowClassId]); + } + } + ); + + return () => subscription.unsubscribe(); +}, [flowClassId]); +``` + +### With Existing UI + +#### Page Integration +The Flow Class Editor is a separate page in the workbench, controlled by a feature toggle: + +```tsx +// src/components/settings/FeatureSwitchesSection.tsx +// Add to existing feature switches: + + + Flow Class Editor + + + Enable the visual flow class editor for creating and modifying dataflow patterns + + + experimental + + + + + onFlowClassEditorChange(details.checked) + } + > + + + + + + +``` + +#### Sidebar Navigation +```tsx +// src/components/Sidebar.tsx +// Add conditional menu item based on feature switch: +{settings.featureSwitches.flowClassEditor && ( + } + label="Flow Class Editor" + path="/flow-class-editor" + isActive={location.pathname === "/flow-class-editor"} + /> +)} +``` + +#### Route Configuration +```tsx +// src/App.tsx +// Add route for the editor page: +{settings.featureSwitches.flowClassEditor && ( + } /> +)} +``` + +#### Page Component +```tsx +// src/pages/FlowClassEditorPage.tsx +import React from "react"; +import PageHeader from "../components/common/PageHeader"; +import FlowClassEditor from "../components/flow-editor/FlowClassEditor"; +import { GitBranch } from "lucide-react"; + +const FlowClassEditorPage: React.FC = () => { + return ( + <> + } + title="Flow Class Editor" + description="Visual editor for creating and modifying TrustGraph dataflow patterns" + /> + + + ); +}; + +export default FlowClassEditorPage; +``` + +#### Settings State Update +```tsx +// src/state/settings.ts +interface FeatureSwitches { + ontologyEditor: boolean; + submissions: boolean; + agentTools: boolean; + mcpTools: boolean; + schemas: boolean; + tokenCost: boolean; + flowClasses: boolean; // Existing flow classes management + flowClassEditor: boolean; // New visual editor + structuredQuery: boolean; +} +``` + +#### Integration Features +- Uses consistent Chakra UI theming +- Integrates with notification system via `useNotification` +- Progress indicators via `useActivity` +- Follows existing Config API patterns +- Respects user's feature toggle preferences + +## Responsive Design + +### Desktop (Primary) +- Full editor with all panels visible +- Optimal canvas size for complex flows +- Properties panel as sidebar + +### Tablet +- Collapsible panels to maximize canvas +- Touch-friendly node manipulation +- Simplified toolbar + +### Mobile (View-only) +- Read-only flow visualization +- Pan and zoom only +- Export functionality retained + +## Performance Considerations + +### Optimizations +- **Virtualization** for large flows (100+ nodes) +- **Debounced validation** during editing +- **Memoized node/edge components** +- **Lazy loading** of processor templates +- **Web Workers** for layout calculations + +### Limits +- Max 500 nodes per flow class +- Max 1000 edges per flow class +- Auto-layout for flows under 100 nodes +- Real-time validation for flows under 50 nodes + +## Error Handling + +### Validation Errors +- Inline error indicators on invalid nodes/edges +- Validation panel with detailed error list +- Prevent export/save when errors exist + +### Runtime Errors +- Connection rejection with toast notification +- Import failure with detailed parsing errors +- Auto-save recovery for browser crashes + +## Future Enhancements + +### Phase 2 +- **Processor library management** - Add custom processors +- **Collaborative editing** - Real-time multi-user support +- **Version control** - Flow class versioning and diff view +- **Simulation mode** - Visualize data flow through the graph + +### Phase 3 +- **AI assistance** - Suggest connections and optimizations +- **Performance profiling** - Visualize bottlenecks +- **Template marketplace** - Share flow patterns +- **Code generation** - Generate processor stubs from flow + +## Testing Strategy + +### Unit Tests +- Node/edge component rendering +- Validation logic +- Import/export transformations +- State management actions + +### Integration Tests +- Full editor workflow +- Save/load operations +- Template instantiation +- Keyboard shortcuts + +### E2E Tests +- Create flow from scratch +- Import and modify existing flow +- Export and validate JSON +- Deploy flow instance \ No newline at end of file diff --git a/docs/tech-specs/flow-configurable-parameters-client.md b/docs/tech-specs/flow-configurable-parameters-client.md new file mode 100644 index 00000000..6f00100b --- /dev/null +++ b/docs/tech-specs/flow-configurable-parameters-client.md @@ -0,0 +1,1048 @@ +# Flow Configurable Parameters - Client-Side Technical Specification + +## Overview + +This specification describes the client-side implementation of configurable parameters for flow classes in TrustGraph UI. This complements the server-side implementation by providing dynamic form generation, parameter validation, and user-friendly parameter input interfaces for flow creation. + +The client-side implementation enables users to: +- View available parameters when selecting a flow class +- Input parameter values through dynamically generated forms +- Validate parameters according to their schema definitions +- Launch flows with custom parameter configurations +- Save and reuse parameter presets for common configurations + +## Goals + +- **Dynamic Form Generation**: Automatically create parameter input forms based on flow class parameter schemas +- **Type-Safe Validation**: Validate parameter inputs according to schema definitions (string, number, boolean, enum) +- **Intuitive User Experience**: Provide clear parameter descriptions, validation feedback, and sensible defaults +- **Integration with Existing UI**: Seamlessly integrate with the current CreateDialog and flow management system +- **Parameter Presets**: Allow users to save and reuse common parameter configurations +- **Real-time Feedback**: Show validation errors and hints as users input parameters +- **Accessibility**: Ensure parameter forms are accessible and follow project UI patterns + +## Architecture + +### Component Structure + +``` +src/components/flows/ +├── CreateDialog.tsx # Enhanced with parameter support +├── ParameterInputs.tsx # Dynamic parameter form component +├── ParameterPresets.tsx # Parameter preset management +├── ParameterValidation.tsx # Validation logic and error display +└── __tests__/ + ├── ParameterInputs.test.tsx + └── ParameterPresets.test.tsx + +src/state/ +├── flows.ts # Enhanced with parameter support +└── flow-parameters.ts # Parameter definition fetching and caching + +src/model/ +└── flow-parameters.ts # Parameter type definitions and utilities +``` + +### Data Flow + +1. **Parameter Schema Fetching**: + ``` + User selects flow class → Fetch parameter definitions → Parse schema → Generate form + ``` + +2. **Parameter Input**: + ``` + User inputs values → Validate against schema → Update form state → Enable/disable submit + ``` + +3. **Flow Creation**: + ``` + User submits → Validate all parameters → Send to API → Create flow with parameters + ``` + +## Technical Design + +### Core Types + +```typescript +// Parameter schema definition (from server) +interface ParameterSchema { + type: 'string' | 'number' | 'integer' | 'boolean'; + description?: string; + default?: any; + enum?: EnumOption[] | string[]; // Can be rich objects or simple strings + minimum?: number; + maximum?: number; + pattern?: string; + required?: boolean; + helper?: string; // Custom helper text + placeholder?: string; // Custom placeholder text +} + +// Rich enum option structure +interface EnumOption { + id: string; // The actual value + description: string; // Display text +} + +// Flow class structure (from getFlowClass API) +interface FlowClass { + class: { [processorName: string]: any }; // Processor definitions + description: string; + flow: { [stepName: string]: any }; // Flow step definitions + interfaces: { [interfaceName: string]: any }; // Interface definitions + parameters?: { [flowParamName: string]: FlowParameterMetadata }; // Maps flow param names to parameter metadata + tags?: string[]; +} + +// Flow parameter metadata structure +interface FlowParameterMetadata { + type: string; // Reference to parameter-type definition name + description: string; // Human-readable description for UI display + order: number; // Display order for parameter forms (lower numbers appear first) +} + +// Parameter definitions fetched from config +interface ParameterDefinitions { + [definitionName: string]: ParameterSchema; +} + +// User parameter values +interface ParameterValues { + [flowParamName: string]: any; +} + +// Parameter validation result +interface ValidationResult { + isValid: boolean; + errors: { [paramName: string]: string }; +} +``` + +### Component Implementation + +#### 1. Enhanced CreateDialog + +```typescript +// Key enhancements to existing CreateDialog +const CreateDialog = ({ open, onOpenChange }) => { + const [flowClass, setFlowClass] = useState(); + const [id, setId] = useState(""); + const [description, setDescription] = useState(""); + const [parameterValues, setParameterValues] = useState({}); + + // Fetch parameter definitions when flow class is selected + const { + parameterDefinitions, + isLoadingParameters + } = useFlowParameters(flowClass); + + // Validate form including parameters + const { isValid, errors } = useParameterValidation( + flowClass, + parameterDefinitions, + parameterValues + ); + + const onSubmit = () => { + if (!isValid) return; + + flowState.startFlow({ + id, + flowClass, + description, + parameters: parameterValues, // Include parameters + onSuccess: () => { + setParameterValues({}); // Clear parameters on success + // ... rest of success logic + }, + }); + }; + + return ( + + {/* Existing dialog structure */} + + {/* Enhanced with parameter inputs */} + + + {/* Parameter presets */} + + + ); +}; +``` + +#### 2. ParameterInputs Component + +**Following Project Patterns**: +- Uses common components (TextField, SelectField) instead of raw Chakra +- Implements Chakra v3 patterns (Field.Root, etc.) +- Provides proper SelectField descriptions with SelectOptionText + +```typescript +interface ParameterInputsProps { + flowClass?: string; + parameterDefinitions: ParameterDefinitions; + parameterValues: ParameterValues; + onParameterChange: (values: ParameterValues) => void; + validationErrors: { [key: string]: string }; +} + +const ParameterInputs: React.FC = ({ + parameterDefinitions, + parameterValues, + onParameterChange, + validationErrors, +}) => { + const contentRef = useRef(null); + + const handleParameterChange = (paramName: string, value: any) => { + onParameterChange({ + ...parameterValues, + [paramName]: value, + }); + }; + + const renderParameterInput = (paramName: string, schema: ParameterSchema) => { + const defaultValue = schema.default; + const value = parameterValues[paramName] ?? defaultValue ?? ""; + const error = validationErrors[paramName]; + const label = (schema.description || paramName) + (schema.required ? " *" : ""); + + // Helper text priority: schema.helper -> type-based fallback + const getHelperText = () => { + if (schema.helper) return schema.helper; + + switch (schema.type) { + case 'integer': return 'Enter a whole number'; + case 'number': return 'Enter a number (decimals allowed)'; + case 'boolean': return 'Select true or false'; + case 'string': return schema.enum ? undefined : 'Enter text'; + default: return undefined; + } + }; + + const helperText = getHelperText(); + const placeholder = schema.placeholder || ""; + + // Enum parameters - handle both rich {id, description} and simple string arrays + if (schema.enum && schema.enum.length > 0) { + const options = schema.enum.map(option => { + // Handle both rich {id, description} and simple string enums + const optionId = typeof option === 'object' ? option.id : option; + const optionDesc = typeof option === 'object' ? option.description : option; + + return { + value: optionId, + label: optionDesc, + description: ( + + {optionId} + + ), + }; + }); + + return ( + + { + const selectedValue = values.length > 0 ? values[0] : ""; + handleParameterChange(paramName, selectedValue); + }} + contentRef={contentRef} + /> + {error && {error}} + {helperText && ( + + {helperText} + + )} + + ); + } + + // Boolean parameters - use Checkbox + if (schema.type === 'boolean') { + return ( + + + handleParameterChange(paramName, e.target.checked)} + > + {label} + + {helperText && {helperText}} + {error && {error}} + + + ); + } + + // Number/Integer parameters - use TextField with type="number" + if (schema.type === 'number' || schema.type === 'integer') { + let enhancedHelperText = helperText; + if (schema.minimum !== undefined || schema.maximum !== undefined) { + const rangeText = []; + if (schema.minimum !== undefined) rangeText.push(`min: ${schema.minimum}`); + if (schema.maximum !== undefined) rangeText.push(`max: ${schema.maximum}`); + const rangeInfo = rangeText.join(", "); + enhancedHelperText = enhancedHelperText + ? `${enhancedHelperText} (${rangeInfo})` + : rangeInfo; + } + + return ( + + { + const numValue = schema.type === 'integer' + ? parseInt(val, 10) + : parseFloat(val); + if (!isNaN(numValue)) { + handleParameterChange(paramName, numValue); + } else if (val === "") { + handleParameterChange(paramName, ""); + } + }} + type="number" + required={schema.required} + /> + {error && {error}} + + ); + } + + // String parameters - use TextField + return ( + + handleParameterChange(paramName, val)} + required={schema.required} + /> + {error && {error}} + + ); + }; + + return ( + + {Object.entries(parameterDefinitions).map(([paramName, schema]) => + renderParameterInput(paramName, schema) + )} + + ); +}; +``` + +#### 3. State Management + +**Following Project Patterns**: +- Uses TanStack Query for API calls and caching +- Uses useActivity hook for loading states +- Uses useNotification hook for error/success messages + +```typescript +// Enhanced flows.ts state +export const useFlows = () => { + // ... existing code + + /** + * Enhanced mutation for starting flows with parameters + */ + const startFlowMutation = useMutation({ + mutationFn: ({ id, flowClass, description, parameters, onSuccess }) => { + return socket + .flows() + .startFlow(id, flowClass, description, parameters) + .then(() => { + if (onSuccess) onSuccess(); + }); + }, + onError: (err) => { + notify.error(err.message); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["flows"] }); + notify.success("Flow started successfully"); + }, + }); + + // ... rest of existing code +}; + +// New flow-parameters.ts state +export const useFlowParameters = (flowClassName?: string) => { + const socket = useSocket(); + const connectionState = useConnectionState(); + const notify = useNotification(); + + const isSocketReady = + connectionState?.status === "authenticated" || + connectionState?.status === "unauthenticated"; + + /** + * Query for fetching parameter definitions for a flow class + */ + const parametersQuery = useQuery({ + queryKey: ["flow-parameters", flowClassName], + enabled: isSocketReady && !!flowClassName, + queryFn: async () => { + if (!flowClassName) return null; + + // Get flow class definition first + const flowClass = await socket.flows().getFlowClass(flowClassName); + + // Extract parameter metadata + const parameterMetadata = flowClass.parameters || {}; + if (Object.keys(parameterMetadata).length === 0) { + return { parameterDefinitions: {}, parameterMapping: {}, parameterMetadata: {} }; + } + + // Extract unique parameter types for fetching definitions + const parameterTypes = [...new Set(Object.values(parameterMetadata).map(meta => meta.type))]; + const configKeys = parameterTypes.map(type => ({ type: "parameter-types", key: type })); + + const configResponse = await socket.config().getConfig(configKeys); + const parameterDefinitions = {}; + + // Parse config response to get parameter definitions + configResponse.values?.forEach(item => { + if (item.type === "parameter-types") { + parameterDefinitions[item.key] = JSON.parse(item.value); + } + }); + + // Create mapping for backwards compatibility + const parameterMapping = {}; + Object.entries(parameterMetadata).forEach(([paramName, meta]) => { + parameterMapping[paramName] = meta.type; + }); + + return { + parameterDefinitions, + parameterMapping, // Maps flow param names to definition names (backwards compatibility) + parameterMetadata, // Full metadata with description, order, and type + }; + }, + }); + + useActivity(parametersQuery.isLoading, "Loading flow parameters"); + + return { + parameterDefinitions: parametersQuery.data?.parameterDefinitions || {}, + parameterMapping: parametersQuery.data?.parameterMapping || {}, + parameterMetadata: parametersQuery.data?.parameterMetadata || {}, + isLoading: parametersQuery.isLoading, + isError: parametersQuery.isError, + error: parametersQuery.error, + }; +}; +``` + +#### 4. Parameter Validation + +```typescript +// Custom hook for parameter validation +export const useParameterValidation = ( + flowClass: string, + parameterDefinitions: ParameterDefinitions, + parameterValues: ParameterValues +) => { + return useMemo(() => { + const errors: { [key: string]: string } = {}; + let isValid = true; + + Object.entries(parameterDefinitions).forEach(([paramName, schema]) => { + const value = parameterValues[paramName]; + + // Check required fields + if (schema.required && (value === undefined || value === "")) { + errors[paramName] = `${paramName} is required`; + isValid = false; + return; + } + + // Skip validation for empty optional fields + if (value === undefined || value === "") { + return; + } + + // Type validation + if (schema.type === 'number' || schema.type === 'integer') { + const numValue = typeof value === 'string' ? parseFloat(value) : value; + if (isNaN(numValue)) { + errors[paramName] = `${paramName} must be a valid number`; + isValid = false; + return; + } + + if (schema.type === 'integer' && !Number.isInteger(numValue)) { + errors[paramName] = `${paramName} must be an integer`; + isValid = false; + return; + } + + // Range validation + if (schema.minimum !== undefined && numValue < schema.minimum) { + errors[paramName] = `${paramName} must be at least ${schema.minimum}`; + isValid = false; + } + if (schema.maximum !== undefined && numValue > schema.maximum) { + errors[paramName] = `${paramName} must be at most ${schema.maximum}`; + isValid = false; + } + } + + // Enum validation + if (schema.enum && schema.enum.length > 0) { + if (!schema.enum.includes(value)) { + errors[paramName] = `${paramName} must be one of: ${schema.enum.join(', ')}`; + isValid = false; + } + } + + // Pattern validation for strings + if (schema.pattern && schema.type === 'string') { + const regex = new RegExp(schema.pattern); + if (!regex.test(value.toString())) { + errors[paramName] = `${paramName} format is invalid`; + isValid = false; + } + } + }); + + return { isValid, errors }; + }, [parameterDefinitions, parameterValues]); +}; +``` + +### API Integration + +#### Enhanced Socket API + +The socket API has been enhanced to support parameters (already implemented): + +```typescript +// In trustgraph-socket.ts - already updated +startFlow(id: string, class_name: string, description: string, parameters?: { [key: string]: any }) { + return this.api.makeRequest( + "flow", + { + operation: "start-flow", + "flow-id": id, + "class-name": class_name, + description: description, + parameters: parameters, + }, + 30000, + ).then((response) => { + if (response.error) { + const errorMessage = typeof response.error === 'object' && response.error.message + ? response.error.message + : typeof response.error === 'string' + ? response.error + : "Flow start failed"; + throw new Error(errorMessage); + } + return response; + }); +} +``` + +#### Config API Integration + +```typescript +// Fetching parameter definitions from config system +const fetchParameterDefinitions = async (definitionNames: string[]) => { + const configKeys = definitionNames.map(name => ({ + type: "parameter-types", + key: name + })); + + const response = await socket.config().getConfig(configKeys); + const definitions = {}; + + response.values?.forEach(item => { + if (item.type === "parameter-types") { + definitions[item.key] = JSON.parse(item.value); + } + }); + + return definitions; +}; +``` + +## Real-World Examples + +### Flow Class Example +```json +{ + "class": { + "text-completion:{id}": { + "model": "{llm-model}", + "request": "non-persistent://tg/request/text-completion:{id}", + "response": "non-persistent://tg/response/text-completion:{id}" + } + }, + "description": "GraphRAG, DocumentRAG, structured data + knowledge cores", + "flow": { + "text-completion:{id}": { + "model": "{llm-model}", + "temperature": "{llm-temperature}", + "request": "non-persistent://tg/request/text-completion:{id}", + "response": "non-persistent://tg/response/text-completion:{id}" + } + }, + "interfaces": { /* ... */ }, + "parameters": { + "llm-model": { + "description": "LLM model", + "order": 1, + "type": "llm-model" + }, + "llm-rag-model": { + "description": "LLM model for RAG", + "order": 2, + "type": "llm-model" + }, + "llm-rag-temperature": { + "description": "LLM temperature", + "order": 3, + "type": "llm-temperature" + }, + "llm-temperature": { + "description": "LLM temperature", + "order": 3, + "type": "llm-temperature" + } + }, + "tags": ["document-rag", "graph-rag"] +} +``` + +### Parameter Definition Examples + +#### Rich Enum Parameter (LLM Model) +```json +{ + "default": "gemini-2.5-flash-lite", + "description": "LLM model to use", + "enum": [ + { + "description": "Gemini 2.5 Pro", + "id": "gemini-2.5-pro" + }, + { + "description": "Claude 3.5 Sonnet (via VertexAI)", + "id": "claude-3-5-sonnet@20241022" + } + ], + "required": true, + "type": "string" +} +``` + +#### String Parameter (Free-form) +```json +{ + "default": "gemini-2.5-flash-lite", + "description": "LLM model to use", + "required": true, + "type": "string", + "helper": "Enter the model identifier", + "placeholder": "e.g. gpt-4, claude-3" +} +``` + +#### Number Parameter Example +```json +{ + "default": 0.7, + "description": "Temperature for model generation", + "type": "number", + "minimum": 0.0, + "maximum": 2.0, + "required": false, + "helper": "Controls randomness of model output" +} +``` + +#### Boolean Parameter Example +```json +{ + "default": false, + "description": "Enable streaming responses", + "type": "boolean", + "required": false, + "helper": "Stream responses as they are generated" +} +``` + +### Config API Response Example +```json +{ + "values": [ + { + "type": "parameter-types", + "key": "llm-model", + "value": "{\"default\": \"gemini-2.5-flash-lite\", \"description\": \"LLM model to use\", \"enum\": [{\"description\": \"Gemini 2.5 Pro\", \"id\": \"gemini-2.5-pro\"}], \"required\": true, \"type\": \"string\"}" + } + ] +} +``` + +## User Experience Design + +### Form Behavior + +1. **Initial State**: When CreateDialog opens, no parameters are shown +2. **Flow Class Selection**: When user selects a flow class: + - Show loading indicator while fetching parameters + - Display parameter form sections if parameters exist + - Show "No additional parameters required" if none exist +3. **Parameter Input**: + - Show validation errors in real-time + - Disable submit button until all required parameters are valid + - Provide clear descriptions and hints for each parameter +4. **Form Submission**: + - Validate all parameters before submission + - Show progress indicator during submission + - Clear form on successful submission + - Retain values on error for correction + +### Parameter Input Types + +1. **Enum Parameters (with rich options)**: + - SelectField dropdown with `{id, description}` structure + - Display user-friendly descriptions, store technical IDs + - Example: "Gemini 2.5 Pro" displays, "gemini-2.5-pro" is the value + - Supports both rich objects and simple string arrays + +2. **String Parameters (no enum)**: + - TextField for free-form text input + - Uses `description` field as label + - Uses `helper` field or falls back to "Enter text" + - Uses `placeholder` field if provided + +3. **Integer Parameters**: + - TextField with `type="number"` + - Helper text: "Enter a whole number" + - Validates and converts to integer + +4. **Number/Float Parameters**: + - TextField with `type="number"` + - Helper text: "Enter a number (decimals allowed)" + - Validates and converts to float + +5. **Boolean Parameters**: + - Checkbox component + - Helper text: "Select true or false" + - Direct true/false values + +6. **Required Parameters**: + - Marked with asterisk (*) in label + - Red error styling for validation failures + - Cannot submit form without valid values + +7. **Default Values**: + - Pre-populated when form loads + - Shows current value or default from schema + +### Parameter Presets (Future Enhancement) + +```typescript +// Parameter preset management component +const ParameterPresets: React.FC<{ + flowClass: string; + onPresetLoad: (values: ParameterValues) => void; + currentValues: ParameterValues; +}> = ({ flowClass, onPresetLoad, currentValues }) => { + const [presets, setPresets] = useState([]); + const [presetName, setPresetName] = useState(""); + + const savePreset = () => { + const preset: ParameterPreset = { + id: generateId(), + name: presetName, + flowClass, + values: currentValues, + createdAt: new Date(), + }; + // Save to local storage or backend + saveParameterPreset(preset); + setPresets([...presets, preset]); + }; + + return ( + + Parameter Presets + + {/* Preset selection */} + {presets.length > 0 && ( + ({ + value: preset.id, + label: preset.name, + description: ( + + {preset.name} - {formatDate(preset.createdAt)} + + ), + }))} + value={[]} + onValueChange={(values) => { + const presetId = values[0]; + const preset = presets.find(p => p.id === presetId); + if (preset) { + onPresetLoad(preset.values); + } + }} + /> + )} + + {/* Save current values as preset */} + + + + + + ); +}; +``` + +## Testing Strategy + +### Unit Tests + +1. **ParameterInputs Component Tests**: +```typescript +describe('ParameterInputs', () => { + it('renders string parameters with TextField', () => { + const schema = { type: 'string', description: 'Test string param' }; + render(); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + }); + + it('renders enum parameters with SelectField', () => { + const schema = { type: 'string', enum: ['option1', 'option2'] }; + render(); + expect(screen.getByRole('combobox')).toBeInTheDocument(); + }); + + it('validates required parameters', () => { + const schema = { type: 'string', required: true }; + const validationErrors = { param1: 'param1 is required' }; + render(); + expect(screen.getByText('param1 is required')).toBeInTheDocument(); + }); + + it('calls onParameterChange when value changes', () => { + const mockOnChange = jest.fn(); + const schema = { type: 'string' }; + render(); + + const input = screen.getByRole('textbox'); + fireEvent.change(input, { target: { value: 'test value' } }); + + expect(mockOnChange).toHaveBeenCalledWith({ param1: 'test value' }); + }); +}); +``` + +2. **Parameter Validation Tests**: +```typescript +describe('useParameterValidation', () => { + it('validates required parameters', () => { + const { result } = renderHook(() => useParameterValidation( + 'test-flow', + { param1: { type: 'string', required: true } }, + {} + )); + + expect(result.current.isValid).toBe(false); + expect(result.current.errors.param1).toBe('param1 is required'); + }); + + it('validates number ranges', () => { + const { result } = renderHook(() => useParameterValidation( + 'test-flow', + { param1: { type: 'number', minimum: 5, maximum: 10 } }, + { param1: 15 } + )); + + expect(result.current.isValid).toBe(false); + expect(result.current.errors.param1).toContain('must be at most 10'); + }); + + it('validates enum values', () => { + const { result } = renderHook(() => useParameterValidation( + 'test-flow', + { param1: { type: 'string', enum: ['opt1', 'opt2'] } }, + { param1: 'invalid' } + )); + + expect(result.current.isValid).toBe(false); + expect(result.current.errors.param1).toContain('must be one of: opt1, opt2'); + }); +}); +``` + +### Integration Tests + +1. **CreateDialog with Parameters**: +```typescript +describe('CreateDialog with Parameters', () => { + it('fetches and displays parameters when flow class is selected', async () => { + const mockFlowClass = { + parameters: { 'model': 'llm-model', 'temp': 'temperature' } + }; + const mockParameterDefs = { + 'llm-model': { type: 'string', enum: ['gpt-4', 'claude-3'] }, + 'temperature': { type: 'number', minimum: 0, maximum: 2 } + }; + + mockSocket.flows().getFlowClass.mockResolvedValue(mockFlowClass); + mockSocket.config().getConfig.mockResolvedValue({ + values: [ + { type: 'parameters', key: 'llm-model', value: JSON.stringify(mockParameterDefs['llm-model']) }, + { type: 'parameters', key: 'temperature', value: JSON.stringify(mockParameterDefs.temperature) } + ] + }); + + render(); + + // Select flow class + const flowClassSelect = screen.getByLabelText('Flow class'); + fireEvent.change(flowClassSelect, { target: { value: 'test-flow' } }); + + // Wait for parameters to load and display + await waitFor(() => { + expect(screen.getByLabelText('model')).toBeInTheDocument(); + expect(screen.getByLabelText('temp')).toBeInTheDocument(); + }); + }); + + it('submits flow with parameter values', async () => { + // Set up mocks and render + // Fill in form including parameters + // Submit form + // Verify startFlow called with correct parameters + }); + + it('prevents submission with invalid parameters', () => { + // Set up form with required parameters + // Leave parameters empty + // Verify submit button is disabled + // Verify validation errors are shown + }); +}); +``` + +### End-to-End Tests + +1. **Complete Flow Creation with Parameters**: + - Navigate to Flows page + - Click Create button + - Select flow class with parameters + - Fill in all required fields and parameters + - Submit form + - Verify flow appears in list + - Verify flow has correct parameter values + +2. **Parameter Validation Scenarios**: + - Test all parameter types (string, number, boolean, enum) + - Test required field validation + - Test range validation for numbers + - Test enum value validation + - Test form reset after successful submission + +## Migration Plan + +### Phase 1: Core Parameter Support +1. ✅ Update FlowRequest/FlowResponse types (completed) +2. ✅ Update startFlow API method (completed) +3. ✅ Create ParameterInputs component (completed) +4. Integrate ParameterInputs into CreateDialog +5. Implement parameter fetching from config API +6. Add parameter validation logic + +### Phase 2: Enhanced User Experience +1. Add loading states during parameter fetching +2. Improve validation error display +3. Add parameter descriptions and help text +4. Implement form reset on successful submission + +### Phase 3: Advanced Features (Future) +1. Parameter presets and templates +2. Parameter value suggestions based on history +3. Bulk parameter import/export +4. Parameter dependency validation +5. Real-time parameter preview + +## Security Considerations + +1. **Parameter Validation**: All parameter validation occurs on both client and server +2. **Type Safety**: TypeScript ensures type safety for parameter values +3. **Sanitization**: Parameter values are properly sanitized before API calls +4. **Schema Validation**: Parameter schemas are validated against expected formats +5. **Error Handling**: Sensitive information is not exposed in validation errors + +## Performance Considerations + +1. **Lazy Loading**: Parameter definitions are only fetched when needed +2. **Caching**: Parameter definitions are cached using TanStack Query +3. **Validation Debouncing**: Real-time validation is debounced to avoid excessive computation +4. **Component Optimization**: ParameterInputs uses React.memo for performance +5. **Bundle Size**: Components are tree-shakeable and imported dynamically where possible + +## Backwards Compatibility + +1. **Flow Classes Without Parameters**: Existing flow classes continue to work without changes +2. **API Compatibility**: Parameter field is optional in API calls +3. **UI Graceful Degradation**: UI gracefully handles flows with no parameters +4. **Existing Flows**: Existing flows without parameters continue to function normally + +## Future Enhancements + +1. **Parameter Templates**: Pre-defined parameter sets for common use cases +2. **Conditional Parameters**: Parameters that appear based on other parameter values +3. **Parameter Groups**: Organize related parameters into collapsible sections +4. **Import/Export**: Bulk parameter configuration via JSON/YAML files +5. **Parameter History**: Track and suggest parameter values based on usage patterns +6. **Advanced Validation**: Custom validation rules and cross-parameter validation +7. **Parameter Documentation**: Rich documentation with examples and best practices \ No newline at end of file diff --git a/docs/tech-specs/gateway-auth.md b/docs/tech-specs/gateway-auth.md new file mode 100644 index 00000000..19f5c9cf --- /dev/null +++ b/docs/tech-specs/gateway-auth.md @@ -0,0 +1,373 @@ +# Gateway Authentication for TrustGraph UI + +## Overview + +This document specifies the implementation of gateway authentication for the TrustGraph UI application. The gateway authentication system will provide secure communication between the UI and the TrustGraph backend services through an authentication token mechanism. + +## Requirements + +The gateway authentication system should provide: + +1. **Authentication Modes** + - **Unauthenticated Mode**: When no API key is entered (empty string), the system operates without authentication + - **Authenticated Mode**: When an API key is specified in settings, all communications include the authentication token + +2. **Token Management** + - Secure storage and retrieval of authentication credentials via settings system + - Authentication is determined by presence/absence of API key + - Token persists across sessions (stored in localStorage via settings) + - Token should be masked in UI displays + +3. **Integration Points** + - **WebSocket Connections**: Append `?token=` to WebSocket connection URL + - **REST API Calls**: Include token as Bearer token in Authorization header + - Settings page for token configuration + - Error handling for 401/403 responses + +## Implementation Details + +### Settings Integration + +**Authentication Settings** (already exists in settings-types.ts): +```typescript +authentication: { + apiKey: string; // Gateway authentication token/secret +} +``` + +### Socket Layer Integration + +**WebSocket Authentication**: +```typescript +// In createTrustGraphSocket or SocketProvider +const wsUrl = settings.authentication.apiKey + ? `/api/socket?token=${settings.authentication.apiKey}` + : `/api/socket`; +``` + +**REST API Authentication**: +```typescript +// For any REST endpoints (if used) +const headers = settings.authentication.apiKey + ? { 'Authorization': `Bearer ${settings.authentication.apiKey}` } + : {}; +``` + +**Current State**: +- `useSocket()` hook now has access to settings via `useSettings()` +- Socket context created once at app initialization +- Settings changes require socket reconnection for auth updates + +**Required Changes**: +1. Modify `createTrustGraphSocket` to accept optional token parameter +2. Update socket initialization to append token to WebSocket URL when present +3. Add Bearer token to any REST API calls (if applicable) +4. Handle authentication errors (401/403) gracefully +5. Consider socket reconnection when authentication settings change + +### User Interface + +**Settings Page**: +- Password input field for gateway secret +- Show/hide toggle for secret visibility +- Clear button to remove authentication +- Save confirmation with success/error feedback + +**Authentication Status**: +- Optional status indicator in header/sidebar +- Error notifications for auth failures +- Redirect to settings on 401/403 errors + +## Socket Initialization Timing + +### Critical Requirements +1. **The socket MUST NOT be created until settings have been loaded from localStorage/backend**. Creating the socket too early will result in incorrect authentication state. +2. **The socket MUST reconnect when the API key changes**. This ensures authentication state stays synchronized with user settings. + +### Initialization Scenarios + +1. **Scenario 1: Token Already Configured** + - User has previously saved an API key in settings + - Settings load from localStorage → contains `apiKey: "token123"` + - Socket creation MUST wait for settings load + - Socket connects with `?token=token123` appended to URL + - **Risk if socket created early**: Connects without auth, requires reconnection + +2. **Scenario 2: Explicitly Unauthenticated** + - User has explicitly chosen no authentication (saved empty token) + - Settings load from localStorage → contains `apiKey: ""` + - Socket creation MUST wait for settings load + - Socket connects WITHOUT token parameter + - **Risk if socket created early**: Might use stale token from previous session + +3. **Scenario 3: First-Time User / No Settings** + - No settings have been saved yet + - Settings system returns defaults (empty apiKey) + - **Options**: + a. Wait for settings to initialize with defaults, then create socket (safest) + b. Create socket immediately without auth (assumes unauthenticated default) + c. Show setup wizard requiring auth decision before socket creation + - **Recommendation**: Option (a) - always wait for settings initialization + +### Socket Reconnection Requirements + +When the API key changes (user updates settings), the socket must: + +1. **Detect the Change** + - Monitor `settings.authentication.apiKey` for changes + - Triggered when user saves new API key in settings + - Also triggered when user clears API key (switches to unauthenticated) + +2. **Clean Disconnect** + - Close existing WebSocket connection gracefully + - Cancel any pending requests/subscriptions + - Clear any auth-related state + +3. **Reconnect with New Auth** + - Create new socket with updated token (or no token) + - Re-establish WebSocket connection + - Show brief loading/reconnecting state to user + +4. **Handle Edge Cases** + - API key changes from `""` to `"token123"` (unauthenticated → authenticated) + - API key changes from `"token123"` to `"token456"` (change tokens) + - API key changes from `"token123"` to `""` (authenticated → unauthenticated) + - Rapid API key changes (debounce or queue reconnections) + +### Implementation Strategy + +```typescript +// BAD - Socket created immediately, no reconnection +const socket = createTrustGraphSocket(); // ❌ No access to settings yet +export const SocketContext = createContext(socket); + +// GOOD - Socket created after settings load, reconnects on auth change +const SocketProvider = ({ children }) => { + const { settings, isLoaded } = useSettings(); + const [socket, setSocket] = useState(null); + const [isReconnecting, setIsReconnecting] = useState(false); + + useEffect(() => { + if (!isLoaded) return; // Wait for settings + + // Show reconnecting state during transitions + setIsReconnecting(true); + + // Clean up old socket if it exists + if (socket) { + console.log("Closing existing socket for reconnection..."); + socket.close(); + } + + // Create new socket with current auth settings + const newSocket = createTrustGraphSocket(settings.authentication.apiKey); + + // Wait for connection to establish + newSocket.addEventListener('open', () => { + console.log("Socket connected with auth:", + settings.authentication.apiKey ? 'enabled' : 'disabled'); + setIsReconnecting(false); + }); + + setSocket(newSocket); + + return () => newSocket?.close(); + }, [isLoaded, settings.authentication.apiKey]); // Re-run when apiKey changes + + if (!socket || isReconnecting) { + return ( + + + {isReconnecting ? 'Reconnecting...' : 'Initializing...'} + + ); + } + + return ( + + {children} + + ); +}; +``` + +## Technical Approach + +### Phase 1: Deferred Socket Initialization +1. Convert static socket creation to dynamic SocketProvider +2. Wait for settings to load before creating socket +3. Show loading state while settings/socket initialize +4. Pass loaded auth token to socket creation + +### Phase 2: Basic Authentication +1. Append `?token=` to WebSocket URL if token exists +2. Add Bearer token to REST API headers if token exists +3. Handle basic auth success/failure + +### Phase 3: Socket Reconnection on Auth Change +1. Detect when authentication settings change +2. Close existing socket connection gracefully +3. Create new socket with updated authentication +4. Handle in-flight requests during reconnection +5. Restore any active subscriptions/state (if needed) + +### Phase 4: Enhanced Features (Future) +1. Token validation endpoint +2. Authentication status indicator +3. Auto-retry with exponential backoff on auth failures +4. Better error messages for authentication issues + +## Security Considerations + +1. **Token Storage**: + - Stored in localStorage via settings system + - Never logged to console in production + - Masked in UI displays + +2. **Token Transmission**: + - Sent via secure headers + - HTTPS required in production + - No token in URL parameters + +3. **Error Handling**: + - Generic error messages to users + - Detailed errors only in development mode + - Rate limiting on failed attempts + +## Testing Strategy + +1. **Unit Tests**: + - Settings storage and retrieval + - Header injection logic + - Error handling paths + +2. **Integration Tests**: + - Full authentication flow + - Token persistence across sessions + - Error recovery scenarios + +3. **Manual Testing**: + - UI interaction flows + - Network failure scenarios + - Token expiration handling + +## Migration Path + +1. **Backwards Compatibility**: + - Support unauthenticated mode (empty token) + - Graceful degradation for older backends + - Feature detection for auth requirements + +2. **Rollout Strategy**: + - Deploy with auth disabled by default + - Enable per-user via settings + - Monitor error rates during rollout + +## Implementation Example + +```typescript +// In trustgraph-socket.ts +export const createTrustGraphSocket = (token?: string) => { + // Use relative URL for WebSocket connection + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const host = window.location.host; + const baseUrl = `${protocol}//${host}/api/socket`; + const wsUrl = token ? `${baseUrl}?token=${token}` : baseUrl; + + console.log(`Creating socket with auth: ${token ? 'enabled' : 'disabled'}`); + + // Create WebSocket connection with authentication + const socket = new WebSocket(wsUrl); + + // ... rest of socket implementation +}; + +// In SocketProvider.tsx (NEW FILE) +export const SocketProvider = ({ children }) => { + const { settings, isLoaded } = useSettings(); + const [socket, setSocket] = useState(null); + const [isSocketReady, setIsSocketReady] = useState(false); + + useEffect(() => { + // CRITICAL: Wait for settings to load + if (!isLoaded) { + console.log("Waiting for settings to load before creating socket..."); + return; + } + + console.log("Settings loaded, creating socket with auth:", + settings.authentication.apiKey ? 'enabled' : 'disabled'); + + // Clean up existing socket before creating new one + if (socket) { + console.log("API key changed, closing existing socket..."); + socket.close(); + setIsSocketReady(false); + } + + // Create socket with current auth settings + const newSocket = createTrustGraphSocket(settings.authentication.apiKey); + + // Mark socket as ready when connection opens + newSocket.addEventListener('open', () => { + console.log("Socket connected successfully"); + setIsSocketReady(true); + }); + + setSocket(newSocket); + + return () => { + newSocket?.close(); + setIsSocketReady(false); + }; + }, [isLoaded, settings.authentication.apiKey]); // Reconnects when API key changes + + // Show loading state until both settings and socket are ready + if (!isSocketReady) { + return ( + + + Initializing connection... + + ); + } + + return ( + + {children} + + ); +}; + +// In App.tsx or index.tsx + + {/* Now waits for settings before creating socket */} + + + + + +``` + +## Open Questions + +1. **Socket Reconnection Strategy**: + - Should socket reconnect automatically when auth settings change? + - How to handle in-flight requests during reconnection? + - Should we show a loading state during reconnection? + +2. **Error Handling**: + - How does the backend communicate auth failures (401 vs 403)? + - Should we automatically redirect to settings on auth failure? + - How to differentiate between network errors and auth errors? + +3. **Token Security**: + - Should we support token rotation/refresh? + - How long should tokens be valid? + - Should we add CSRF protection for REST calls? + +## References + +- [Settings System Documentation](./SETTINGS.md) +- [Socket Implementation](../../src/api/trustgraph/socket.ts) +- [TrustGraph Socket API](../../src/api/trustgraph/trustgraph-socket.ts) \ No newline at end of file diff --git a/docs/tech-specs/llm-models-editor.md b/docs/tech-specs/llm-models-editor.md new file mode 100644 index 00000000..eedae62c --- /dev/null +++ b/docs/tech-specs/llm-models-editor.md @@ -0,0 +1,185 @@ +# LLM Models Editor Technical Specification + +## Overview + +This specification describes the LLM Models Editor in the TrustGraph UI. This feature allows administrators to manage the `llm-model` parameter type - the list of available LLM models that appear in dropdown menus when launching flows. + +The LLM model list is stored as a parameter type definition in the configuration system with type `"parameter-types"` and key `"llm-model"`. The editor provides a simple table interface for managing model options (ID, Description, Default). + +## Background + +The `llm-model` parameter controls which models are available when configuring flows. It's stored as a parameter type definition with an `enum` field containing model options. + +### Current State + +- The llm-model parameter can be modified through direct config API calls or CLI commands +- The parameter type with its `enum` array renders as a dropdown in flow dialogs +- The default value determines which model is pre-selected + +### Feature Switch + +This feature is controlled by a feature switch in Settings: +- **Setting Name**: `llmModels` +- **Display Label**: "LLM Models" +- **Default**: `false` (off by default) +- **Location**: Settings page → Feature Switches section + +## Goals + +- **Simple Table Editor**: Editable table with ID, Description, and Default columns +- **Direct Editing**: Edit model options directly in table cells +- **Add/Delete Rows**: Add new models or delete existing ones +- **Default Selection**: Radio button to mark one model as default +- **Save Changes**: Manual save with "Save Changes" button +- **Auto-defaults**: First model automatically selected as default when adding to empty table + +## Technical Design + +### Architecture + +Following CODEBOT-INSTRUCTIONS.md patterns: + +**Component Structure:** +``` +src/ +├── pages/ +│ └── LLMModelsPage.tsx # Main page with PageHeader +├── components/ +│ └── llm-models/ # Domain-specific directory +│ ├── LLMModels.tsx # Container component +│ ├── ParameterTypeSelector.tsx # (unused - kept for future) +│ └── ModelsTable.tsx # Editable table with save +├── state/ +│ └── llm-models.ts # API hooks +└── model/ + └── llm-models.ts # TypeScript types +``` + +### Data Models + +#### EnumOption (Model Option) + +```typescript +interface EnumOption { + id: string; // Model ID (e.g., "gemini-2.5-flash") + description: string; // Display text (e.g., "Gemini 2.5 Flash") +} +``` + +#### LLMModelParameter + +```typescript +interface LLMModelParameter { + name: string; // Parameter type key (always "llm-model") + type: string; // Always "string" + description: string; // Read-only (e.g., "LLM model to use") + default: string; // Default model ID + enum: EnumOption[]; // List of models + required: boolean; // Read-only +} +``` + +### Implementation Details + +**Key Behavior:** +1. Page only handles the single `llm-model` parameter type +2. Table edits are local until "Save Changes" is clicked +3. Radio buttons use native HTML inputs (Chakra RadioGroup had issues in tables) +4. When adding first model to empty table, it's auto-selected as default +5. When editing ID of default model, default value updates to track changes +6. When deleting default model, first remaining model becomes default +7. Empty ID fields are allowed but disabled for default selection + +**State Management:** +- Uses `getConfig([{type: "parameter-types", key: "llm-model"}])` to fetch single param +- Uses `putConfig()` to save changes, preserving read-only fields +- React Query handles caching and invalidation + +### Routing and Navigation + +#### Route (`src/App.tsx`) +```typescript +} /> +``` + +#### Sidebar Navigation (`src/components/Sidebar.tsx`) +```typescript +{settings.featureSwitches.llmModels && ( + +)} +``` + +### Feature Switch Integration + +#### Settings Types (`src/model/settings-types.ts`) +```typescript +featureSwitches: { + llmModels: boolean; // Default: false +} +``` + +#### Feature Switches Section (`src/components/settings/FeatureSwitchesSection.tsx`) +Adds toggle UI with prop `llmModels` and handler `onLlmModelsChange` + +## User Workflows + +### Editing Model Options + +1. Enable feature in Settings → Feature Switches → LLM Models +2. Navigate to LLM Models page from sidebar +3. View current models in table +4. Edit ID or Description fields directly +5. Click "Save Changes" to persist +6. Notification confirms success + +### Setting Default Model + +1. View models table +2. Click radio button in "Default" column for desired model +3. Click "Save Changes" to persist + +### Adding New Model + +1. Click "Add Model" button +2. New empty row appears +3. Enter Model ID and Description +4. If it's the only model, radio button is auto-selected +5. Click "Save Changes" to persist + +### Deleting Model + +1. Click trash icon next to model +2. Row is removed from local state +3. If deleted model was default, first remaining model becomes default +4. Click "Save Changes" to persist + +## Implementation Checklist + +- [x] Update `src/model/settings-types.ts` - Add `llmModels` feature switch +- [x] Update `src/components/settings/FeatureSwitchesSection.tsx` - Add LLM Models toggle +- [x] Update `src/components/settings/Settings.tsx` - Wire up llmModels prop +- [x] Create `src/model/llm-models.ts` - Type definitions +- [x] Create `src/state/llm-models.ts` - useLLMModels hook +- [x] Create `src/components/llm-models/LLMModels.tsx` - Container +- [x] Create `src/components/llm-models/ParameterTypeSelector.tsx` - (Created but unused) +- [x] Create `src/components/llm-models/ModelsTable.tsx` - Editable table with save +- [x] Create `src/pages/LLMModelsPage.tsx` - Main page with PageHeader +- [x] Update `src/App.tsx` - Add route +- [x] Update `src/components/Sidebar.tsx` - Add navigation item with Bot icon +- [x] Test CRUD operations +- [x] Test feature switch toggle + +## Future Enhancements + +1. **Multiple Parameter Types**: Support editing other parameter types with enum arrays (llm-rag-model, etc.) +2. **Import/Export**: Bulk import/export model lists from JSON +3. **Templates**: Pre-configured model lists for common providers +4. **Model Metadata**: Additional fields like context length, cost per token +5. **Reordering**: Drag-and-drop or up/down arrows to reorder models + +## References + +- Flow Configurable Parameters: `docs/tech-specs/flow-configurable-parameters.md` +- Parameter Inputs Component: `src/components/flows/ParameterInputs.tsx` +- Settings Feature Switches: `src/components/settings/FeatureSwitchesSection.tsx` +- CODEBOT Instructions: `CODEBOT-INSTRUCTIONS.md` diff --git a/docs/tech-specs/ontology.md b/docs/tech-specs/ontology.md new file mode 100644 index 00000000..c3357cb0 --- /dev/null +++ b/docs/tech-specs/ontology.md @@ -0,0 +1,1029 @@ +# OWL Ontology Management UI Technical Specification + +## Overview + +This specification describes a user interface component for managing OWL ontologies within the existing TrustGraph system. The UI enables data architects to create, edit, and maintain formal ontologies with classes, properties, and complex relationships, supporting multiple concurrent ontologies stored as configuration items in the backend. + +**Naming Convention**: This project uses kebab-case for all identifiers (configuration keys, API endpoints, module names, etc.) rather than snake_case. + +The system supports four primary use cases: + +1. **Ontology Creation and Management**: Create new ontologies and manage existing ones through a structured interface +2. **Class and Property Definition**: Define OWL classes, object properties, and datatype properties with domains and ranges +3. **Rich Metadata and Constraints**: Add labels, descriptions, cardinality constraints, and type specifications +4. **AI-Assisted Content Generation**: Optional wizard/getting started flow using LLM helpers to generate or enhance ontology content + +## Goals + +- **Class and Property Management**: Define and manage OWL classes with properties, domains, ranges, and type constraints +- **Rich Semantic Support**: Enable comprehensive editing of RDFS/OWL properties including labels, multi-language support, and formal constraints +- **Multi-Ontology Support**: Allow data architects to work with multiple ontologies simultaneously +- **AI-Enhanced Productivity**: Provide optional LLM assistance for content generation and ontology enhancement +- **Configuration Integration**: Seamlessly store and retrieve ontologies from the existing configuration backend +- **Data Architect Focus**: Design interfaces that expose technical details and provide precise control over ontology structure +- **Validation and Reasoning**: Ensure ontologies conform to OWL standards with consistency checking and inference support +- **Import/Export Capabilities**: Support standard formats (Turtle, RDF/XML, OWL/XML) for ontology interchange + +## Background + +TrustGraph currently stores configuration data in a flexible key-value system that can accommodate various data types. OWL ontologies represent formal knowledge models that need to be managed through dedicated tooling rather than raw configuration editing. + +Data architects working with knowledge graphs need ontologies to: +- Define formal object types and their properties +- Specify property domains, ranges, and type constraints +- Enable logical reasoning and inference +- Support complex relationships and cardinality constraints +- Provide unique identifiers and external reference capabilities + +Current limitations include: +- No dedicated UI for ontology management +- Manual editing of complex class and property definitions +- Difficulty maintaining OWL consistency +- No automated assistance for content generation +- Limited validation and reasoning capabilities + +This specification addresses these gaps by providing a specialized interface that understands OWL/RDFS semantics while integrating with the existing configuration infrastructure. + +## Technical Design + +### Architecture + +The taxonomy management UI requires the following components: + +1. **Ontology Manager Component** + - React-based interface component for the main ontology management workspace + - Integrates with existing UI framework and design system + - Provides tree view, detail panels, and editing interfaces + - Supports undo/redo operations and change tracking + + Module: workbench-ui/src/components/OntologyManager + +2. **Configuration API Integration** ✅ **[EXISTING]** + - Extends existing configuration API to handle ontology-specific operations + - Type: `ontology` for all OWL ontology configurations + - Key: Unique ontology identifier (e.g., `domain-model`, `process-ontology`) + - Value: Complete ontology in JSON-LD, Turtle, or OWL/XML format + +3. **OWL Parser/Serializer Module** + - Converts between internal JSON representation and standard OWL formats + - Supports Turtle, RDF/XML, and OWL/XML import/export + - Validates OWL compliance and logical consistency + - Handles namespace management and URI generation + + Module: workbench-ui/src/utils/owl + +4. **LLM Assistant Integration (Optional)** + - Provides an optional wizard/getting started flow for new ontologies + - Connects to existing LLM services for content generation + - Offers context-aware suggestions for classes, properties, and constraints + - Supports bulk content enhancement and ontology expansion + - Can be bypassed entirely for manual ontology creation + - Maintains logical consistency with existing definitions + + Module: workbench-ui/src/services/ontologyAssistant + +5. **Class Hierarchy Visualization Component** + - Tree-based visual representation of class hierarchies and property relationships + - Drag-and-drop support for restructuring class inheritance + - Visual indicators for class completeness, property assignments, and validation status + - Search and filtering capabilities for classes and properties + + Module: workbench-ui/src/components/OntologyTree + +6. **Class/Property Editor Component** + - Form-based interface for editing classes and properties + - Dedicated sections for domains, ranges, type constraints, and cardinality + - Real-time validation and consistency checking + - Preview of generated OWL/Turtle output + + Module: workbench-ui/src/components/OntologyEditor + +7. **Ontology Import/Export Service** + - Handles file uploads and downloads + - Supports multiple formats (Turtle, RDF/XML, OWL/XML, JSON-LD) + - Provides format conversion and validation + - Manages conflict resolution during imports + + Module: workbench-ui/src/services/ontologyIO + +### Data Models + +#### Internal Representation + +Ontologies are stored as configuration items with the following structure: + +**Configuration Schema:** +``` +Type: ontology +Key: [ontology_identifier] +Value: { + "metadata": { + "name": "Domain Ontology", + "description": "Comprehensive domain model ontology", + "version": "1.2", + "created": "2025-01-15T10:30:00Z", + "modified": "2025-08-15T14:22:00Z", + "creator": "data-architect-001", + "namespace": "http://example.org/domain/", + "imports": ["http://www.w3.org/2002/07/owl#"] + }, + "classes": { + "Document": { + "uri": "http://example.org/domain/Document", + "type": "owl:Class", + "rdfs:label": [{"value": "Document", "lang": "en"}, {"value": "Documento", "lang": "es"}], + "rdfs:comment": "A document in the system", + "rdfs:subClassOf": "Resource", + "owl:disjointWith": ["Person", "Organization"], + "dcterms:identifier": "DOC-001" + }, + "Resource": { + "uri": "http://example.org/domain/Resource", + "type": "owl:Class", + "rdfs:label": [{"value": "Resource", "lang": "en"}], + "rdfs:comment": "Base class for all resources" + } + }, + "objectProperties": { + "hasAuthor": { + "uri": "http://example.org/domain/hasAuthor", + "type": "owl:ObjectProperty", + "rdfs:label": [{"value": "has author", "lang": "en"}], + "rdfs:domain": "Document", + "rdfs:range": "Person", + "owl:inverseOf": "authorOf", + "rdfs:comment": "Links a document to its author" + } + }, + "datatypeProperties": { + "title": { + "uri": "http://example.org/domain/title", + "type": "owl:DatatypeProperty", + "rdfs:label": [{"value": "title", "lang": "en"}], + "rdfs:domain": "Document", + "rdfs:range": "xsd:string", + "owl:functionalProperty": true, + "rdfs:comment": "The title of a document" + }, + "pageCount": { + "uri": "http://example.org/domain/pageCount", + "type": "owl:DatatypeProperty", + "rdfs:label": [{"value": "page count", "lang": "en"}], + "rdfs:domain": "Document", + "rdfs:range": "xsd:integer", + "rdfs:comment": "Number of pages in the document" + } + } +} +``` + +#### OWL/RDFS Property Mapping + +The UI supports all essential OWL and RDFS properties: + +**Class Definition Properties:** +- `owl:Class` - Define object types +- `rdfs:subClassOf` - Class hierarchy and inheritance +- `owl:equivalentClass` - Class equivalence relationships +- `owl:disjointWith` - Disjoint class declarations +- `rdfs:label` - Human-readable labels with language tags +- `rdfs:comment` - Class descriptions and documentation +- `dcterms:identifier` - External reference IDs + +**Property Definition:** +- `owl:ObjectProperty` - Properties linking to other classes +- `owl:DatatypeProperty` - Properties with literal values +- `rdfs:domain` - Specifies which classes have the property +- `rdfs:range` - Specifies property value types (classes or XSD datatypes) +- `rdfs:subPropertyOf` - Property hierarchy +- `owl:inverseOf` - Inverse property relationships + +**Cardinality and Constraints:** +- `owl:functionalProperty` - At most one value +- `owl:inverseFunctionalProperty` - Unique identifying property +- `owl:minCardinality` - Minimum number of values +- `owl:maxCardinality` - Maximum number of values +- `owl:cardinality` - Exact number of values + +**Datatype Support (XSD):** +- `xsd:string` - Text values +- `xsd:integer` - Integer numbers +- `xsd:float` - Floating point numbers +- `xsd:boolean` - True/false values +- `xsd:dateTime` - Date and time values +- `xsd:anyURI` - URI references + +### User Interface Design + +#### Main Workspace Layout + +``` ++----------------------------------------------------------+ +| Ontology Manager [?] [⚙] | ++----------------------------------------------------------+ +| [Ontologies ▼] [+ New] [Import] [Export] [Reasoner] | ++------------------+---------------------------------------+ +| Ontology Tree | Element Details | +| +--------------+ | +-----------------------------------+ | +| | ▼ Classes | | | Class: Document | | +| | ├─ Resource| | | ┌─ Basic Info ──────────────────┐ | | +| | │ └─ Doc. | | | │ URI: http://example.org/Doc │ | | +| | └─ Person | | | │ Labels: [en: Document_____] │ | | +| | ▼ Properties | | | │ [+ Add language] │ | | +| | ├─ Object | | | │ Parent: [Resource_____▼] │ | | +| | │ └─ has..| | | │ Disjoint: [Person, Org.] │ | | +| | └─ Datatype| | | │ External ID: [DOC-001____] │ | | +| | [+ Add Class]| | | └─────────────────────────────────┘ | | +| | [🔍 Search_] | | | ┌─ Properties ───────────────────┐ | | +| +--------------+ | | │ Domain Properties: │ | | +| | | │ • title (string) │ | | +| | | │ • hasAuthor → Person │ | | +| | | │ [+ Add Property] │ | | +| | | └─────────────────────────────────┘ | | ++------------------+---------------------------------------+ +``` + +#### Core UI Components + +**1. Ontology Selector** +- Dropdown showing all available ontologies +- Quick actions: New, Clone, Delete +- Status indicators: Modified, Validated, Consistent + +**2. Ontology Tree** +- Dual-section tree: Classes and Properties +- Expandable class hierarchy with inheritance visualization +- Property organization by type (Object/Datatype) +- Visual indicators: ✓ Complete, ⚠ Missing constraints, ❌ Inconsistencies +- Context menu: Add Subclass, Add Property, Edit, Delete, Move +- Search with highlighting and filtering + +**3. Element Detail Panel** +- Context-sensitive form based on selection (Class or Property): + - **For Classes**: + - URI, labels (multi-language), parent class + - Equivalent/disjoint classes + - Properties with this class as domain + - External identifiers + - **For Properties**: + - Type (Object/Datatype) + - Domain and range specifications + - Cardinality constraints + - Inverse relationships + - Functional property settings + +**4. AI Assistant Panel (Optional)** +- Toggle on/off for manual-only workflow +- When enabled, provides: + - Context-aware class and property suggestions + - Bulk operations: "Generate properties for all classes" + - Smart templates: "Create subclasses for [parent]" + - Consistency checking: "Find logical conflicts" + - Domain/range recommendations +- Can be hidden completely for users preferring manual creation + +### AI Assistant Features (Optional Wizard) + +The AI Assistant is designed as an optional "getting started" wizard that helps users bootstrap their ontologies. Users can: +- Choose to start with the AI wizard or create ontologies manually from scratch +- Toggle AI assistance on/off at any point during editing +- Use AI for specific tasks (e.g., generating properties) while doing everything else manually +- Skip the wizard entirely and build ontologies using traditional manual methods + +#### Content Generation Capabilities + +**Class Definition Assistant** +- Analyzes class names and inheritance structure +- Suggests appropriate parent classes +- Recommends disjoint class relationships +- Generates class descriptions and documentation + +**Property Generator** +- Creates relevant object and datatype properties for classes +- Suggests appropriate domains and ranges +- Recommends cardinality constraints +- Identifies potential inverse relationships + +**Type and Constraint Suggestions** +- Recommends appropriate XSD datatypes for properties +- Suggests functional property declarations +- Identifies candidates for inverse functional properties +- Detects potential consistency issues + +**Bulk Enhancement** +- Processes multiple classes simultaneously +- Maintains consistency across the ontology +- Fills in missing domains/ranges systematically +- Provides logical consistency checking + +#### Implementation Details + +**Assistant Integration:** +```javascript +const assistantService = { + generateClassProperties: async (className, context) => { + // Analyze class name and context to suggest properties + // Return object and datatype properties with domains/ranges + }, + + suggestDomainRange: async (propertyName, ontologyContext) => { + // Recommend appropriate domain classes and range types + // Consider existing class hierarchy and property patterns + }, + + expandClassHierarchy: async (parentClass, depth = 2) => { + // Suggest logical subclasses + // Maintain naming consistency and inheritance logic + }, + + validateConsistency: async (ontology) => { + // Check for logical inconsistencies + // Identify missing constraints or conflicting definitions + } +}; +``` + +### APIs + +#### Using the Existing Config API + +The ontology management system uses the existing configuration API with type `ontology`. No new API endpoints are needed - all operations go through the existing socket-based config API. + +**React Hook Implementation (following the established pattern):** + +```typescript +// src/state/ontologies.ts +import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query"; +import { useSocket } from "../api/trustgraph/socket"; +import { useNotification } from "./notify"; +import { useActivity } from "./activity"; + +export const useOntologies = () => { + const socket = useSocket(); + const queryClient = useQueryClient(); + const notify = useNotification(); + + // Fetch all ontologies using getValues + const ontologiesQuery = useQuery({ + queryKey: ["ontologies"], + queryFn: () => { + return socket + .config() + .getValues("ontology") + .then((values) => { + // Returns array of [id, ontologyData] tuples + return values.map((item) => [item.key, JSON.parse(item.value)]); + }) + .catch((err) => { + console.log("Error:", err); + throw err; + }); + }, + }); + + // Create/Update ontology mutation + const updateOntologyMutation = useMutation({ + mutationFn: ({ id, ontology, onSuccess }) => { + return socket + .config() + .putConfig([ + { + type: "ontology", + key: id, + value: JSON.stringify(ontology), + }, + ]) + .then((x) => { + if (x["error"]) { + console.log("Error:", x); + throw x.error.message; + } + if (onSuccess) onSuccess(); + }); + }, + onError: (err) => { + console.log("Error:", err); + notify.error(err.toString()); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["ontologies"] }); + notify.success("Ontology updated"); + }, + }); + + // Delete ontology mutation + const deleteOntologyMutation = useMutation({ + mutationFn: ({ id, onSuccess }) => { + return socket + .config() + .deleteConfig([ + { + type: "ontology", + key: id, + }, + ]) + .then((x) => { + if (x["error"]) { + console.log("Error:", x); + throw x.error.message; + } + if (onSuccess) onSuccess(); + }); + }, + onError: (err) => { + console.log("Error:", err); + notify.error(err.toString()); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["ontologies"] }); + notify.success("Ontology deleted"); + }, + }); + + // Track loading states + useActivity(ontologiesQuery.isLoading, "Loading ontologies"); + useActivity(updateOntologyMutation.isPending, "Updating ontology"); + useActivity(deleteOntologyMutation.isPending, "Deleting ontology"); + + return { + ontologies: ontologiesQuery.data || [], + ontologiesLoading: ontologiesQuery.isLoading, + ontologiesError: ontologiesQuery.error, + + updateOntology: updateOntologyMutation.mutate, + isUpdatingOntology: updateOntologyMutation.isPending, + + createOntology: updateOntologyMutation.mutate, // Same as update + isCreatingOntology: updateOntologyMutation.isPending, + + deleteOntology: deleteOntologyMutation.mutate, + isDeletingOntology: deleteOntologyMutation.isPending, + + refetch: () => ontologiesQuery.refetch(), + }; +}; +``` + +**AI Assistant APIs:** + +The AI assistant functionality will use the existing LLM integration through the socket API. No new endpoints needed - the assistant will use the existing prompt/completion mechanisms. + +### Table Component Implementation + +Following the established pattern using Tanstack Table: + +**Table Column Definition:** + +```typescript +// src/model/ontologies-table.tsx +import { createColumnHelper } from "@tanstack/react-table"; + +export type OntologyTableRow = [string, { + metadata: { + name: string; + description: string; + version: string; + modified: string; + creator: string; + namespace: string; + }; + classes: Record; + objectProperties: Record; + datatypeProperties: Record; +}]; + +const columnHelper = createColumnHelper(); + +export const ontologyColumns = [ + columnHelper.accessor((row) => row[0], { + id: "id", + header: "ID", + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row[1].metadata.name, { + id: "name", + header: "Name", + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row[1].metadata.description, { + id: "description", + header: "Description", + cell: (info) => info.getValue() || "-", + }), + columnHelper.accessor((row) => Object.keys(row[1].classes || {}).length, { + id: "classCount", + header: "Classes", + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => + Object.keys(row[1].objectProperties || {}).length + + Object.keys(row[1].datatypeProperties || {}).length, { + id: "propertyCount", + header: "Properties", + cell: (info) => info.getValue(), + }), + columnHelper.accessor((row) => row[1].metadata.modified, { + id: "modified", + header: "Last Modified", + cell: (info) => new Date(info.getValue()).toLocaleDateString(), + }), +]; +``` + +**Table Component:** + +```typescript +// src/components/ontologies/OntologiesTable.tsx +import React from "react"; +import { Box, Table, Text, Spinner, Center } from "@chakra-ui/react"; +import { + useReactTable, + getCoreRowModel, + getSortedRowModel, + flexRender, +} from "@tanstack/react-table"; +import { useOntologies } from "../../state/ontologies"; +import { OntologyTableRow, ontologyColumns } from "../../model/ontologies-table"; +import { EditOntologyDialog } from "./EditOntologyDialog"; + +export const OntologiesTable: React.FC = () => { + const { ontologies, ontologiesLoading, ontologiesError } = useOntologies(); + const [isOpen, setIsOpen] = React.useState(false); + const [selectedOntology, setSelectedOntology] = + React.useState(null); + + const table = useReactTable({ + data: ontologies as OntologyTableRow[], + columns: ontologyColumns, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + }); + + const handleRowClick = (row: OntologyTableRow) => { + setSelectedOntology(row); + setIsOpen(true); + }; + + if (ontologiesLoading) { + return ( +
+ +
+ ); + } + + if (ontologiesError) { + return ( + + + Error loading ontologies: {ontologiesError.toString()} + + + ); + } + + if (ontologies.length === 0) { + return ( +
+ + No ontologies found. Create one to get started. + +
+ ); + } + + return ( + <> + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + handleRowClick(row.original)} + style={{ cursor: "pointer" }} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + ))} + + + + + {selectedOntology && ( + setIsOpen(false)} + mode="edit" + ontologyId={selectedOntology[0]} + initialOntology={selectedOntology[1]} + /> + )} + + ); +}; +``` + +### Validation and Quality Assurance + +#### OWL Compliance and Reasoning + +**Structural Validation:** +- Verify proper ontology structure and namespace declarations +- Check for circular class inheritance +- Validate URI consistency and uniqueness +- Ensure required OWL/RDFS properties are present + +**Type and Constraint Checking:** +- Validate domain and range specifications +- Check cardinality constraint consistency +- Verify datatype property ranges against XSD types +- Detect conflicting functional property declarations + +**Logical Consistency:** +- Check for unsatisfiable classes +- Validate disjoint class declarations +- Ensure property inverse relationships are bidirectional +- Detect inconsistent equivalence relationships + +**Reasoning and Inference:** +- Support for OWL reasoners (HermiT, Pellet) +- Automatic classification of inferred relationships +- Detection of implicit contradictions +- Validation of transitivity and symmetry + +#### Real-time Validation + +```javascript +const validator = { + validateClass: (owlClass, ontology) => { + return { + isValid: boolean, + errors: [{ field, message, severity }], + warnings: [{ field, message, suggestion }], + missingProperties: [propertyNames], + completeness: percentage + }; + }, + + validateProperty: (property, ontology) => { + return { + isValid: boolean, + domainIssues: [issues], + rangeIssues: [issues], + cardinalityConflicts: [conflicts] + }; + }, + + validateOntology: (ontology) => { + return { + isConsistent: boolean, + unsatisfiableClasses: [classIds], + logicalErrors: [{ type, message, elements }], + inferredRelationships: [relationships], + qualityScore: percentage + }; + }, + + runReasoner: async (ontology) => { + // Integrate with OWL reasoner service + return { + inferences: [inferredFacts], + inconsistencies: [conflicts], + classification: hierarchyUpdates + }; + } +}; +``` + +### Import/Export Functionality + +#### Supported Formats + +**Turtle (.ttl)** +- Standard OWL/RDF representation +- Full property and constraint support +- Namespace management +- Human-readable format + +**RDF/XML (.rdf, .owl)** +- W3C standard OWL serialization +- Complete OWL feature support +- Tool compatibility (Protégé, etc.) +- Verbose but comprehensive + +**OWL/XML (.owx)** +- OWL-specific XML format +- Structured representation +- Easy parsing and validation +- Direct OWL construct mapping + +**JSON-LD (.jsonld)** +- Web-friendly JSON format +- Linked data compatibility +- API integration ready +- Compact representation + +#### Import Process + +1. **File Upload**: Drag-and-drop or file picker +2. **Format Detection**: Automatic format recognition +3. **Parsing**: Extract classes, properties, and relationships +4. **Preview**: Show ontology structure before import +5. **Conflict Resolution**: Handle duplicate URIs and conflicting definitions +6. **Validation**: Check OWL consistency and run reasoner +7. **Import Execution**: Create configuration entries with full ontology + +### Security Considerations + +**Access Control:** +- Integrate with existing user authentication +- Role-based permissions for ontology editing +- Audit trail for all modifications +- Version control for ontology changes + +**Data Validation:** +- Sanitize all user inputs +- Validate file uploads for malicious content +- Limit ontology size and complexity +- Rate limiting for AI assistant and reasoner requests + +### Performance Considerations + +**Frontend Optimization:** +- Virtual scrolling for large ontologies +- Lazy loading of class and property details +- Debounced search and filtering +- Optimistic UI updates with rollback +- Incremental reasoning for real-time validation + +**Backend Efficiency:** +- Efficient configuration storage queries +- Caching for frequently accessed ontologies +- Batch operations for bulk modifications +- Async processing for large imports +- Background reasoning for complex ontologies + +### Testing Strategy + +**Unit Testing:** +- Component testing for UI elements +- OWL parser/serializer validation +- AI assistant response handling +- Import/export functionality +- Property constraint validation + +**Integration Testing:** +- Configuration API integration +- Full ontology CRUD operations +- Import/export round-trip testing +- AI assistant service integration +- Reasoner integration testing + +**User Acceptance Testing:** +- Data architect workflow validation +- Ontology creation scenarios +- Class and property definition workflows +- Constraint specification and validation +- Import/export with real OWL files + +## UX Refactoring Plan + +### Problem Statement + +The current schema creation flow has significant duplication between the creation dialog and the main editor: +- The creation dialog allows entering concepts/fields which duplicates the editor functionality +- Users are forced to work in a constrained dialog when they could be using the full editor +- The transition from creation to editing is jarring and requires mental context switching + +### Solution: Streamlined Creation Flow + +#### 1. Minimal Creation Dialog + +The "Create New Ontology" dialog should only capture the absolute minimum needed to create an ontology: + +``` ++---------------------------------------------+ +| Create New Ontology [X] | ++---------------------------------------------+ +| Ontology ID: [___________________] | +| | +| Name: [___________________] | +| | +| Description: [___________________] | +| [___________________] | +| | +| Namespace URI: [http://example.org/____] | +| | +| ○ Start with blank ontology | +| ○ Use AI assistant to bootstrap | +| ○ Import from file | +| | +| [Cancel] [Create & Open Editor] | ++---------------------------------------------+ +``` + +**Key Features:** +- Only 4 required fields: ID, name, description, namespace +- Three clear starting options +- Single action: "Create & Open Editor" +- No concept/class creation in dialog + +#### 2. Main Ontology Editor Workflow + +After creation, immediately open the full ontology editor: + +``` ++----------------------------------------------------------+ +| Ontology Editor: Domain Ontology [Save] | ++----------------------------------------------------------+ +| [Classes] [Properties] [Imports] [Prefixes] [Settings] | ++----------------------------------------------------------+ +| Welcome Panel (shown for new ontologies): | +| ┌────────────────────────────────────────────────────┐ | +| │ Getting Started with Your Ontology │ | +| │ │ | +| │ Your ontology is ready! Here's what to do next: │ | +| │ │ | +| │ [+ Add First Class] Start by defining object types │ | +| │ [⚡ Use AI Assistant] Let AI suggest initial classes │ | +| │ [📁 Import Classes] Load from existing ontology │ | +| │ │ | +| │ [Dismiss] [Show me around 🎯] │ | +| └────────────────────────────────────────────────────┘ | ++----------------------------------------------------------+ +``` + +**Key Benefits:** +- Full editor context immediately available +- Welcome panel provides guidance without constraining +- Can be dismissed to start working immediately +- All tools accessible from the start + +#### 3. Editor-First Design Principles + +**Immediate Context:** +- User lands in the full editor, not a dialog +- All ontology management happens in one place +- No artificial separation between creation and editing + +**Progressive Disclosure:** +- Start simple with empty ontology +- Reveal complexity as user adds elements +- Contextual help and suggestions based on current state + +**Persistent Workspace:** +- Auto-save drafts during editing +- Can leave and return without losing work +- Version history for rollback + +### Implementation Phases + +#### Phase 1: Simplify Creation Dialog +- Remove all field/concept creation from dialog +- Reduce to just core metadata fields +- Add starting mode selection (blank/AI/import) + +#### Phase 2: Enhance Editor Landing +- Create welcome panel component +- Implement guided first steps +- Add contextual empty states + +#### Phase 3: Improve Editor Navigation +- Tab-based navigation for different aspects +- Breadcrumb trail for deep navigation +- Quick access toolbar for common actions + +#### Phase 4: AI Integration Points +- AI assistant panel (collapsible) +- Inline suggestions in editors +- Bulk generation workflows + +### Detailed Component Changes + +#### CreateOntologyDialog Component + +```typescript +// Simplified creation dialog +interface CreateOntologyDialogProps { + isOpen: boolean; + onClose: () => void; + onCreated: (ontologyId: string) => void; // Callback to open editor +} + +// Only captures: +// - id: string (kebab-case, unique) +// - name: string (display name) +// - description: string (brief description) +// - namespace: string (base URI) +// - startMode: 'blank' | 'ai-assisted' | 'import' +``` + +#### OntologyEditor Component + +```typescript +// Full-featured editor +interface OntologyEditorProps { + ontologyId: string; + showWelcome?: boolean; // Show welcome panel for new ontologies +} + +// Features: +// - Multi-tab interface (Classes, Properties, etc.) +// - Split-pane layout (tree + details) +// - Integrated AI assistant (optional) +// - Real-time validation +// - Import/Export toolbar +``` + +### Benefits of This Approach + +1. **Reduced Cognitive Load**: Users only think about metadata during creation +2. **Faster Time to Value**: Get into the editor immediately +3. **Consistent Context**: All ontology work happens in the editor +4. **Better Discoverability**: Full UI reveals all capabilities +5. **Natural Workflow**: Create → Edit → Refine (no back-and-forth) + +### Migration from Current System + +1. **Preserve Existing Schemas**: Convert to OWL format behind the scenes +2. **Dual Mode Support**: Keep old schema editor while building new ontology editor +3. **Gradual Transition**: Users can opt-in to new editor +4. **Data Migration Tools**: Automated conversion utilities + +### Success Metrics + +- Time from "Create" click to first class/property added +- Number of back-navigations during creation flow +- User feedback on creation experience +- Completion rate of ontology creation +- Feature discovery and usage + +### Migration Plan + +**Phase 1: Core UI** +- Basic ontology management interface +- Configuration API integration +- Class and property CRUD operations + +**Phase 2: Advanced Features** +- Class hierarchy visualization +- Property domain/range specification +- Cardinality and constraint management +- Multi-language label support + +**Phase 3: AI Assistant Wizard** +- Optional LLM integration +- "Getting started" wizard flow +- Property generation features +- Consistency checking tools +- Manual-only mode support + +**Phase 4: Reasoning & Import/Export** +- OWL reasoner integration +- File format support (Turtle, RDF/XML, OWL/XML) +- Import/export workflows +- Inference visualization + +### Open Questions + +- Do we need integration with external ontology repositories (e.g., BioPortal, schema.org)? Not needed currently. +- Should the system support ontology versioning and branching? No. +- How should we handle very large ontologies (1000+ classes)? No explicit support needed at the moment. +- Do we need offline editing capabilities? No. +- Which OWL profiles should we support (OWL Lite, DL, Full)? Start with OWL DL for decidable reasoning. + +### References + +- [OWL 2 Web Ontology Language Reference](https://www.w3.org/TR/owl2-overview/) +- [RDF Schema 1.1](https://www.w3.org/TR/rdf-schema/) +- [RDF/XML Syntax Specification](https://www.w3.org/TR/rdf-syntax-grammar/) +- [OWL 2 XML Serialization](https://www.w3.org/TR/owl2-xml-serialization/) +- [JSON-LD 1.1 Specification](https://www.w3.org/TR/json-ld11/) +- [Turtle RDF Syntax](https://www.w3.org/TR/turtle/) +- [Dublin Core Metadata Terms](https://www.dublincore.org/specifications/dublin-core/dcmi-terms/) +- [XML Schema Datatypes](https://www.w3.org/TR/xmlschema-2/) + diff --git a/docs/tech-specs/schema.md b/docs/tech-specs/schema.md new file mode 100644 index 00000000..e902185d --- /dev/null +++ b/docs/tech-specs/schema.md @@ -0,0 +1,150 @@ +# Schema Support for TrustGraph UI + +## Overview + +This document specifies the UI work needed to support structured data schemas in TrustGraph. Schemas enable the system to work with structured data (rows in tables/objects) alongside unstructured data processing. + +## Schema Representation + +Based on the STRUCTURED_DATA.md specification, schemas are stored in TrustGraph's configuration system with: + +- **Type**: `schema` (fixed value for all structured data schemas) +- **Key**: Unique schema identifier (e.g., `customer_records`, `transaction_log`) +- **Value**: JSON schema definition + +### Schema Structure Example: +```json +{ + "name": "customer_records", + "description": "Customer information table", + "fields": [ + { + "name": "customer_id", + "type": "string", + "primary_key": true + }, + { + "name": "name", + "type": "string", + "required": true + }, + { + "name": "email", + "type": "string", + "required": true + }, + { + "name": "registration_date", + "type": "timestamp" + }, + { + "name": "status", + "type": "string", + "enum": ["active", "inactive", "suspended"] + } + ], + "indexes": ["email", "registration_date"] +} +``` + +### Field Types Supported: +- `string` +- `integer` +- `float` +- `boolean` +- `timestamp` +- `enum` (with predefined values) + +### Field Properties: +- `name`: Field identifier +- `type`: Data type +- `primary_key`: Boolean flag for primary key fields +- `required`: Boolean flag for required fields +- `enum`: Array of allowed values for enum types + +## Requirements + +Based on the Prompts page implementation pattern, the Schema UI should provide: + +1. **Schema Management Page** + - List all schemas in a table view + - Create new schemas via modal dialog + - Edit existing schemas + - Delete schemas with confirmation + - View schema details in a readable format + +2. **UI Components Needed** + - Main schemas page with table listing + - Create/Edit schema dialog with form validation + - Schema field editor (add/remove/edit fields) + - Field type selector with appropriate options + - Primary key and index configuration + - Schema preview/viewer component + +3. **State Management** + - Use React Query for data fetching and mutations + - Implement CRUD operations following the prompts pattern + - Handle loading states and error notifications + - Cache management and invalidation + +## Implementation Details + +### API Integration Pattern (from Prompts example) + +1. **Configuration Keys** + - Individual schemas: `{ type: "schema", key: "{schema_id}" }` + - List all schemas by querying all keys with `type: "schema"` + +2. **State Management Hook** (`useSchemas`) + - `getValues("schema")` to list all schemas (returns array of {key, value} objects) + - `putConfig()` to create/update schemas + - `deleteConfig()` to remove schemas + - No need for separate index management + +3. **Component Structure** + - `SchemasPage.tsx` - Main page component + - `components/schemas/Schemas.tsx` - Container component + - `components/schemas/SchemasTable.tsx` - List view + - `components/schemas/SchemaControls.tsx` - Action buttons + - `components/schemas/EditSchemaDialog.tsx` - Create/Edit form + - `components/schemas/SchemaViewer.tsx` - Read-only schema display + - `state/schemas.ts` - React Query hooks + - `model/schemas-table.tsx` - TypeScript definitions + +4. **Field Editor Requirements** + - Dynamic field list with add/remove capabilities + - Field property editors: + - Name (text input) + - Type (dropdown: string, integer, float, boolean, timestamp, enum) + - Primary key (checkbox) + - Required (checkbox) + - Enum values (list editor, shown only for enum type) + - Index configuration (multi-select from available fields) + +5. **Validation Rules** + - Schema name: Required, unique + - At least one field required + - At least one primary key field + - Field names must be unique within schema + - Enum type requires at least one enum value + +## Tasks + +1. Create schema state management hook (`useSchemas`) +2. Implement SchemasPage and routing +3. Build SchemasTable component with sorting/filtering +4. Create EditSchemaDialog with field editor +5. Add schema validation logic +6. Implement schema viewer component +7. Add TypeScript models and table configurations +8. Integration testing with backend API + +## Notes + +- Follow the existing Prompts page pattern for consistency +- Use Chakra UI components matching current design system +- Implement proper error handling and user feedback +- Consider adding import/export functionality for schemas +- May need to handle schema versioning in the future +- Implementation is simpler than prompts since we use `getValues("schema")` instead of maintaining a separate index +- Reference the agent-tools implementation pattern which also uses `getValues()` directly diff --git a/docs/tech-specs/settings.md b/docs/tech-specs/settings.md new file mode 100644 index 00000000..1a1e5b24 --- /dev/null +++ b/docs/tech-specs/settings.md @@ -0,0 +1,232 @@ +# Settings Page for TrustGraph UI + +## Overview + +This document specifies the implementation of a Settings page for the TrustGraph UI application. The Settings page will provide a centralized interface for configuring application preferences, user settings, and system-wide configurations. + +## Requirements + +The Settings page should provide: + +1. **Settings Management Interface** + - Centralized location for all user and system settings + - Organized into logical sections/categories using visual grouping + - Real-time save functionality with visual feedback + - Reset to defaults capability + - Import/export settings configuration + +2. **Settings Categories** + - **Authentication**: API key configuration for TrustGraph socket authentication + - **GraphRAG Configuration**: Entity limits, triple limits, and graph traversal settings + - **Feature Switches**: Toggle switches for advanced/experimental functionality + +3. **Specific Settings** + + **Authentication Section**: + - **API Key**: Text input field (password type for security) + - Default: empty string (no authentication) + - When set: used for TrustGraph socket authentication + - Should mask the key value when displayed + + **GraphRAG Settings Section**: + - **Entity Limit**: Number input (default: 50) + - **Triple Limit**: Number input (default: 30) + - **Max Subgraph Size**: Number input (default: 1000) + - **Path Length**: Number input (default: 2) + + **Feature Switches Section**: + - **Taxonomy Editor**: Boolean toggle (default: false) + - **Submissions**: Boolean toggle (default: false) + +3. **Navigation Integration** + - Add settings route at the end of the sidebar navigation + - Use Settings icon from lucide-react + - Standard page structure with PageHeader + +## Implementation Details + +### Routing Integration + +**Sidebar Addition** (src/components/Sidebar.tsx): +- Add import: `Settings` from lucide-react +- Add NavItem at end of VStack: `` + +**Route Configuration**: +- Add route in main router configuration +- Path: `/settings` +- Component: `SettingsPage` + +### Component Structure + +Following the established patterns from UI-TOOLKITS.md: + +``` +src/ +├── pages/ +│ └── SettingsPage.tsx # Main page with PageHeader +├── components/ +│ └── settings/ +│ ├── Settings.tsx # Main container component +│ ├── SettingsForm.tsx # Settings form management +│ ├── AuthenticationSection.tsx # API key configuration +│ ├── GraphRagSection.tsx # GraphRAG settings +│ ├── FeatureSwitchesSection.tsx # Feature toggles +│ └── SettingsControls.tsx # Action buttons (save, reset, import/export) +├── state/ +│ └── settings.ts # Settings state management with localStorage +└── model/ + └── settings-types.ts # TypeScript definitions for settings +``` + +### UI Framework Considerations + +Based on UI-TOOLKITS.md guidelines: + +**Chakra UI v3 Components**: +- Use `Field.Root` and `Field.Label` for form inputs +- Use common components: `TextField`, `SelectField`, `Card` +- Use `Alert.Root` for validation feedback +- Follow semantic color tokens (`primary`, `accent`, etc.) + +**Icons**: +- Use `Settings` from lucide-react (already established pattern) +- Other icons as needed: `Save`, `RotateCcw`, `Download`, `Upload` + +**Notifications**: +- Use `useNotification` hook (NOT direct toaster) +- Provide success/error feedback for save operations + +### State Management Pattern + +Following the established React Query pattern: + +**Settings State Hook** (`useSettings`): +- `getSettings()` to retrieve current settings from localStorage +- `updateSetting()` to modify individual settings and persist to localStorage +- `resetSettings()` to restore defaults and clear localStorage +- `exportSettings()` and `importSettings()` for configuration management +- Handle localStorage serialization/deserialization +- Provide default values when localStorage is empty + +**Data Storage**: +- **Browser localStorage**: All settings stored in browser's localStorage +- Settings persist across browser sessions +- Settings are client-side only (no server synchronization) +- Use structured key naming for organized storage + +### Testing Strategy + +Based on TEST_STRATEGY.md: + +**Component Tests**: +- SettingsForm validation and state management +- Settings section rendering and interaction +- Import/export functionality +- Reset to defaults behavior + +**Integration Tests**: +- Settings persistence across sessions +- Settings application to other components +- Route navigation and sidebar integration + +**Test Data**: +```tsx +const mockSettings = { + authentication: { + apiKey: '' // Empty by default + }, + graphrag: { + entityLimit: 50, + tripleLimit: 30, + maxSubgraphSize: 1000, + pathLength: 2 + }, + featureSwitches: { + taxonomyEditor: false, + submissions: false + } +}; +``` + +## Tasks + +1. **Foundation Setup** + - Create SettingsPage component with PageHeader + - Add routing integration and sidebar navigation + - Set up basic component structure + +2. **State Management** + - Implement settings state hook with localStorage integration + - Define settings data model with typed interfaces + - Create default settings configuration + - Handle localStorage persistence and retrieval + +3. **UI Implementation** + - Build AuthenticationSection with masked API key input + - Create GraphRagSection with NumberField components for limits + - Implement FeatureSwitchesSection with toggle switches + - Add visual grouping with Card components for each section + - Implement form validation and submission + - Add import/export functionality + - Create reset to defaults mechanism + +4. **Integration & Testing** + - Add route configuration + - Implement component tests + - Add integration tests for settings persistence + - Verify UI consistency with design system + +## Data Model + +### Settings Structure +```tsx +interface Settings { + authentication: { + apiKey: string; // Default: '' + }; + graphrag: { + entityLimit: number; // Default: 50 + tripleLimit: number; // Default: 30 + maxSubgraphSize: number; // Default: 1000 + pathLength: number; // Default: 2 + }; + featureSwitches: { + taxonomyEditor: boolean; // Default: false + submissions: boolean; // Default: false + }; +} +``` + +### LocalStorage Keys +- Main settings: `trustgraph-settings` +- Backup/versioning: Consider `trustgraph-settings-backup` for import/export + +## Integration Points + +### API Key Integration +- Settings API key should be used by TrustGraph socket authentication +- When API key is empty, no authentication is used +- When API key has value, it's passed to socket connection for authentication + +### Feature Switches Integration +- **Taxonomy Editor**: Controls visibility of taxonomy-related routes/components +- **Submissions**: Controls visibility of submissions/processing routes/components +- Features should be conditionally rendered based on these settings + +## Notes + +- **Security**: API key should be masked in UI but stored as plaintext in localStorage +- **Visual Grouping**: Use Card components to separate the three main sections +- **Real-time Updates**: Settings changes should be immediately persisted to localStorage +- **Validation**: Number inputs should have min/max constraints and validation +- **Accessibility**: Ensure full keyboard navigation and screen reader support +- **Responsive**: Settings should work well on mobile and desktop layouts + +## Future Considerations + +- User-specific vs. system-wide settings +- Settings synchronization across devices +- Advanced settings with warnings/confirmations +- Settings search/filter capability +- Bulk settings operations +- Settings versioning and migration \ No newline at end of file diff --git a/docs/tech-specs/socket-reliability.md b/docs/tech-specs/socket-reliability.md new file mode 100644 index 00000000..1081dea8 --- /dev/null +++ b/docs/tech-specs/socket-reliability.md @@ -0,0 +1,289 @@ +# Socket Reliability Refactor + +## Overview + +This document outlines a comprehensive refactor to address critical issues in the TrustGraph UI WebSocket connection handling that are causing exponential retry storms and excessive logging. + +## Current Problems + +### Issue #1: Dual Retry System Conflict ⚠️ CRITICAL + +**Problem**: Two independent retry mechanisms create multiplicative retry storms: + +1. **BaseApi Socket-Level Reconnection** (`trustgraph-socket.ts`) + - Triggers on `onClose()` events + - 10 attempts with exponential backoff (2-60 seconds) + - Handles socket-level connection failures + +2. **ServiceCall Request-Level Retries** (`service-call.ts`) + - Triggers on send failures and timeouts + - 3 retries per request with backoff + - **Calls `socket.reopen()` which triggers BaseApi reconnection** + +**Result**: Single connection failure → 3 request retries × 10 socket reconnections = **30+ retry attempts** + +```typescript +// service-call.ts:160, 174 - PROBLEM LINES +console.log("Reopen..."); +this.socket.reopen(); // ← Triggers BaseApi reconnection +``` + +### Issue #2: SocketProvider Dependency Loop ✅ FIXED +**Status**: Resolved by removing `socket` from dependency array + +### Issue #3: Inconsistent Request Retry Backoff ⚠️ MEDIUM +**Location**: `service-call.ts:170` + +```typescript +// Inconsistent retry strategies: +setTimeout(this.attempt.bind(this), backoffDelay); // Exponential backoff ✅ +setTimeout(this.attempt.bind(this), 500); // Fixed 500ms ❌ (spams) +setTimeout(this.attempt.bind(this), backoffDelay); // Exponential backoff ✅ +``` + +### Issue #4: Concurrent Socket Reopen Calls ⚠️ MEDIUM +**Problem**: Multiple failed requests simultaneously call `socket.reopen()`: +- No coordination between ServiceCalls +- Redundant reconnection attempts +- Race conditions in connection state + +## Proposed Solution: Centralized Retry Strategy + +### Architectural Decision + +**Adopt Option A: Let BaseApi handle ALL reconnection logic** + +**Rationale**: +- ✅ Single source of truth for connection state +- ✅ BaseApi already has robust exponential backoff +- ✅ Eliminates retry system conflicts +- ✅ Cleaner separation of concerns +- ✅ Minimal code changes required + +### Implementation Plan + +#### Phase 1: Remove ServiceCall Reconnection Triggers + +**File**: `src/api/trustgraph/service-call.ts` + +**Changes**: +1. Remove `this.socket.reopen()` calls (lines 160, 174) +2. Replace with passive waiting for socket reconnection +3. Standardize backoff for all retry paths + +```typescript +// BEFORE (service-call.ts:156-161) +console.log("Reopen..."); +this.socket.reopen(); // ← REMOVE THIS + +// AFTER +console.log("Message send failure, waiting for socket reconnection..."); +// Let BaseApi handle reconnection, just retry the request +``` + +#### Phase 2: Improve Request Queueing Strategy + +**Current Behavior**: ServiceCall attempts fail when socket is not ready + +**New Behavior**: ServiceCall waits for socket to become available + +```typescript +// Enhanced attempt() method logic +attempt() { + if (this.complete) return; + + this.retries--; + if (this.retries < 0) { + // Give up after retries exhausted + this.error("Ran out of retries"); + return; + } + + if (this.socket.ws && this.socket.ws.readyState === WebSocket.OPEN) { + // Socket ready - send message + try { + this.socket.ws.send(JSON.stringify(this.msg)); + this.timeoutId = setTimeout(this.onTimeout.bind(this), this.timeout); + } catch (e) { + // Send failed - wait and retry (no socket reopen) + setTimeout(this.attempt.bind(this), this.calculateBackoff()); + } + } else { + // Socket not ready - wait for BaseApi to reconnect + console.log("Request", this.mid, "waiting for socket reconnection..."); + setTimeout(this.attempt.bind(this), this.calculateBackoff()); + } +} + +calculateBackoff() { + return Math.min( + SOCKET_RECONNECTION_TIMEOUT * Math.pow(2, 3 - this.retries) + Math.random() * 1000, + 30000 + ); +} +``` + +#### Phase 3: Enhanced BaseApi Connection Management + +**File**: `src/api/trustgraph/trustgraph-socket.ts` + +**Improvements**: +1. Add connection state tracking +2. Prevent redundant reconnection attempts +3. Improve logging for debugging + +```typescript +class BaseApi { + reconnectionState: 'idle' | 'reconnecting' | 'failed' = 'idle'; + + scheduleReconnect() { + // Prevent concurrent reconnection attempts + if (this.reconnectionState === 'reconnecting') { + console.log("[socket] Reconnection already in progress, skipping"); + return; + } + + if (this.reconnectTimer) return; + + this.reconnectionState = 'reconnecting'; + // ... existing logic + } + + onOpen() { + console.log("[socket open]"); + this.reconnectAttempts = 0; + this.reconnectionState = 'idle'; // Reset state + + // Clear any pending reconnect timer + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = undefined; + } + } +} +``` + +## Expected Benefits + +### Immediate Impact +- **80-90% reduction in retry attempts** - eliminates dual retry system +- **Cleaner logs** - single source of reconnection messages +- **Predictable behavior** - one retry algorithm instead of two + +### Log Message Changes +``` +// BEFORE: Chaotic dual retry messages +[socket] Reconnecting in 2000ms (attempt 1) +Request test-123 timed out +Message send failure, retry... +Reopen... +[socket] Reconnecting in 4000ms (attempt 2) +Request test-123 ran out of retries +Request test-456 timed out +Reopen... +[socket] Reconnecting in 8000ms (attempt 3) + +// AFTER: Clean, coordinated messages +[socket] Reconnecting in 2000ms (attempt 1) +Request test-123 waiting for socket reconnection... +Request test-456 waiting for socket reconnection... +[socket open] +Request test-123 sent successfully +Request test-456 sent successfully +``` + +### Performance Improvements +- **Reduced CPU usage** - fewer concurrent timers and retry loops +- **Less network spam** - coordinated reconnection attempts +- **Better user experience** - faster recovery from connection issues + +## Risk Assessment + +### Low Risk Changes +- ✅ Removing `socket.reopen()` calls from ServiceCall +- ✅ Standardizing backoff calculations +- ✅ Adding connection state tracking + +### Potential Issues +- ⚠️ **Request timeout behavior may change** - requests may take longer to fail +- ⚠️ **Need to test edge cases** - rapid API key changes, server restarts +- ⚠️ **Verify inflight request cleanup** - ensure requests don't hang indefinitely + +### Mitigation Strategies +1. **Preserve existing timeout behavior** - requests should still timeout appropriately +2. **Add circuit breaker** - stop retrying after socket reconnection gives up +3. **Comprehensive testing** - test connection failure scenarios + +## Testing Strategy + +### Unit Tests +- Mock WebSocket state transitions +- Verify ServiceCall doesn't trigger socket reopens +- Test backoff calculations are consistent + +### Integration Tests +- Test connection failure and recovery scenarios +- Verify request queueing during reconnection +- Test concurrent request handling + +### Manual Testing Scenarios +1. **Server shutdown** - verify clean reconnection behavior +2. **Network interruption** - test mobile/wifi scenarios +3. **API key changes** - ensure proper socket recreation +4. **High load** - multiple concurrent requests during connection issues + +## Implementation Timeline + +### Phase 1: Core Fixes (1-2 hours) +- Remove `socket.reopen()` calls from ServiceCall +- Standardize ServiceCall backoff calculations +- Add basic connection state tracking + +### Phase 2: Enhanced Reliability (2-3 hours) +- Implement request queueing improvements +- Add comprehensive logging +- Enhanced error handling + +### Phase 3: Testing & Validation (2-4 hours) +- Unit test coverage +- Integration testing +- Performance validation + +**Total Estimated Effort**: 5-9 hours + +## Success Metrics + +### Quantitative Goals +- **Reduce retry attempts by 80%+** during connection failures +- **Eliminate concurrent socket reopen calls** +- **Standardize all retry backoff to exponential** + +### Qualitative Goals +- **Cleaner, more understandable logs** +- **Predictable connection recovery behavior** +- **Better separation of concerns in codebase** + +## Future Enhancements + +### Potential Improvements (Out of Scope) +1. **Request prioritization** - critical requests retry faster +2. **Connection health monitoring** - proactive reconnection +3. **Metrics collection** - track connection reliability +4. **Advanced queueing** - persist important requests across sessions + +### Monitoring Additions +```typescript +// Connection reliability metrics +interface SocketMetrics { + connectionAttempts: number; + successfulConnections: number; + averageReconnectionTime: number; + requestsLostDuringReconnection: number; +} +``` + +## Conclusion + +This refactor addresses the root cause of socket retry storms by establishing BaseApi as the single authority for connection management. The changes are surgical and low-risk, focusing on removing the problematic dual retry system while preserving all existing functionality. + +**Next Steps**: Implement Phase 1 changes and validate that retry storms are eliminated before proceeding with enhanced features. \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..7a981f2d --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,29 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["dist"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_" }, + ], + }, + }, +); diff --git a/index.html b/index.html new file mode 100644 index 00000000..ce7b96af --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + + TrustGraph + + + +
+ + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..d90ce585 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9485 @@ +{ + "name": "vite-ts", + "version": "1.0.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vite-ts", + "version": "1.0.2", + "dependencies": { + "@chakra-ui/react": "^3.19.1", + "@emotion/styled": "^11.13.0", + "@msgpack/msgpack": "^3.1.1", + "@tanstack/react-query": "^5.80.3", + "@tanstack/react-table": "^8.21.3", + "@trustgraph/client": "github:trustgraph-ai/trustgraph-client#master", + "@trustgraph/react-provider": "github:trustgraph-ai/trustgraph-react-provider#master", + "@trustgraph/react-state": "github:trustgraph-ai/trustgraph-react-state#d24cdec0", + "@types/dagre": "^0.7.53", + "compute-cosine-similarity": "^1.1.0", + "dagre": "^0.8.5", + "jszip": "^3.10.1", + "lucide-react": "^0.511.0", + "n3": "^1.26.0", + "next-themes": "^0.4.6", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-force-graph": ">=1.47.0", + "react-hotkeys-hook": "^5.1.0", + "react-icons": "^5.5.0", + "react-markdown-it": "^1.0.2", + "react-resize-detector": "^12.0.2", + "react-router": "^7.6.0", + "reactflow": "^11.11.4", + "streamsaver": "^2.0.6", + "three-spritetext": "^1.9.3", + "uuid": "^11.0.3", + "zustand": "^5.0.0-rc.2" + }, + "devDependencies": { + "@eslint/js": "^9.9.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^9.9.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "jsdom": "^26.1.0", + "prettier": "3.5.3", + "sass-embedded": "^1.77.8", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.1", + "vite": "^6.3.4", + "vitest": "^3.2.4" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ark-ui/react": { + "version": "5.26.2", + "resolved": "https://registry.npmjs.org/@ark-ui/react/-/react-5.26.2.tgz", + "integrity": "sha512-qB2i9AoxhXbADTo+LEphrf/mOnxJJP18ya/0lmoZ4LZ4/K7rY4gStPaj79gKJzIkLYpOO1G4sS5ycPk/Btmiig==", + "license": "MIT", + "dependencies": { + "@internationalized/date": "3.10.0", + "@zag-js/accordion": "1.26.3", + "@zag-js/anatomy": "1.26.3", + "@zag-js/angle-slider": "1.26.3", + "@zag-js/async-list": "1.26.3", + "@zag-js/auto-resize": "1.26.3", + "@zag-js/avatar": "1.26.3", + "@zag-js/bottom-sheet": "1.26.3", + "@zag-js/carousel": "1.26.3", + "@zag-js/checkbox": "1.26.3", + "@zag-js/clipboard": "1.26.3", + "@zag-js/collapsible": "1.26.3", + "@zag-js/collection": "1.26.3", + "@zag-js/color-picker": "1.26.3", + "@zag-js/color-utils": "1.26.3", + "@zag-js/combobox": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/date-picker": "1.26.3", + "@zag-js/date-utils": "1.26.3", + "@zag-js/dialog": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/editable": "1.26.3", + "@zag-js/file-upload": "1.26.3", + "@zag-js/file-utils": "1.26.3", + "@zag-js/floating-panel": "1.26.3", + "@zag-js/focus-trap": "1.26.3", + "@zag-js/highlight-word": "1.26.3", + "@zag-js/hover-card": "1.26.3", + "@zag-js/i18n-utils": "1.26.3", + "@zag-js/json-tree-utils": "1.26.3", + "@zag-js/listbox": "1.26.3", + "@zag-js/menu": "1.26.3", + "@zag-js/number-input": "1.26.3", + "@zag-js/pagination": "1.26.3", + "@zag-js/password-input": "1.26.3", + "@zag-js/pin-input": "1.26.3", + "@zag-js/popover": "1.26.3", + "@zag-js/presence": "1.26.3", + "@zag-js/progress": "1.26.3", + "@zag-js/qr-code": "1.26.3", + "@zag-js/radio-group": "1.26.3", + "@zag-js/rating-group": "1.26.3", + "@zag-js/react": "1.26.3", + "@zag-js/scroll-area": "1.26.3", + "@zag-js/select": "1.26.3", + "@zag-js/signature-pad": "1.26.3", + "@zag-js/slider": "1.26.3", + "@zag-js/splitter": "1.26.3", + "@zag-js/steps": "1.26.3", + "@zag-js/switch": "1.26.3", + "@zag-js/tabs": "1.26.3", + "@zag-js/tags-input": "1.26.3", + "@zag-js/timer": "1.26.3", + "@zag-js/toast": "1.26.3", + "@zag-js/toggle": "1.26.3", + "@zag-js/toggle-group": "1.26.3", + "@zag-js/tooltip": "1.26.3", + "@zag-js/tour": "1.26.3", + "@zag-js/tree-view": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.0.tgz", + "integrity": "sha512-fdRs9PSrBF7QUntpZpq6BTw58fhgGJojgg39m9oFOJGZT+nip9b0so5cYY1oWl5pvemDLr0cPPsH46vwThEbpQ==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@chakra-ui/react": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-3.28.0.tgz", + "integrity": "sha512-Rm9Fppqmc4NleAhNhbvE/fiA6IfSYpzRE5IQcQ39v27yFmWwSC8M1q7E5CYF36JJp1jmMG3CBPv4JRox/WESBg==", + "license": "MIT", + "dependencies": { + "@ark-ui/react": "^5.26.2", + "@emotion/is-prop-valid": "^1.4.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@pandacss/is-valid-prop": "^1.4.2", + "csstype": "^3.1.3", + "fast-safe-stringify": "^2.1.1" + }, + "peerDependencies": { + "@emotion/react": ">=11", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=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/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", + "peer": true, + "dependencies": { + "@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/@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/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", + "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", + "peer": true, + "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" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/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", + "peer": true + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "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/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", + "peer": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz", + "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", + "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "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/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@internationalized/date": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", + "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@msgpack/msgpack": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.2.tgz", + "integrity": "sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ==", + "license": "ISC", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "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", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pandacss/is-valid-prop": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@pandacss/is-valid-prop/-/is-valid-prop-1.4.3.tgz", + "integrity": "sha512-9xHAFaRGoXikp0SKUyTHdO97mjIRuGFLDqQ+zphaVeNjTIsyac+fZjOS/2jJNuTS1eybU0/ny1FZ3BpS6SkEqw==", + "license": "MIT" + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "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/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@reactflow/background": { + "version": "11.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz", + "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/background/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/@reactflow/controls": { + "version": "11.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", + "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/controls/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/@reactflow/core": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", + "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==", + "license": "MIT", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core/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/@reactflow/minimap": { + "version": "11.7.14", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", + "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/minimap/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/@reactflow/node-resizer": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", + "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.4", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-resizer/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/@reactflow/node-toolbar": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", + "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-toolbar/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/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.5.tgz", + "integrity": "sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.5.tgz", + "integrity": "sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@trustgraph/client": { + "version": "0.2.0", + "resolved": "git+ssh://git@github.com/trustgraph-ai/trustgraph-client.git#972d944b66c020c5d55ed6667a4ed8b0ee30f408", + "license": "Apache-2.0" + }, + "node_modules/@trustgraph/react-provider": { + "version": "0.2.0", + "resolved": "git+ssh://git@github.com/trustgraph-ai/trustgraph-react-provider.git#e0ea931a7f6184b218039401b09988f09f017572", + "license": "Apache-2.0", + "peerDependencies": { + "@trustgraph/client": "^0.2.0", + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@trustgraph/react-state": { + "version": "0.2.0", + "resolved": "git+ssh://git@github.com/trustgraph-ai/trustgraph-react-state.git#d24cdec00075b2797a9c417485d2cb6f6858f523", + "license": "MIT", + "dependencies": { + "@trustgraph/client": "github:trustgraph-ai/trustgraph-client#master", + "@trustgraph/react-provider": "github:trustgraph-ai/trustgraph-react-provider#master", + "compute-cosine-similarity": "^1.1.0", + "uuid": "^11.0.3" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.0.0", + "react": "^18.0.0", + "zustand": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "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", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.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" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.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.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.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/dagre": { + "version": "0.7.53", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.53.tgz", + "integrity": "sha512-f4gkWqzPZvYmKhOsDnhq/R8mO4UMcKdxZo+i5SCkOU1wvGeHJeUXGIHeE9pnwGyPMDof1Vx5ZQo4nxpeg2TTVQ==", + "license": "MIT" + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "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/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", + "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", + "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/type-utils": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.2", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", + "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", + "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.2", + "@typescript-eslint/types": "^8.46.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", + "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", + "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", + "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", + "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", + "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.2", + "@typescript-eslint/tsconfig-utils": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", + "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", + "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@zag-js/accordion": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/accordion/-/accordion-1.26.3.tgz", + "integrity": "sha512-xZgXTc4AhH0vha5zLjjSj3GZml2LFMhJnZ/F9TU0KEci7xML1U0n2ay4KydgtiEG31/A3j3LLE+vON+/N/0jAg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/anatomy": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/anatomy/-/anatomy-1.26.3.tgz", + "integrity": "sha512-2HL+BDX05zRtctKwrSlYEtxeLdiJZIV5SbEpqTNvbNJ9aZNxHKgg0ciEi0bYZe8qJ70TqfNfwWb2xwRamzedhg==", + "license": "MIT" + }, + "node_modules/@zag-js/angle-slider": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/angle-slider/-/angle-slider-1.26.3.tgz", + "integrity": "sha512-tmOcOLiKN+5enp2YobuZkhVCWY8Q78SnGVO4tJgj+IjZ1PU/EJwipAiAXRM1Adl/6MPIwIMe2ag5g+Qi9xIDuw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/rect-utils": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/aria-hidden": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/aria-hidden/-/aria-hidden-1.26.3.tgz", + "integrity": "sha512-F6P7Ff4iqly4VmxBRZaJoG09QonOIlYDmMRgHH8d8vca7aBmR5InTxy3iOjCZvX3U4xFFednEARRPjncy4Vl6Q==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3" + } + }, + "node_modules/@zag-js/async-list": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/async-list/-/async-list-1.26.3.tgz", + "integrity": "sha512-rcM4E8ITSpTANSgk6QX7TKbLyewE1iHOYIJ0NKvSLqfLqSXZgjYTVbWnOmt5q3UOhR3paCbxkEtSdX2/ZCNO/A==", + "license": "MIT", + "dependencies": { + "@zag-js/core": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/auto-resize": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/auto-resize/-/auto-resize-1.26.3.tgz", + "integrity": "sha512-/hIJGEHjknBnrbGjz7NqZbRGCldklJEvePMbMi5JnELZIFRCcCKnvaXwq7DS1mYODjsLqxBZRSL4y0hNxExk4Q==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3" + } + }, + "node_modules/@zag-js/avatar": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/avatar/-/avatar-1.26.3.tgz", + "integrity": "sha512-CGQunbcfHzDi1p1B51TN+sGLGaBgLTsgHpqKM8YbR385hMuULcUguOwcnx7rFBq/w79QsdrACwhz3LFlZOsyUQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/bottom-sheet": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/bottom-sheet/-/bottom-sheet-1.26.3.tgz", + "integrity": "sha512-qhlkSmBz/Ch2piku5h7itWNO0Uit4pTlhICVb8lLf0Q17X7SNxGYW+pMKAQMlL2M4EgA9tV1z/xE1pW4R05F6A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/aria-hidden": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/focus-trap": "1.26.3", + "@zag-js/remove-scroll": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/carousel": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/carousel/-/carousel-1.26.3.tgz", + "integrity": "sha512-zF7RD7SR/nzl2CDPIp06J3eMXbY2Vk4auMzo9R4puuGN3XSh4+KYmKRt6DH5XmkOd/fFknT3eHH2gNRlpxBaMQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/scroll-snap": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/checkbox": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/checkbox/-/checkbox-1.26.3.tgz", + "integrity": "sha512-7WcJChF9jKLr3e5aJfLNiMxmAA8iYt2ZEjEmmaL1KRDUyONpig9JMDX5mg4u3iXQvZdmsxCptIYHXwdYjZjr3Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/focus-visible": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/clipboard": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/clipboard/-/clipboard-1.26.3.tgz", + "integrity": "sha512-Cx8oafXtujYRlBU0XA2aXb78bs3JgNVZ4ikR6cm7Qo1AMUxh7FejIBNVaWGzHxUc5XrfliKGvQzJotyrGS3GDA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/collapsible": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/collapsible/-/collapsible-1.26.3.tgz", + "integrity": "sha512-vPnWRHolQXdXhP0GkNMADZsBuxhLqGzv/krrJitslYOwsKebtrYz3DrIvtLwpRPsED4igzNLv1ihHPa89kaOAA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/collection": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/collection/-/collection-1.26.3.tgz", + "integrity": "sha512-AYiIuZZ3Fr8eVH657uKGobf6WU5t7a4f/HfFiekwfvG0U0vsySBP7eOxukMvQJDCRNEKxARW9JyDj9pl4d7T8Q==", + "license": "MIT", + "dependencies": { + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/color-picker": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/color-picker/-/color-picker-1.26.3.tgz", + "integrity": "sha512-qsmgl1vHIavY+wH9jHLXlVnbSn+HBbIJy9ysC1vyM1nAM8Qm1P+h1PhojxgPkVujI6QcxSBzA+YcY56ZuSUHdw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/color-utils": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/color-utils": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/color-utils/-/color-utils-1.26.3.tgz", + "integrity": "sha512-II2mfISJc0xLupXHmfc5AABG0pZy0cYwn741yjlbe7orubWnFLZLmVCzLF4b0qpLzYM+c+MnRVAb/YV6t7Mw6A==", + "license": "MIT", + "dependencies": { + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/combobox": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/combobox/-/combobox-1.26.3.tgz", + "integrity": "sha512-CejmsLlKhxfSg0FkGMMS8JPGnCa3sCHv4hZrqRrBuhUJaLjiH5/VazV+WFlHEDUrSzVWUNeoYiZuqQlBWuIeCA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/aria-hidden": "1.26.3", + "@zag-js/collection": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/core": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/core/-/core-1.26.3.tgz", + "integrity": "sha512-NU/uBGEFE5kUDJfvayZuPom/mcvJjjIytBclIO4/dCt1IBo+C1ETbpQjG/RgpfmSjfAs2G/9pQgdqobakyWU2A==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/date-picker": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/date-picker/-/date-picker-1.26.3.tgz", + "integrity": "sha512-3JZckrr1EI01bEYfKeB4nAgI4f+bWZLQnfOq8LtiL4HnS0a31Z3DYFxk3r49X4dxg3B62j1tlb6DzdQJARaElg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/date-utils": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/live-region": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + }, + "peerDependencies": { + "@internationalized/date": ">=3.0.0" + } + }, + "node_modules/@zag-js/date-utils": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/date-utils/-/date-utils-1.26.3.tgz", + "integrity": "sha512-eoWJZzFY3fkTJU7rzFcRLGCecAnJiu1/xHrB317jgHd0eCGUrgJVY2m0h+agWUCIFtt1VdvEbcf9R9rZmQm7LQ==", + "license": "MIT", + "peerDependencies": { + "@internationalized/date": ">=3.0.0" + } + }, + "node_modules/@zag-js/dialog": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/dialog/-/dialog-1.26.3.tgz", + "integrity": "sha512-8fRmRdTrmX5o9IRCo9kpldlM+L4boesyU3mDYCGoCxymzUImg8Crr+8y3uUHEKFWdxnlisYfBGbC586kWaaCSQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/aria-hidden": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/focus-trap": "1.26.3", + "@zag-js/remove-scroll": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/dismissable": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/dismissable/-/dismissable-1.26.3.tgz", + "integrity": "sha512-THAzkq2KLYq7px+pdIZ4QQoRjF02nxa5WXXVyw+cv7yb+CNcemcNaPtAAIORJhKsQC+1XF1JNiT5TKrvhSprhw==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3", + "@zag-js/interact-outside": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/dom-query": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-1.26.3.tgz", + "integrity": "sha512-ZI/EzpWRDFjeSuXiHuCHwFUtFXX7ZNiBiOnsqR5AnbQSTEvYv+Oo803RRuQ+zHDpGSFyYluSnT00/zt4vskXDQ==", + "license": "MIT", + "dependencies": { + "@zag-js/types": "1.26.3" + } + }, + "node_modules/@zag-js/editable": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/editable/-/editable-1.26.3.tgz", + "integrity": "sha512-DdlBwOqoXbF+X0uM7k5mYxiKI4eDkX+BYar/DnEifnEgpsTkYMtl3J5B9WmuWmFtmSoTauScHQsVe3OmVCTqTA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/interact-outside": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/file-upload": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/file-upload/-/file-upload-1.26.3.tgz", + "integrity": "sha512-SAifHG71HmRyAUHlqj50xjMr0Ua0uDp8qOucgr7kP2xQvxp8ETfJ5LXrvf+IWv67bF4GmXgIH5Tn7OVEY6ETWQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/file-utils": "1.26.3", + "@zag-js/i18n-utils": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/file-utils": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/file-utils/-/file-utils-1.26.3.tgz", + "integrity": "sha512-chXv4dL4SeBdGuNvHlPRZDDsIqRZ1YHAX7kHTZhQbp8iqXSZR/XCnwnQg93b7pb6rG9P8oN+f/pYAuKfrulXbA==", + "license": "MIT", + "dependencies": { + "@zag-js/i18n-utils": "1.26.3" + } + }, + "node_modules/@zag-js/floating-panel": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/floating-panel/-/floating-panel-1.26.3.tgz", + "integrity": "sha512-Xue020m4gb9K/zboeW+r6bhSzqqwQpuGqfeFqgekP1XZTxGcEBDHO330gngi6hu7gO9DEMVr1sKinJKZ5kgQ2Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/rect-utils": "1.26.3", + "@zag-js/store": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/focus-trap": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/focus-trap/-/focus-trap-1.26.3.tgz", + "integrity": "sha512-mzvt9pzZ80T7O3TDcpUc7kybuWcOEO6yWvDUbJybJsMCKMvLG3HpH14gcPGOL41YZ7RTOh5PixdML8/Xb7/5Iw==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3" + } + }, + "node_modules/@zag-js/focus-visible": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-1.26.3.tgz", + "integrity": "sha512-gjcOCZeVlqDsCes3z5bmVLmVJQXhs+aMFs3+1JCmOUOT9zfLQ4HLlGfoesG3Zf2h/VUiIV6oZIJDLu3t/iUSig==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3" + } + }, + "node_modules/@zag-js/highlight-word": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/highlight-word/-/highlight-word-1.26.3.tgz", + "integrity": "sha512-AylY/vZMPzN4FfZeeURlMmyvS4ytQDvAYFOGLPwJjK9zajIuP7CuIa6DtkAIvhf1hfb4oHtH4MwHA2TA2hfUAQ==", + "license": "MIT" + }, + "node_modules/@zag-js/hover-card": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/hover-card/-/hover-card-1.26.3.tgz", + "integrity": "sha512-M6hEQHZnwx4PeGIl3GsMdGFtKvp2f+dYiDttqkhi5DCcWN4S3173mExK7MNFTgqFCGLTqX86w4eIyWRGBWrZ0Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/i18n-utils": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/i18n-utils/-/i18n-utils-1.26.3.tgz", + "integrity": "sha512-wFCVuUK3/GjOryQYPbsQMKlE7LX2sFgU0JccbzrvvKLe1fBCznESWrYjPd93hA+Dm6myQYQa9maxnJ2HiVB4vA==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3" + } + }, + "node_modules/@zag-js/interact-outside": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/interact-outside/-/interact-outside-1.26.3.tgz", + "integrity": "sha512-CBTagsGQ6QXaPISc8SS1wwjdpZNgMJt3ecYCIA0FP7QwUXNSnNVq7izxXwyoSzkO6xLkXS+kiqwd9guKTUBQ0Q==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/json-tree-utils": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/json-tree-utils/-/json-tree-utils-1.26.3.tgz", + "integrity": "sha512-SubdKHCnAqZAFd4H0YOCBLFS2ZKP9+eq6+sBlj/2M8hmFPltjSDQWHko5u+/RMo3nKae/FpxLu2yo4CsswKpHA==", + "license": "MIT" + }, + "node_modules/@zag-js/listbox": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/listbox/-/listbox-1.26.3.tgz", + "integrity": "sha512-oeD5s8R37xTo+sXIEAb4uNzu+RxTfSdojchg7WGBQMQfbgiTyyzSpZeQ1WpvdiRNiI7RREKfCBwNDEE+EWwwHA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/collection": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/focus-visible": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/live-region": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/live-region/-/live-region-1.26.3.tgz", + "integrity": "sha512-of7L/R3sdDmsRU3JhZ0azKWqveCBSsTU6yT9xDocY+m7g4MRyUtNfzaqpVDQeJI1S7BD6rGhL7podYYuRhJUvg==", + "license": "MIT" + }, + "node_modules/@zag-js/menu": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/menu/-/menu-1.26.3.tgz", + "integrity": "sha512-92GHkFKW6PdG8DUnXlZWJskW+rjWtHUgdPUVOI94EN/+Bz7IqJ0AtVTiR9Sc/tuvxP6RRcBelD1pj+8+wVucfg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/rect-utils": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/number-input": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/number-input/-/number-input-1.26.3.tgz", + "integrity": "sha512-Ft/Ot4jJpkaTGS1wK7z9YzdKxqAAKOM8EiGFHcMufPpyGwLIxNmScXNVGTjDqdWIIW3rPPrxLtIlA4ovUe4ZsQ==", + "license": "MIT", + "dependencies": { + "@internationalized/number": "3.6.5", + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/pagination": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/pagination/-/pagination-1.26.3.tgz", + "integrity": "sha512-dTuDXWx833UUzVfn0HLoTtj0SIfKr5sJl/CEyLaLLS4+GAhDYsPkkPereLSjAL3ffUoKzqkqACh17wI/IibmgQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/password-input": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/password-input/-/password-input-1.26.3.tgz", + "integrity": "sha512-rdItTM+TnhW6Wpzt0fPhqLug0BfHUZYSK3+77gny9ZhrBH9XrPXBbHb3ORzmGZfBFuVG+bmF1shI+wg6YS/6rA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/pin-input": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/pin-input/-/pin-input-1.26.3.tgz", + "integrity": "sha512-4e5h3T9UirAd8+StrHJ180PComxXofg2Pc5NxYs1D73dIKRhbB03Gbbt8ONJg0Pys3HCCcMe5IMzhmmwIdTBCw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/popover": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/popover/-/popover-1.26.3.tgz", + "integrity": "sha512-9eohN1s5ha0mwF6AIht9SE6D0Nl40IL4BY8gL4QWiaLZPPuILqzFby7yug+USdQuMNV2sk7ow6khaU2f7DTQEQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/aria-hidden": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/focus-trap": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/remove-scroll": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/popper": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/popper/-/popper-1.26.3.tgz", + "integrity": "sha512-rCBhh+yfMUmBKJkzFvOdeeS3z/uHgbts1lXQ9SZqHBXQ8zQNjSREdQt9E1Ge2lR+ZPtwLpS8adrUtLzLRFc7Uw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "1.7.4", + "@zag-js/dom-query": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/presence": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/presence/-/presence-1.26.3.tgz", + "integrity": "sha512-K3jKOcqimLOGRGcywY128NRzUHeUKXVKYALA5yi/gn6EfJRbX/lV/CQGG9dRiVpI+KnpuCIgJ5zr0ojU0d27yw==", + "license": "MIT", + "dependencies": { + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3" + } + }, + "node_modules/@zag-js/progress": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/progress/-/progress-1.26.3.tgz", + "integrity": "sha512-NwCjePJLEhtNypBFvHg74dtzx6TsK4+1lr/2jwDWO7/vTCLzMRa6TzHD/Tt6KJl5SlLKRSPeCRRc3pnOmKC4LA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/qr-code": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/qr-code/-/qr-code-1.26.3.tgz", + "integrity": "sha512-Tq3TWpECOlUmYiC+8svYaUaocBYH4/psAAl2tq4f2qQdCYsB3c20DAG/x6GaDZPf6EFPC/jSgIjCDeBZGw2g4A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3", + "proxy-memoize": "3.0.1", + "uqr": "0.1.2" + } + }, + "node_modules/@zag-js/radio-group": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/radio-group/-/radio-group-1.26.3.tgz", + "integrity": "sha512-3JLamlpsaa5qW2BzQ6qjoSS70DV2E5YBJWdFAYIHKttQMBzxhRux0U3GR7SCVXp6C7xinR+/xxYEt4uexMK2MQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/focus-visible": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/rating-group": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/rating-group/-/rating-group-1.26.3.tgz", + "integrity": "sha512-4Ki6GtCY5+su2V6080NlYtOwt+DTjvWcJ0SVPqc/TDe6FgefkHhqAlTClKWVR8hocxk5Mq0z3mgZZ/Y2Yzss9Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/react": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/react/-/react-1.26.3.tgz", + "integrity": "sha512-Z+O89rAmpMmNTMmdLAambnnwTwsRYMAiUZe7RF5TjlLuWMtV6Nc5TpBii3fqxZ/5S9vaUMtDMb9gsHPEfOR7Kw==", + "license": "MIT", + "dependencies": { + "@zag-js/core": "1.26.3", + "@zag-js/store": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@zag-js/rect-utils": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/rect-utils/-/rect-utils-1.26.3.tgz", + "integrity": "sha512-+ayBRgNO0xo+sJmnGfZDTKxHyWj6acAUjELwlcHbT0LIxMULTPxbA+Sf1d8Zz3x4fah9v2UN6/AaIrYnldFxTQ==", + "license": "MIT" + }, + "node_modules/@zag-js/remove-scroll": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/remove-scroll/-/remove-scroll-1.26.3.tgz", + "integrity": "sha512-vofKmcJqN9AQqH6BXOii6cIQ5wj6w4cMj0psGeMUE5Rh1Xifg6chvp6ZMX0EqlTz5pYuE9e3VLPpuWUEuY+8Hg==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3" + } + }, + "node_modules/@zag-js/scroll-area": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/scroll-area/-/scroll-area-1.26.3.tgz", + "integrity": "sha512-C35ad+q5tZkZQwgoiUwyB/HoGLiin+DBPKvKcXQjztGMTB8fv/7n0vzKdpbo5BEFV626ys8vKGV/WY9Zkjdj6w==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/scroll-snap": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/scroll-snap/-/scroll-snap-1.26.3.tgz", + "integrity": "sha512-Pzy2p2xAYILg18Z2h8xtVZqxgcITf7igXIJEngR8Tmvg+97NBYHPgl9sp86mNdoVKkQ5FB4ohirYvHrHONIurw==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.26.3" + } + }, + "node_modules/@zag-js/select": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/select/-/select-1.26.3.tgz", + "integrity": "sha512-4T7Y6fd8FZiTuPQWjuBNG6MWDZbVPO74BlEpBJdbDK9LEJnR2yPnDmNS855BAk0ERzw4UWBwX/HbbDDaYwiRiQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/collection": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/signature-pad": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/signature-pad/-/signature-pad-1.26.3.tgz", + "integrity": "sha512-2oO06kTt96POPkQ1VvXcfSKihHJAdGSKhIQHpi5pqPl5OWCyD7DlqVeeP+IHsoTSOIyR2w1EzEG9PCQFc9F50Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3", + "perfect-freehand": "^1.2.2" + } + }, + "node_modules/@zag-js/slider": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/slider/-/slider-1.26.3.tgz", + "integrity": "sha512-EIZcljXslieIz6uWe4YOUjrZbLiW5w6LFhlSfszDs4OGnyKxOImiFWEhuArPCGW7CyuyISFy1GWFW+vi5+6Y1w==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/splitter": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/splitter/-/splitter-1.26.3.tgz", + "integrity": "sha512-dN4ZbOXv83bBVsDyZxBFmPB/o/0OMcOTVIdZbt7aOe1y2z/As/8omzg6zzhqrIV+ASVJwFl6R+QHMngS2NZFhQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/steps": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/steps/-/steps-1.26.3.tgz", + "integrity": "sha512-eM4ytff6eo93J6AlG8OJcKZ7t5XF8SLLHDJFKIGG59GDYtK8oPSdIVkjy6Ud4XvKXhKufZJUqT7qDT8UDOHxww==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/store": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/store/-/store-1.26.3.tgz", + "integrity": "sha512-mDBylMkKKobJTUCJmuLDRzZsDRBkwQFcjlyCFQa9fLAjVhfCkF2Y3lAp3MuyqeJa+c7X3BbkXDD/kL2UoiTFMg==", + "license": "MIT", + "dependencies": { + "proxy-compare": "3.0.1" + } + }, + "node_modules/@zag-js/switch": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/switch/-/switch-1.26.3.tgz", + "integrity": "sha512-Fc56yuh0cIf0/8AdRtBS9YVoxIIESQiYf61QMQS/pfO8hBeSFQQmbjeAf8MyOd2QadWwoNKABCiUArP2nBl1Rg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/focus-visible": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/tabs": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/tabs/-/tabs-1.26.3.tgz", + "integrity": "sha512-3Iqb+TAw49WldJaMczyzhOQl6F/x9QBgMSp7NhE6AIMaityr0ZE3jCU0qYS+F2DPAu7ng87SOCVA0dOaO/J4eg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/tags-input": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/tags-input/-/tags-input-1.26.3.tgz", + "integrity": "sha512-BSzQtfx72zhs4mfBsWReY30HhnKg1KSTzCVThm8oHmkcDGvpDuUbylM2KMsQdgYWTGYHI4FiIzUsUd+RVDOAgw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/auto-resize": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/interact-outside": "1.26.3", + "@zag-js/live-region": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/timer": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/timer/-/timer-1.26.3.tgz", + "integrity": "sha512-VwRvtY8OJSsSii3Is+8iqf48eY2tc5bKEgCOCQ3JKWhlDtj+Z9toXEQzR2aEiCk64M9AnzKFhFjDS+l7GmWIGg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/toast": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/toast/-/toast-1.26.3.tgz", + "integrity": "sha512-JOLvSHXC4hKlzNb2dDU7tvXIY6U4M+z8ewnHSAOO8Sl8OKTE3pbRU3Q9A0B2qW/3qteQA/P7411e5WrWdxn+9Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/toggle": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/toggle/-/toggle-1.26.3.tgz", + "integrity": "sha512-nxsVxkQCTzeOCJcOwqrsIccSf+jjAKmVrmFD0l5IjvZWrypukKPmUFd9BgM7QYTtd3STaQPMMjkYzfFQ/SljzA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/toggle-group": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/toggle-group/-/toggle-group-1.26.3.tgz", + "integrity": "sha512-CL6oOih/7R8r2NAq7U9HuR5MYlDmAPmS2q9VZINb415bKYuFEJyGMClFB3B+NFaLy6KG/voYahOBJ1NRsgMnxQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/tooltip": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/tooltip/-/tooltip-1.26.3.tgz", + "integrity": "sha512-givMhlRGWt9PD9JMrA+GBceD2ViQT8MUgb5r/ovDdaahW8xlMkosWIBnjJBafilrg3tw2Oemw1gPwctcPjAlMQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/focus-visible": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/tour": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/tour/-/tour-1.26.3.tgz", + "integrity": "sha512-2WqYTpCTo46LWsF/arI+kuCexDDbgyKwE0hYjsK5NKf1BFAsAWpwmZ1Ne1RGDYl52sXMGfGu7tCQhDzGTzWU9w==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dismissable": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/focus-trap": "1.26.3", + "@zag-js/interact-outside": "1.26.3", + "@zag-js/popper": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/tree-view": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/tree-view/-/tree-view-1.26.3.tgz", + "integrity": "sha512-3P376SKf/poaUUjeobm/qenxaO8ApB/P/rpplkUE3fZVhnwKJbzKoUeNZiTpVX36FaJBb3AwBavmyQxMuYoxiQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.26.3", + "@zag-js/collection": "1.26.3", + "@zag-js/core": "1.26.3", + "@zag-js/dom-query": "1.26.3", + "@zag-js/types": "1.26.3", + "@zag-js/utils": "1.26.3" + } + }, + "node_modules/@zag-js/types": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/types/-/types-1.26.3.tgz", + "integrity": "sha512-fJf2CgNLQuaFCRZzwGP69vWdFPc1bd1sPngzrYxIfT9SpIRFcBUrBa3p8hXlXg3EScx4O8qC0PrMe9NasUXV1Q==", + "license": "MIT", + "dependencies": { + "csstype": "3.1.3" + } + }, + "node_modules/@zag-js/utils": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@zag-js/utils/-/utils-1.26.3.tgz", + "integrity": "sha512-C5PlGTVfuMYc/GydvyIyxjSoHib9ZNcinLoucZaRjXF4l6ClDPIlujXc11//XZ0EajpxOKNhfjP9m9stj5Vk0A==", + "license": "MIT" + }, + "node_modules/3d-force-graph": { + "version": "1.79.0", + "resolved": "https://registry.npmjs.org/3d-force-graph/-/3d-force-graph-1.79.0.tgz", + "integrity": "sha512-0RUNcfiH12f93loY/iS4wShzhXzdLLN4futvFnintF7eP30DjX+nAdLDAGOZwSflhijQyVwnGtpczNjFrDLUzQ==", + "license": "MIT", + "dependencies": { + "accessor-fn": "1", + "kapsule": "^1.16", + "three": ">=0.118 <1", + "three-forcegraph": "1", + "three-render-objects": "^1.35" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/3d-force-graph-ar": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/3d-force-graph-ar/-/3d-force-graph-ar-1.10.0.tgz", + "integrity": "sha512-I93bRB+PY7RGPGEPa/+MyC17UYXUBkGu8i71VjRVJCSDtk23XOJ3RlcyVWHKzifTktQ7Oy0YRnqWwbxkHV8m4g==", + "license": "MIT", + "dependencies": { + "aframe-forcegraph-component": "3", + "kapsule": "^1.16" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/3d-force-graph-vr": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/3d-force-graph-vr/-/3d-force-graph-vr-3.1.1.tgz", + "integrity": "sha512-q/NhHQyAkGMHkf9Irvt6350yIYnCK77XuEc/09HrRqYbaENqwraPPzahr9BG5wkd2//KtzEEp9AYhPU2GQ0KHg==", + "license": "MIT", + "dependencies": { + "accessor-fn": "1", + "aframe-extras": "^7.2", + "aframe-forcegraph-component": "3", + "kapsule": "^1.16", + "polished": "4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "aframe": "^1.5" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accessor-fn": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", + "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aframe": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/aframe/-/aframe-1.7.1.tgz", + "integrity": "sha512-dcc7PWI5z8pyJ0s2W0mUd8d83339frgMXhUvWr1yxkdgg6zSExkuQwsSJjiNn7XWKMUUqKYDvV/WzQQRA+OBXA==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.4", + "deep-assign": "^2.0.0", + "load-bmfont": "^1.2.3", + "super-animejs": "^3.1.0", + "three": "npm:super-three@0.173.5", + "three-bmfont-text": "github:dmarcos/three-bmfont-text#eed4878795be9b3e38cf6aec6b903f56acd1f695" + }, + "engines": { + "node": ">= 4.6.0", + "npm": ">= 2.15.9" + } + }, + "node_modules/aframe-extras": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/aframe-extras/-/aframe-extras-7.6.0.tgz", + "integrity": "sha512-IKRMWsU1DgxIYPDatFpB0JMErIGSh1tWoLx01rodOM5mgzqyEumlhphh5ouCwz0aFKdWQ8KxznECaXf4B+xwAw==", + "license": "MIT", + "dependencies": { + "nipplejs": "^0.10.2", + "three": "^0.164.0", + "three-pathfinding": "^1.3.0" + } + }, + "node_modules/aframe-extras/node_modules/three": { + "version": "0.164.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.164.1.tgz", + "integrity": "sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w==", + "license": "MIT" + }, + "node_modules/aframe-forcegraph-component": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/aframe-forcegraph-component/-/aframe-forcegraph-component-3.3.0.tgz", + "integrity": "sha512-rwVk1t93YGOQ9+qaeFdcYgY8RZfd3NHbrIBhT9Ju7gxXP8QDtE6fIi409OiPZEhMx9/36zFQ/etxdpQNsWmHPQ==", + "license": "MIT", + "dependencies": { + "three-forcegraph": "1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "aframe": "*" + } + }, + "node_modules/aframe/node_modules/three": { + "name": "super-three", + "version": "0.173.5", + "resolved": "https://registry.npmjs.org/super-three/-/super-three-0.173.5.tgz", + "integrity": "sha512-ecjojbhUg/5QrixwqF4s6gvtJap9XQz7TcnFUX/J8Yosgb2eE2ZUqsyqr/JczFG//6hwIaZPeAa9M5DNaM1dmQ==", + "license": "MIT", + "peer": true + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/an-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/an-array/-/an-array-1.0.0.tgz", + "integrity": "sha512-M175GYI7RmsYu24Ok383yZQa3eveDfNnmhTe3OQ3bm70bEovz2gWenH+ST/n32M8lrwLWk74hcPds5CDRPe2wg==", + "license": "MIT", + "peer": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-shuffle": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-shuffle/-/array-shuffle-1.0.1.tgz", + "integrity": "sha512-PBqgo1Y2XWSksBzq3GFPEb798ZrW2snAcmr4drbVeF/6MT/5aBlkGJEvu5A/CzXHf4EjbHOj/ZowatjlIiVidA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/as-number": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/as-number/-/as-number-1.0.0.tgz", + "integrity": "sha512-HkI/zLo2AbSRO4fqVkmyf3hms0bJDs3iboHqTrNuwTiCRvdYXM7HFhfhB6Dk51anV2LM/IMB83mtK9mHw4FlAg==", + "license": "MIT", + "peer": true + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "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/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.19.tgz", + "integrity": "sha512-zoKGUdu6vb2jd3YOq0nnhEDQVbPcHhco3UImJrv5dSkvxTc2pl2WjOPsjZXDwPDSl5eghIMuY3R6J9NDKF3KcQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bezier-js": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", + "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "dev": true, + "license": "MIT/X11" + }, + "node_modules/buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "license": "MIT", + "peer": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "peer": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "license": "MIT", + "peer": true + }, + "node_modules/cacheable-request/node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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/caniuse-lite": { + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/canvas-color-tracker": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", + "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", + "license": "MIT", + "dependencies": { + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/centra": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz", + "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.6" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/compute-cosine-similarity": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/compute-cosine-similarity/-/compute-cosine-similarity-1.1.0.tgz", + "integrity": "sha512-FXhNx0ILLjGi9Z9+lglLzM12+0uoTnYkHm7GiadXDAr0HGVLm25OivUS1B/LPkbzzvlcXz/1EvWg9ZYyJSdhTw==", + "dependencies": { + "compute-dot": "^1.1.0", + "compute-l2norm": "^1.1.0", + "validate.io-array": "^1.0.5", + "validate.io-function": "^1.0.2" + } + }, + "node_modules/compute-dot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/compute-dot/-/compute-dot-1.1.0.tgz", + "integrity": "sha512-L5Ocet4DdMrXboss13K59OK23GXjiSia7+7Ukc7q4Bl+RVpIXK2W9IHMbWDZkh+JUEvJAwOKRaJDiFUa1LTnJg==", + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2" + } + }, + "node_modules/compute-l2norm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/compute-l2norm/-/compute-l2norm-1.1.0.tgz", + "integrity": "sha512-6EHh1Elj90eU28SXi+h2PLnTQvZmkkHWySpoFz+WOlVNLz3DQoC4ISUHSV9n5jMxPHtKGJ01F4uu2PsXBB8sSg==", + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "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-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, + "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", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.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": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "node_modules/data-bind-mapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/data-bind-mapper/-/data-bind-mapper-1.0.3.tgz", + "integrity": "sha512-QmU3lyEnbENQPo0M1F9BMu4s6cqNNp8iJA+b/HP2sSb7pf3dxwF3+EP1eO69rwBfH9kFJ1apmzrtogAmVt2/Xw==", + "license": "MIT", + "dependencies": { + "accessor-fn": "1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-assign": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-2.0.0.tgz", + "integrity": "sha512-2QhG3Kxulu4XIF3WL5C5x0sc/S17JLgm1SfvDfIRsR/5m7ZGmcejII7fZ2RyWhN0UWIJm0TNM/eKow6LAn3evQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "license": "MIT", + "peer": true + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "peer": true + }, + "node_modules/dtype": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dtype/-/dtype-2.0.0.tgz", + "integrity": "sha512-s2YVcLKdFGS0hpFqJaTwscsyt0E8nNFdmo73Ocd81xNPj4URI4rj6D60A+vFMIw7BXWlb4yRkEwfBqcZzPGiZg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.239", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.239.tgz", + "integrity": "sha512-1y5w0Zsq39MSPmEjHjbizvhYoTaulVtivpxkp5q5kaPmQtsK6/2nvAzGRxNMS9DoYySp9PkW0MAQDwU1m764mg==", + "dev": true, + "license": "ISC" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "peer": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/end-of-stream/node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "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-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-toolkit": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.40.0.tgz", + "integrity": "sha512-8o6w0KFmU0CiIl0/Q/BCEOabF2IJaELM1T2PWj6e8KqzHv1gdx+7JtFnDwOx1kJH/isJ5NwlDG1nCr1HrRF94Q==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/eslint": { + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", + "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.1", + "@eslint/core": "^0.16.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.38.0", + "@eslint/plugin-kit": "^0.4.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/float-tooltip": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", + "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", + "license": "MIT", + "dependencies": { + "d3-selection": "2 - 3", + "kapsule": "^1.16", + "preact": "10" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/force-graph": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.0.tgz", + "integrity": "sha512-aTnihCmiMA0ItLJLCbrQYS9mzriopW24goFPgUnKAAmAlPogTSmFWqoBPMXzIfPb7bs04Hur5zEI4WYgLW3Sig==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "18 - 25", + "accessor-fn": "1", + "bezier-js": "3 - 6", + "canvas-color-tracker": "^1.3", + "d3-array": "1 - 3", + "d3-drag": "2 - 3", + "d3-force-3d": "2 - 3", + "d3-scale": "1 - 4", + "d3-scale-chromatic": "1 - 3", + "d3-selection": "2 - 3", + "d3-zoom": "2 - 3", + "float-tooltip": "^1.7", + "index-array-by": "1", + "kapsule": "^1.16", + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "license": "MIT", + "peer": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "license": "MIT", + "peer": true, + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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", + "peer": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "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/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/index-array-by": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", + "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT", + "peer": true + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "license": "MIT", + "peer": true + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jerrypick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/jerrypick/-/jerrypick-1.1.2.tgz", + "integrity": "sha512-YKnxXEekXKzhpf7CLYA0A+oDP8V0OhICNCr5lv96FvSsDEmrb0GKM776JgQvHTMjr7DTTPEVv/1Ciaw0uEWzBA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "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/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/kapsule": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", + "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", + "license": "MIT", + "dependencies": { + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/layout-bmfont-text": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/layout-bmfont-text/-/layout-bmfont-text-1.3.4.tgz", + "integrity": "sha512-mceomHZ8W7pSKQhTdLvOe1Im4n37u8xa5Gr0J3KPCHRMO/9o7+goWIOzZcUUd+Xgzy3+22bvoIQ0OaN3LRtgaw==", + "license": "MIT", + "peer": true, + "dependencies": { + "as-number": "^1.0.0", + "word-wrapper": "^1.0.7", + "xtend": "^4.0.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-1.2.4.tgz", + "integrity": "sha512-eGHwtlABkp1NOJSiKUNqBf3SYAS5jPHtvRXPAgNaQwTqmkTahjtiLH9NtxdR5IOPhNvwNMN/diswSfZKzUkhGg==", + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/load-bmfont": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz", + "integrity": "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^3.7.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "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", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.511.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz", + "integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/map-limit": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", + "integrity": "sha512-pJpcfLPnIF/Sk3taPW21G/RQsEEirGaFpCW3oXRwH9dnFHPHNGjNyvh++rdmC2fNqEaTw2MhYJraoJWAHx8kEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "once": "~1.3.0" + } + }, + "node_modules/markdown-it": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-4.4.0.tgz", + "integrity": "sha512-Rl8dHHeLuAh3E72OPY0tY7CLvlxgHiLhlshIYswAAabAg4YDBLa6e/LTgNkkxBO2K61ESzoquPQFMw/iMrT1PA==", + "license": "MIT", + "dependencies": { + "argparse": "~1.0.2", + "entities": "~1.1.1", + "linkify-it": "~1.2.0", + "mdurl": "~1.0.0", + "uc.micro": "^1.0.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "license": "BSD-2-Clause" + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "peer": true, + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/n3": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.26.0.tgz", + "integrity": "sha512-SQknS0ua90rN+3RHuk8BeIqeYyqIH/+ecViZxX08jR4j6MugqWRjtONl3uANG/crWXnOM2WIqBJtjIhVYFha+w==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">=12.0" + } + }, + "node_modules/n3/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/n3/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/n3/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/new-array": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/new-array/-/new-array-1.0.0.tgz", + "integrity": "sha512-K5AyFYbuHZ4e/ti52y7k18q8UHsS78FlRd85w2Fmsd6AkuLipDihPflKC0p3PN5i8II7+uHxo+CtkLiJDfmS5A==", + "license": "MIT", + "peer": true + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/ngraph.events": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.4.0.tgz", + "integrity": "sha512-NeDGI4DSyjBNBRtA86222JoYietsmCXbs8CEB0dZ51Xeh4lhVl1y3wpWLumczvnha8sFQIW4E0vvVWwgmX2mGw==", + "license": "BSD-3-Clause" + }, + "node_modules/ngraph.forcelayout": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ngraph.forcelayout/-/ngraph.forcelayout-3.3.1.tgz", + "integrity": "sha512-MKBuEh1wujyQHFTW57y5vd/uuEOK0XfXYxm3lC7kktjJLRdt/KEKEknyOlc6tjXflqBKEuYBBcu7Ax5VY+S6aw==", + "license": "BSD-3-Clause", + "dependencies": { + "ngraph.events": "^1.0.0", + "ngraph.merge": "^1.0.0", + "ngraph.random": "^1.0.0" + } + }, + "node_modules/ngraph.graph": { + "version": "20.1.0", + "resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-20.1.0.tgz", + "integrity": "sha512-1jorNgIc0Kg0L9bTNN4+RCrVvbZ+4pqGVMrbhX3LLyqYcRdLvAQRRnxddmfj9l5f6Eq59SUTfbYZEm8cktiE7Q==", + "license": "BSD-3-Clause", + "dependencies": { + "ngraph.events": "^1.2.1" + } + }, + "node_modules/ngraph.merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ngraph.merge/-/ngraph.merge-1.0.0.tgz", + "integrity": "sha512-5J8YjGITUJeapsomtTALYsw7rFveYkM+lBj3QiYZ79EymQcuri65Nw3knQtFxQBU1r5iOaVRXrSwMENUPK62Vg==", + "license": "MIT" + }, + "node_modules/ngraph.random": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-1.2.0.tgz", + "integrity": "sha512-4EUeAGbB2HWX9njd6bP6tciN6ByJfoaAvmVL9QTaZSeXrW46eNGA9GajiXiPBbvFqxUWFkEbyo6x5qsACUuVfA==", + "license": "BSD-3-Clause" + }, + "node_modules/nice-color-palettes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/nice-color-palettes/-/nice-color-palettes-3.0.0.tgz", + "integrity": "sha512-lL4AjabAAFi313tjrtmgm/bxCRzp4l3vCshojfV/ij3IPdtnRqv6Chcw+SqJUhbe7g3o3BecaqCJYUNLswGBhQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "got": "^9.2.2", + "map-limit": "0.0.1", + "minimist": "^1.2.0", + "new-array": "^1.0.0" + }, + "bin": { + "nice-color-palettes": "bin/index.js" + } + }, + "node_modules/nipplejs": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/nipplejs/-/nipplejs-0.10.2.tgz", + "integrity": "sha512-XGxFY8C2DOtobf1fK+MXINTzkkXJLjZDDpfQhOUZf4TSytbc9s4bmA0lB9eKKM8iDivdr9NQkO7DpIQfsST+9g==", + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-releases": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", + "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==", + "license": "ISC", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "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-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "license": "MIT", + "peer": true + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "license": "MIT", + "peer": true + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse-headers": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", + "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==", + "license": "MIT", + "peer": true + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/perfect-freehand": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.2.2.tgz", + "integrity": "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ==", + "license": "MIT" + }, + "node_modules/phin": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz", + "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "peer": true, + "dependencies": { + "centra": "^2.7.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.27.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", + "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "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", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz", + "integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==", + "license": "MIT" + }, + "node_modules/proxy-memoize": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-memoize/-/proxy-memoize-3.0.1.tgz", + "integrity": "sha512-VDdG/VYtOgdGkWJx7y0o7p+zArSf2383Isci8C+BP3YXgMYDoPd3cCBjw0JdWb6YBb9sFiOPbAADDVTPJnh+9g==", + "license": "MIT", + "dependencies": { + "proxy-compare": "^3.0.0" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "peer": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quad-indices": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/quad-indices/-/quad-indices-2.0.1.tgz", + "integrity": "sha512-6jtmCsEbGAh5npThXrBaubbTjPcF0rMbn57XCJVI7LkW8PUT56V+uIrRCCWCn85PSgJC9v8Pm5tnJDwmOBewvA==", + "license": "MIT", + "peer": true, + "dependencies": { + "an-array": "^1.0.0", + "dtype": "^2.0.0", + "is-buffer": "^1.0.2" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-force-graph": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/react-force-graph/-/react-force-graph-1.48.1.tgz", + "integrity": "sha512-9TdtUM5GNacpurCj2UdSw5h/QO47tDCrU1pHJ7z7xmdPCbtVtsXCftxinQoX9n0eHR3UocxaQiwUNbSEZ3vxnQ==", + "license": "MIT", + "dependencies": { + "3d-force-graph": "^1.79", + "3d-force-graph-ar": "^1.10", + "3d-force-graph-vr": "^3.1", + "force-graph": "^1.51", + "prop-types": "15", + "react-kapsule": "^2.5" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-hotkeys-hook": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-5.2.1.tgz", + "integrity": "sha512-xbKh6zJxd/vJHT4Bw4+0pBD662Fk20V+VFhLqciCg+manTVO4qlqRqiwFOYelfHN9dBvWj9vxaPkSS26ZSIJGg==", + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "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/react-kapsule": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-kapsule/-/react-kapsule-2.5.7.tgz", + "integrity": "sha512-kifAF4ZPD77qZKc4CKLmozq6GY1sBzPEJTIJb0wWFK6HsePJatK3jXplZn2eeAt3x67CDozgi7/rO8fNQ/AL7A==", + "license": "MIT", + "dependencies": { + "jerrypick": "^1.1.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-markdown-it": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-markdown-it/-/react-markdown-it-1.0.2.tgz", + "integrity": "sha512-Bzo/9UCCxlL2D7rYiVlxEqiOU66mqmLTzjxN0JLlioEhZhp7amzSq1YNS0+Jf0YKQmpBb5rfI9nh5s3wBsKnww==", + "license": "MIT", + "dependencies": { + "markdown-it": "^4.4.0", + "strip-indent": "^1.0.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-resize-detector": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-12.3.0.tgz", + "integrity": "sha512-mIDOVrTHKGnKe6qEUWi8dFdfHM5CPyTOpqoHctdMQf89Ljm/0qqDIzkP3vTRZZJi9/raaMiRxDEOqO4you5x+A==", + "license": "MIT", + "dependencies": { + "es-toolkit": "^1.39.6" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-router": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz", + "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/reactflow": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", + "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==", + "license": "MIT", + "dependencies": { + "@reactflow/background": "11.3.14", + "@reactflow/controls": "11.2.14", + "@reactflow/core": "11.11.4", + "@reactflow/minimap": "11.7.14", + "@reactflow/node-resizer": "2.2.14", + "@reactflow/node-toolbar": "1.3.14" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent/node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", + "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.2.tgz", + "integrity": "sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.93.2", + "sass-embedded-android-arm": "1.93.2", + "sass-embedded-android-arm64": "1.93.2", + "sass-embedded-android-riscv64": "1.93.2", + "sass-embedded-android-x64": "1.93.2", + "sass-embedded-darwin-arm64": "1.93.2", + "sass-embedded-darwin-x64": "1.93.2", + "sass-embedded-linux-arm": "1.93.2", + "sass-embedded-linux-arm64": "1.93.2", + "sass-embedded-linux-musl-arm": "1.93.2", + "sass-embedded-linux-musl-arm64": "1.93.2", + "sass-embedded-linux-musl-riscv64": "1.93.2", + "sass-embedded-linux-musl-x64": "1.93.2", + "sass-embedded-linux-riscv64": "1.93.2", + "sass-embedded-linux-x64": "1.93.2", + "sass-embedded-unknown-all": "1.93.2", + "sass-embedded-win32-arm64": "1.93.2", + "sass-embedded-win32-x64": "1.93.2" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.2.tgz", + "integrity": "sha512-GdEuPXIzmhRS5J7UKAwEvtk8YyHQuFZRcpnEnkA3rwRUI27kwjyXkNeIj38XjUQ3DzrfMe8HcKFaqWGHvblS7Q==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "sass": "1.93.2" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.2.tgz", + "integrity": "sha512-I8bpO8meZNo5FvFx5FIiE7DGPVOYft0WjuwcCCdeJ6duwfkl6tZdatex1GrSigvTsuz9L0m4ngDcX/Tj/8yMow==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.2.tgz", + "integrity": "sha512-346f4iVGAPGcNP6V6IOOFkN5qnArAoXNTPr5eA/rmNpeGwomdb7kJyQ717r9rbJXxOG8OAAUado6J0qLsjnjXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.2.tgz", + "integrity": "sha512-hSMW1s4yJf5guT9mrdkumluqrwh7BjbZ4MbBW9tmi1DRDdlw1Wh9Oy1HnnmOG8x9XcI1qkojtPL6LUuEJmsiDg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.2.tgz", + "integrity": "sha512-JqktiHZduvn+ldGBosE40ALgQ//tGCVNAObgcQ6UIZznEJbsHegqStqhRo8UW3x2cgOO2XYJcrInH6cc7wdKbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.2.tgz", + "integrity": "sha512-qI1X16qKNeBJp+M/5BNW7v/JHCDYWr1/mdoJ7+UMHmP0b5AVudIZtimtK0hnjrLnBECURifd6IkulybR+h+4UA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.2.tgz", + "integrity": "sha512-4KeAvlkQ0m0enKUnDGQJZwpovYw99iiMb8CTZRSsQm8Eh7halbJZVmx67f4heFY/zISgVOCcxNg19GrM5NTwtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.2.tgz", + "integrity": "sha512-N3+D/ToHtzwLDO+lSH05Wo6/KRxFBPnbjVHASOlHzqJnK+g5cqex7IFAp6ozzlRStySk61Rp6d+YGrqZ6/P0PA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.2.tgz", + "integrity": "sha512-9ftX6nd5CsShJqJ2WRg+ptaYvUW+spqZfJ88FbcKQBNFQm6L87luj3UI1rB6cP5EWrLwHA754OKxRJyzWiaN6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.2.tgz", + "integrity": "sha512-XBTvx66yRenvEsp3VaJCb3HQSyqCsUh7R+pbxcN5TuzueybZi0LXvn9zneksdXcmjACMlMpIVXi6LyHPQkYc8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.2.tgz", + "integrity": "sha512-+3EHuDPkMiAX5kytsjEC1bKZCawB9J6pm2eBIzzLMPWbf5xdx++vO1DpT7hD4bm4ZGn0eVHgSOKIfP6CVz6tVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.2.tgz", + "integrity": "sha512-0sB5kmVZDKTYzmCSlTUnjh6mzOhzmQiW/NNI5g8JS4JiHw2sDNTvt1dsFTuqFkUHyEOY3ESTsfHHBQV8Ip4bEA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.2.tgz", + "integrity": "sha512-t3ejQ+1LEVuHy7JHBI2tWHhoMfhedUNDjGJR2FKaLgrtJntGnyD1RyX0xb3nuqL/UXiEAtmTmZY+Uh3SLUe1Hg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.2.tgz", + "integrity": "sha512-e7AndEwAbFtXaLy6on4BfNGTr3wtGZQmypUgYpSNVcYDO+CWxatKVY4cxbehMPhxG9g5ru+eaMfynvhZt7fLaA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.2.tgz", + "integrity": "sha512-U3EIUZQL11DU0xDDHXexd4PYPHQaSQa2hzc4EzmhHqrAj+TyfYO94htjWOd+DdTPtSwmLp+9cTWwPZBODzC96w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.2.tgz", + "integrity": "sha512-7VnaOmyewcXohiuoFagJ3SK5ddP9yXpU0rzz+pZQmS1/+5O6vzyFCUoEt3HDRaLctH4GT3nUGoK1jg0ae62IfQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "dependencies": { + "sass": "1.93.2" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.2.tgz", + "integrity": "sha512-Y90DZDbQvtv4Bt0GTXKlcT9pn4pz8AObEjFF8eyul+/boXwyptPZ/A1EyziAeNaIEIfxyy87z78PUgCeGHsx3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.2.tgz", + "integrity": "sha512-BbSucRP6PVRZGIwlEBkp+6VQl2GWdkWFMN+9EuOTPrLxCJZoq+yhzmbjspd3PeM8+7WJ7AdFu/uRYdO8tor1iQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC", + "peer": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/streamsaver": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/streamsaver/-/streamsaver-2.0.6.tgz", + "integrity": "sha512-LK4e7TfCV8HzuM0PKXuVUfKyCB1FtT9L0EGxsFk5Up8njj0bXK8pJM9+Wq2Nya7/jslmCQwRK39LFm55h7NBTw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + } + ], + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", + "license": "MIT", + "dependencies": { + "get-stdin": "^4.0.1" + }, + "bin": { + "strip-indent": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "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/super-animejs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/super-animejs/-/super-animejs-3.1.0.tgz", + "integrity": "sha512-6MFAFJDRuvwkovxQZPruuyHinTa4rgj4hNLOndjcYYhZLckoXtVRY9rJPuq8p6c/tgZJrFYEAYAfJ2/hhNtUCA==", + "license": "MIT", + "peer": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/three": { + "version": "0.180.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz", + "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==", + "license": "MIT" + }, + "node_modules/three-bmfont-text": { + "version": "3.0.0", + "resolved": "git+ssh://git@github.com/dmarcos/three-bmfont-text.git#eed4878795be9b3e38cf6aec6b903f56acd1f695", + "integrity": "sha512-JkvSn4FoichG1tsUJ9SRN452+jvf7RDf/M/Y4j5G7LfaAWmrhGQQbjlCJLJft6sSbjqqB96R1OKvyZmVZff7ew==", + "license": "MIT", + "peer": true, + "dependencies": { + "array-shuffle": "^1.0.1", + "layout-bmfont-text": "^1.2.0", + "nice-color-palettes": "^3.0.0", + "quad-indices": "^2.0.1" + } + }, + "node_modules/three-forcegraph": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/three-forcegraph/-/three-forcegraph-1.43.0.tgz", + "integrity": "sha512-1AqLmTCjjjwcuccObG96fCxiRnNJjCLdA5Mozl7XK+ROwTJ6QEJPo2XJ6uxWeuAmPE7ukMhgv4lj28oZSfE4wg==", + "license": "MIT", + "dependencies": { + "accessor-fn": "1", + "d3-array": "1 - 3", + "d3-force-3d": "2 - 3", + "d3-scale": "1 - 4", + "d3-scale-chromatic": "1 - 3", + "data-bind-mapper": "1", + "kapsule": "^1.16", + "ngraph.forcelayout": "3", + "ngraph.graph": "20", + "tinycolor2": "1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "three": ">=0.118.3" + } + }, + "node_modules/three-pathfinding": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/three-pathfinding/-/three-pathfinding-1.3.0.tgz", + "integrity": "sha512-LKxMI3/YqdMYvt6AdE2vB6s5ueDFczt/DWoxhtPNgRsH6E0D8LMYQxz+eIrmKo0MQpDvMVzXYUMBk+b86+k97w==", + "license": "MIT", + "peerDependencies": { + "three": "0.x.x" + } + }, + "node_modules/three-render-objects": { + "version": "1.40.4", + "resolved": "https://registry.npmjs.org/three-render-objects/-/three-render-objects-1.40.4.tgz", + "integrity": "sha512-Ukpu1pei3L5r809izvjsZxwuRcYLiyn6Uvy3lZ9bpMTdvj3i6PeX6w++/hs2ZS3KnEzGjb6YvTvh4UQuwHTDJg==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "18 - 25", + "accessor-fn": "1", + "float-tooltip": "^1.7", + "kapsule": "^1.16", + "polished": "4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "three": ">=0.168" + } + }, + "node_modules/three-spritetext": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/three-spritetext/-/three-spritetext-1.10.0.tgz", + "integrity": "sha512-t08iP1FCU1lQh8T5MmCpdijKgas8GDHJE0LqMGBuVu3xqMMpFnEZhTlih7FlxLPQizHIGoumUSpfOlY1GO/Tgg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "three": ">=0.86.0" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.2.tgz", + "integrity": "sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.46.2", + "@typescript-eslint/parser": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uqr": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.2.tgz", + "integrity": "sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==", + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "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", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/validate.io-array": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", + "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==", + "license": "MIT" + }, + "node_modules/validate.io-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz", + "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==" + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/word-wrapper": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/word-wrapper/-/word-wrapper-1.0.7.tgz", + "integrity": "sha512-VOPBFCm9b6FyYKQYfn9AVn2dQvdR/YOVFV6IBRA1TBMJWKffvhEX1af6FMGrttILs2Q9ikCRhLqkbY2weW6dOQ==", + "license": "MIT", + "peer": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "peer": true + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "license": "MIT", + "peer": true, + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "license": "MIT", + "peer": true + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..61399b26 --- /dev/null +++ b/package.json @@ -0,0 +1,67 @@ +{ + "name": "vite-ts", + "private": true, + "version": "1.0.2", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint .", + "prettify": "prettier src --write", + "test": "vitest run", + "test:ui": "vitest --ui", + "test:run": "vitest", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "@chakra-ui/react": "^3.19.1", + "@emotion/styled": "^11.13.0", + "@msgpack/msgpack": "^3.1.1", + "@tanstack/react-query": "^5.80.3", + "@tanstack/react-table": "^8.21.3", + "@trustgraph/client": "github:trustgraph-ai/trustgraph-client#master", + "@trustgraph/react-provider": "github:trustgraph-ai/trustgraph-react-provider#master", + "@trustgraph/react-state": "github:trustgraph-ai/trustgraph-react-state#d24cdec0", + "@types/dagre": "^0.7.53", + "compute-cosine-similarity": "^1.1.0", + "dagre": "^0.8.5", + "jszip": "^3.10.1", + "lucide-react": "^0.511.0", + "n3": "^1.26.0", + "next-themes": "^0.4.6", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-force-graph": ">=1.47.0", + "react-hotkeys-hook": "^5.1.0", + "react-icons": "^5.5.0", + "react-markdown-it": "^1.0.2", + "react-resize-detector": "^12.0.2", + "react-router": "^7.6.0", + "reactflow": "^11.11.4", + "streamsaver": "^2.0.6", + "three-spritetext": "^1.9.3", + "uuid": "^11.0.3", + "zustand": "^5.0.0-rc.2" + }, + "devDependencies": { + "@eslint/js": "^9.9.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "eslint": "^9.9.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "jsdom": "^26.1.0", + "prettier": "3.5.3", + "sass-embedded": "^1.77.8", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.1", + "vite": "^6.3.4", + "vitest": "^3.2.4" + } +} diff --git a/palette.svg b/palette.svg new file mode 100644 index 00000000..dc1b87e8 --- /dev/null +++ b/palette.svg @@ -0,0 +1,8 @@ + + + + ,,,, + Exported from Coolors.co + https://coolors.co/250219-83b7ce-417094-3f1d44-dac0ec + + \ No newline at end of file diff --git a/public/tg.svg b/public/tg.svg new file mode 100644 index 00000000..7123ae45 --- /dev/null +++ b/public/tg.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/pulumi/Pulumi.dev.yaml b/pulumi/Pulumi.dev.yaml new file mode 100644 index 00000000..431ff4c1 --- /dev/null +++ b/pulumi/Pulumi.dev.yaml @@ -0,0 +1,14 @@ +encryptionsalt: v1:vQGk98eEeYI=:v1:tHg+f1b66tEydgA9:J1RGVNI0FssyjSXVhcKU7bfBofNFTg== +config: + config-ui:artifact-name: config-ui-dev + config-ui:artifact-repo: us-central1-docker.pkg.dev/trustgraph-demo/config-ui-dev + config-ui:artifact-repo-region: us-central1 + config-ui:cloud-run-region: us-central1 + config-ui:domain: demo.trustgraph.ai + config-ui:environment: dev + config-ui:gcp-project: trustgraph-demo + config-ui:gcp-region: us-central1 + config-ui:hostname: dev.config-ui.demo.trustgraph.ai + config-ui:managed-zone: demo + config-ui:max-scale: "2" + config-ui:min-scale: "0" diff --git a/pulumi/Pulumi.prod.yaml b/pulumi/Pulumi.prod.yaml new file mode 100644 index 00000000..96dfcf00 --- /dev/null +++ b/pulumi/Pulumi.prod.yaml @@ -0,0 +1,14 @@ +encryptionsalt: v1:vQGk98eEeYI=:v1:tHg+f1b66tEydgA9:J1RGVNI0FssyjSXVhcKU7bfBofNFTg== +config: + config-ui:artifact-name: config-ui-prod + config-ui:artifact-repo: us-central1-docker.pkg.dev/trustgraph-demo/config-ui-prod + config-ui:artifact-repo-region: us-central1 + config-ui:cloud-run-region: us-central1 + config-ui:domain: demo.trustgraph.ai + config-ui:environment: prod + config-ui:gcp-project: trustgraph-demo + config-ui:gcp-region: us-central1 + config-ui:hostname: config-ui.demo.trustgraph.ai + config-ui:managed-zone: demo + config-ui:max-scale: "2" + config-ui:min-scale: "0" diff --git a/pulumi/Pulumi.yaml b/pulumi/Pulumi.yaml new file mode 100644 index 00000000..53285728 --- /dev/null +++ b/pulumi/Pulumi.yaml @@ -0,0 +1,3 @@ +name: config-ui +runtime: nodejs +description: Config UI diff --git a/pulumi/index.ts b/pulumi/index.ts new file mode 100644 index 00000000..e393802a --- /dev/null +++ b/pulumi/index.ts @@ -0,0 +1,348 @@ + +import * as pulumi from "@pulumi/pulumi"; +import * as gcp from "@pulumi/gcp"; +import { local } from "@pulumi/command"; +//import * as fs from 'fs'; + +const cfg = new pulumi.Config(); + +function get(tag : string) { + + const val = cfg.get(tag); + + if (!val) { + console.log("ERROR: The '" + tag + "' config is mandatory"); + throw "The '" + tag + "' config is mandatory"; + } + + return val; + +} + +const imageVersion = process.env.IMAGE_VERSION; +if (!imageVersion) + throw Error("IMAGE_VERSION not defined"); + +const repo = get("artifact-repo"); +const artifactRepoRegion = get("artifact-repo-region"); +const artifactName = get("artifact-name"); +const hostname = get("hostname"); +const managedZone = get("managed-zone"); +const project = get("gcp-project"); +const region = get("gcp-region"); +const cloudRunRegion = get("cloud-run-region"); +const environment = get("environment"); +//const domain = get("domain"); +const minScale = get("min-scale"); +const maxScale = get("max-scale"); + +const provider = new gcp.Provider( + "gcp", + { + project: project, + region: region, + } +); + +const artifactRepo = new gcp.artifactregistry.Repository( + "artifact-repo", + { + description: "repository for " + environment, + format: "DOCKER", + location: artifactRepoRegion, + repositoryId: artifactName, + cleanupPolicies: [ + { + id: "keep-minimum-versions", + action: "KEEP", + mostRecentVersions: { + keepCount: 5, + }, + } + ], + }, + { + provider: provider, + } +); + +const localImageName = "localhost/config-ui:" + imageVersion; + +const imageName = repo + "/config-ui:" + imageVersion; + +const taggedImage = new local.Command( + "podman-tag-command", + { + create: "podman tag " + localImageName + " " + imageName, + } +); + +const image = new local.Command( + "podman-push-command", + { + create: "podman push " + imageName, + }, + { + dependsOn: [taggedImage, artifactRepo], + } +); + +const svcAccount = new gcp.serviceaccount.Account( + "service-account", + { + accountId: "config-ui-" + environment, + displayName: "Config UI", + description: "Config UI", + }, + { + provider: provider, + } +); + +/* + +const vertexAiUserMember = new gcp.projects.IAMMember( + "vertexai-user-role", + { + member: svcAccount.email.apply(x => "serviceAccount:" + x), + project: project, + role: "roles/aiplatform.admin", + }, + { + provider: provider, + } +); + +*/ + +const service = new gcp.cloudrun.Service( + "service", + { + name: "config-ui-" + environment, + location: cloudRunRegion, + template: { + metadata: { + labels: { + version: "v" + imageVersion.replace(/\./g, "-"), + }, + annotations: { + + // Scale attributes + "autoscaling.knative.dev/minScale": minScale, + "autoscaling.knative.dev/maxScale": maxScale, + + // 2nd generation. Need to specify at least 512MB RAM. + // Going back to gen1 because faster cold starts + "run.googleapis.com/execution-environment": "gen1", + + } + }, + spec: { + containerConcurrency: 100, + timeoutSeconds: 300, + serviceAccountName: svcAccount.email, + containers: [ + { + image: imageName, +// commands: [ +// "config-ui" +// ], + ports: [ + { + "name": "http1", // Must be http1 or h2c. + "containerPort": 8080, + } + ], + resources: { + limits: { + cpu: "1000m", + memory: "512Mi", + } + }, + } + ], + }, + }, + }, + { + provider: provider, + dependsOn: [image], + } +); + +const allUsersPolicy = gcp.organizations.getIAMPolicy( + { + bindings: [{ + role: "roles/run.invoker", + members: ["allUsers"], + }], + }, + { + provider: provider, + } +); + +/*const _noAuthPolicy =*/ new gcp.cloudrun.IamPolicy( + "no-auth-policy", + { + location: service.location, + project: service.project, + service: service.name, + policyData: allUsersPolicy.then(pol => pol.policyData), + }, + { + provider: provider, + } +); + +//////////////////////////////////////////////////////////////////////////// + +const domainMapping = new gcp.cloudrun.DomainMapping( + "domain-mapping", + { + name: hostname, + location: cloudRunRegion, + metadata: { + namespace: project, + }, + spec: { + routeName: service.name, + } + }, + { + provider: provider + } +); + +const zone = gcp.dns.getManagedZoneOutput( + { + name: managedZone, + }, + { + provider: provider, + } +); + +//////////////////////////////////////////////////////////////////////////// + +domainMapping.statuses.apply( + ss => ss[0].resourceRecords +).apply( + rrs => { + if (rrs) { + + let mapping : { [k : string] : string[] } = {}; + + for(let i = 0; i < rrs.length; i++) { + if (rrs[i].rrdata) { + + const rr = rrs[i].rrdata; + const tp = rrs[i].type; + + if (!rr || !tp) continue; + + if (mapping[tp]) + mapping = { + ...mapping, + [tp]: [...mapping[tp], rr], + }; + else + mapping = { + ...mapping, + [tp]: [rr], + }; + + } + } + + for (const tp in mapping) { + + new gcp.dns.RecordSet( + "resource-record-" + tp, + { + name: hostname + ".", + managedZone: zone.name, + type: tp, + ttl: 300, + rrdatas: mapping[tp], + }, + { + provider: provider, + } + ); + + } + + } + + } +); + +//////////////////////////////////////////////////////////////////////////// + +const serviceMon = new gcp.monitoring.GenericService( + "service-monitoring", + { + basicService: { + serviceLabels: { + service_name: service.name, + location: cloudRunRegion, + }, + serviceType: "CLOUD_RUN", + }, + displayName: "Config UI service (" + environment + ")", + serviceId: "config-ui-service-" + environment + "-mon", + userLabels: { + "service": service.name, + "application": "config-ui", + "environment": environment, + }, + }, + { + provider: provider, + } +); + +new gcp.monitoring.Slo( + "latency-slo", + { + service: serviceMon.serviceId, + sloId: "config-ui-service-" + environment + "-latency-slo", + displayName: "Config UI latency (" + environment + ")", + goal: 0.95, + rollingPeriodDays: 5, + basicSli: { + latency: { + threshold: "2s" + } + }, + }, + { + provider: provider, + } +); + +new gcp.monitoring.Slo( + "availability-slo", + { + service: serviceMon.serviceId, + sloId: "config-ui-service-" + environment + "-availability-slo", + displayName: "Config UI availability (" + environment + ")", + goal: 0.95, + rollingPeriodDays: 5, + windowsBasedSli: { + windowPeriod: "3600s", + goodTotalRatioThreshold: { + basicSliPerformance: { + availability: { + } + }, + threshold: 0.9, + } + } + }, + { + provider: provider, + } +); + diff --git a/pulumi/package-lock.json b/pulumi/package-lock.json new file mode 100644 index 00000000..410840f8 --- /dev/null +++ b/pulumi/package-lock.json @@ -0,0 +1,3625 @@ +{ + "name": "safety-ai", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "safety-ai", + "dependencies": { + "@pulumi/command": "^0.7.2", + "@pulumi/gcp": "^6.58.0", + "@pulumi/pulumi": "^3.0.0" + }, + "devDependencies": { + "@types/node": "^16" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.0.tgz", + "integrity": "sha512-eWdP97A6xKtZXVP/ze9y8zYRB2t6ugQAuLXFuZXAsyqmyltaAjl4yPkmIfc0wuTFJMOUF1AdvIFQCL7fMtaX6g==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "license": "ISC" + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@logdna/tail-file": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@logdna/tail-file/-/tail-file-2.2.0.tgz", + "integrity": "sha512-XGSsWDweP80Fks16lwkAUIr54ICyBs6PsI4mpfTLQaWgEJRtY9xEV+PeyDpJ+sJEGZxqINlpmAwe/6tS1pP8Ng==", + "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-7.5.4.tgz", + "integrity": "sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==", + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.1", + "@npmcli/installed-package-contents": "^2.1.0", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.1.1", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.1.0", + "@npmcli/query": "^3.1.0", + "@npmcli/redact": "^2.0.0", + "@npmcli/run-script": "^8.1.0", + "bin-links": "^4.0.4", + "cacache": "^18.0.3", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.2", + "json-parse-even-better-errors": "^3.0.2", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^7.2.1", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.0.1", + "npm-registry-fetch": "^17.0.1", + "pacote": "^18.0.6", + "parse-conflict-json": "^3.0.0", + "proc-log": "^4.2.0", + "proggy": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.6", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", + "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-7.1.1.tgz", + "integrity": "sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==", + "license": "ISC", + "dependencies": { + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^18.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/query": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.1.0.tgz", + "integrity": "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", + "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz", + "integrity": "sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.26.0.tgz", + "integrity": "sha512-HedpXXYzzbaoutw6DFLWLDket2FwLkLpil4hGCZ1xYEIMTcivdfwEOISgdbLEWyG3HW52gTq2V9mOVJrONgiwg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.26.0.tgz", + "integrity": "sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.26.0.tgz", + "integrity": "sha512-PW5R34n3SJHO4t0UetyHKiXL6LixIqWN6lWncg3eRXhKuT30x+b7m5sDJS0kEWRfHeS+kG7uCw2vBzmB2lk3Dw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.26.0", + "@opentelemetry/resources": "1.26.0", + "@opentelemetry/sdk-trace-base": "1.26.0", + "@opentelemetry/semantic-conventions": "1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz", + "integrity": "sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.52.1", + "@types/shimmer": "^1.0.2", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-grpc": { + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.52.1.tgz", + "integrity": "sha512-EdSDiDSAO+XRXk/ZN128qQpBo1I51+Uay/LUPcPQhSRGf7fBPIEUBeOLQiItguGsug5MGOYjql2w/1wCQF3fdQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "0.52.1", + "@opentelemetry/semantic-conventions": "1.25.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-grpc/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", + "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.26.0.tgz", + "integrity": "sha512-vvVkQLQ/lGGyEy9GT8uFnI047pajSOVnZI2poJqVGD3nJ+B9sFGdlHNnQKophE3lHfnIH0pw2ubrCTjZCgIj+Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.26.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.26.0.tgz", + "integrity": "sha512-DelFGkCdaxA1C/QA0Xilszfr0t4YbGd3DjxiCDPh34lfnFr+VkkrjV9S8ZTJvAzfdKERXhfOxIKBoGPJwoSz7Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.26.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", + "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.26.0", + "@opentelemetry/semantic-conventions": "1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", + "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.26.0", + "@opentelemetry/resources": "1.26.0", + "@opentelemetry/semantic-conventions": "1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.26.0.tgz", + "integrity": "sha512-Fj5IVKrj0yeUwlewCRwzOVcr5avTuNnMHWf7GPc1t6WaT78J6CJyF3saZ/0RkZfdeNO8IcBl/bNcWMVZBMRW8Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "1.26.0", + "@opentelemetry/core": "1.26.0", + "@opentelemetry/propagator-b3": "1.26.0", + "@opentelemetry/propagator-jaeger": "1.26.0", + "@opentelemetry/sdk-trace-base": "1.26.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", + "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@pulumi/command": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@pulumi/command/-/command-0.7.2.tgz", + "integrity": "sha512-R/eAIXIywSkn2cHIaNqs0EXcXduNeDuS+wom6oadB5KEP+6elgJbo4WMDP+OuDYGkkxEWIKjk0bPPO7570aXFQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@pulumi/pulumi": "^3.0.0" + } + }, + "node_modules/@pulumi/gcp": { + "version": "6.67.1", + "resolved": "https://registry.npmjs.org/@pulumi/gcp/-/gcp-6.67.1.tgz", + "integrity": "sha512-/4BQ4j0Yz5VobPRe96o34SZoV1JlJSRlChI0uNx36uq17GHpzSFnQkKTb+nNvK7ujqSoQFIVuy5Ez9pY5uyFiA==", + "license": "Apache-2.0", + "dependencies": { + "@pulumi/pulumi": "^3.0.0", + "@types/express": "^4.16.0", + "read-package-json": "^2.0.13" + } + }, + "node_modules/@pulumi/pulumi": { + "version": "3.135.1", + "resolved": "https://registry.npmjs.org/@pulumi/pulumi/-/pulumi-3.135.1.tgz", + "integrity": "sha512-uSAewnD7KgkR0oEzfBlbYbLVJ3qEOXA/Q/NU127DXSOvZi//AMd7MHrIxMd2DIFQnho3cjgtc12r+XZCjkePJQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.10.1", + "@logdna/tail-file": "^2.0.6", + "@npmcli/arborist": "^7.3.1", + "@opentelemetry/api": "^1.9", + "@opentelemetry/exporter-zipkin": "^1.25", + "@opentelemetry/instrumentation": "^0.52", + "@opentelemetry/instrumentation-grpc": "^0.52", + "@opentelemetry/resources": "^1.25", + "@opentelemetry/sdk-trace-base": "^1.25", + "@opentelemetry/sdk-trace-node": "^1.25", + "@opentelemetry/semantic-conventions": "^1.25", + "@pulumi/query": "^0.3.0", + "@types/google-protobuf": "^3.15.5", + "@types/semver": "^7.5.6", + "@types/tmp": "^0.2.6", + "execa": "^5.1.0", + "fdir": "^6.1.1", + "google-protobuf": "^3.5.0", + "got": "^11.8.6", + "ini": "^2.0.0", + "js-yaml": "^3.14.0", + "minimist": "^1.2.6", + "normalize-package-data": "^6.0.0", + "picomatch": "^3.0.1", + "pkg-dir": "^7.0.0", + "require-from-string": "^2.0.1", + "semver": "^7.5.2", + "source-map-support": "^0.5.6", + "tmp": "^0.2.1", + "upath": "^1.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ts-node": ">= 7.0.1 < 12", + "typescript": ">= 3.8.3 < 6" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@pulumi/query": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@pulumi/query/-/query-0.3.0.tgz", + "integrity": "sha512-xfo+yLRM2zVjVEA4p23IjQWzyWl1ZhWOGobsBqRpIarzLvwNH/RAGaoehdxlhx4X92302DrpdIFgTICMN4P38w==", + "license": "Apache-2.0" + }, + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", + "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/google-protobuf": { + "version": "3.15.12", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.12.tgz", + "integrity": "sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==", + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.18.112", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.112.tgz", + "integrity": "sha512-EKrbKUGJROm17+dY/gMi31aJlGLJ75e1IkTojt9n6u+hnaTBDs+M1bIdOawpk2m6YUAXq/R2W0SxCng1tndHCg==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", + "license": "MIT" + }, + "node_modules/@types/tmp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==", + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bin-links": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", + "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cmd-shim": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", + "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "license": "Apache-2.0" + }, + "node_modules/fdir": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.0.tgz", + "integrity": "sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "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", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/import-in-the-middle": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.11.2.tgz", + "integrity": "sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.8.2", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", + "license": "MIT" + }, + "node_modules/just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-gyp": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", + "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/parse-conflict-json": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", + "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==", + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picomatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/proggy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-2.0.0.tgz", + "integrity": "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-call-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", + "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "license": "ISC", + "dependencies": { + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/read-package-json/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/read-package-json/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "license": "ISC" + }, + "node_modules/read-package-json/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/read-package-json/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/read-package-json/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-package-json/node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "license": "ISC" + }, + "node_modules/read-package-json/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz", + "integrity": "sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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", + "optional": true + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "license": "BSD-2-Clause" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sigstore": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "license": "CC0-1.0" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tuf-js": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "license": "MIT", + "dependencies": { + "@tufjs/models": "2.0.1", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "license": "ISC" + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/pulumi/package.json b/pulumi/package.json new file mode 100644 index 00000000..d7eec6a1 --- /dev/null +++ b/pulumi/package.json @@ -0,0 +1,12 @@ +{ + "name": "safety-ai", + "main": "index.ts", + "devDependencies": { + "@types/node": "^16" + }, + "dependencies": { + "@pulumi/command": "^0.7.2", + "@pulumi/gcp": "^6.58.0", + "@pulumi/pulumi": "^3.0.0" + } +} diff --git a/pulumi/tsconfig.json b/pulumi/tsconfig.json new file mode 100644 index 00000000..ab65afa6 --- /dev/null +++ b/pulumi/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "bin", + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.ts" + ] +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..c1846968 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +aiohttp +pyyaml +wheel diff --git a/src/App.scss b/src/App.scss new file mode 100644 index 00000000..366ad033 --- /dev/null +++ b/src/App.scss @@ -0,0 +1,16 @@ +#root { + margin: 0; + padding: 0; + height: 100vh; + width: 100vw; +} + +/* + +body { + width: 100vw; + height: 100vh; + margin: 0; +} + +*/ diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 00000000..794c6cbb --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,202 @@ +import { useEffect, lazy, Suspense } from "react"; + +import { Box } from "@chakra-ui/react"; +import { BrowserRouter as Router, Routes, Route } from "react-router"; + +import Layout from "./components/Layout"; + +import ChatPage from "./pages/ChatPage"; +import SearchPage from "./pages/SearchPage"; +import EntityPage from "./pages/EntityPage"; + +// Lazy load GraphPage since it includes heavy 3D visualization library (react-force-graph/Three.js) +const GraphPage = lazy(() => import("./pages/GraphPage")); +// Lazy load FlowClassesPage since it includes reactflow library +const FlowClassesPage = lazy(() => import("./pages/FlowClassesPage")); +// Lazy load less frequently used pages +const OntologiesPage = lazy(() => import("./pages/OntologiesPage")); +const StructuredQueryPage = lazy(() => import("./pages/StructuredQueryPage")); +const SettingsPage = lazy(() => import("./pages/SettingsPage")); +const SchemasPage = lazy(() => import("./pages/SchemasPage")); +const LLMModelsPage = lazy(() => import("./pages/LLMModelsPage")); +const McpToolsPage = lazy(() => import("./pages/McpToolsPage")); + +import FlowsPage from "./pages/FlowsPage"; +import LibraryPage from "./pages/LibraryPage"; +import KnowledgeCoresPage from "./pages/KnowledgeCoresPage"; +import ProcessingPage from "./pages/ProcessingPage"; +import TokenCostPage from "./pages/TokenCostPage"; +import PromptsPage from "./pages/PromptsPage"; +import ToolsPage from "./pages/ToolsPage"; + +import CenterSpinner from "./components/common/CenterSpinner"; +import Progress from "./components/common/Progress"; +import { Toaster } from "./components/ui/ToasterComponent"; + +import { useSocket, useConnectionState } from "@trustgraph/react-provider"; +import { + useProgressStateStore, + useSessionStore, +} from "@trustgraph/react-state"; + +const App = () => { + const socket = useSocket(); + const connectionState = useConnectionState(); + + const addActivity = useProgressStateStore((state) => state.addActivity); + const removeActivity = useProgressStateStore( + (state) => state.removeActivity, + ); + + const setFlowId = useSessionStore((state) => state.setFlowId); + const setFlow = useSessionStore((state) => state.setFlow); + + useEffect(() => { + // Wait for socket connection to be established before loading flows + if ( + !connectionState || + (connectionState.status !== "connected" && + connectionState.status !== "authenticated" && + connectionState.status !== "unauthenticated") + ) { + console.log( + "App: Waiting for socket connection...", + connectionState?.status, + ); + return; + } + + console.log("App: Socket connected, loading flows..."); + const act = "Load flows"; + addActivity(act); + socket + .flows() + .getFlows() + .then((ids) => { + return Promise.all( + ids.map((id) => + socket + .flows() + .getFlow(id) + .then((x) => [id, x]), + ), + ); + }) + .then((flows) => { + removeActivity(act); + + const flowIds = flows.map((fl) => fl[0]); + if (flowIds.includes("default")) { + setFlowId("default"); + const flow = flows.filter((fl) => fl[0] == "default")[0][1]; + setFlow(flow); + } else { + // No default flow, just pick first in the list. + setFlowId(flows[0][0]); + setFlow(flows[0][1]); + } + }) + .catch((err) => { + removeActivity(act); + console.log("Error:", err); + }); + }, [ + socket, + connectionState, + addActivity, + removeActivity, + setFlow, + setFlowId, + ]); + + return ( + + + + + } /> + } /> + } /> + } /> + }> + + + } + /> + } /> + }> + + + } + /> + } /> + } /> + } /> + } /> + } /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + } /> + }> + + + } + /> + }> + + + } + /> + }> + + + } + /> + + + + + + + + ); +}; + +export default App; diff --git a/src/api/authenticated-fetch.ts b/src/api/authenticated-fetch.ts new file mode 100644 index 00000000..06b63762 --- /dev/null +++ b/src/api/authenticated-fetch.ts @@ -0,0 +1,41 @@ +/** + * Authenticated fetch utility + * + * Provides fetch functions that automatically include Bearer token authentication + * when an API key is configured in settings. + */ + +/** + * Creates an authenticated fetch function that includes Bearer token when available + * @param apiKey - Optional API key for authentication + * @returns Fetch function with automatic auth headers + */ +export const createAuthenticatedFetch = (apiKey?: string) => { + return (url: string, options: RequestInit = {}) => { + const headers: HeadersInit = { + ...options.headers, + }; + + // Add Bearer token if API key is present + if (apiKey) { + headers["Authorization"] = `Bearer ${apiKey}`; + } + + return fetch(url, { + ...options, + headers, + }); + }; +}; + +/** + * Hook-based authenticated fetch that uses current settings + * This is a React hook that must be called from within a component + */ +export const useAuthenticatedFetch = () => { + // Note: This will be implemented when we need it in components + // For now, we'll use the createAuthenticatedFetch directly with settings + throw new Error( + "useAuthenticatedFetch not yet implemented - use createAuthenticatedFetch with settings", + ); +}; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx new file mode 100644 index 00000000..5248a925 --- /dev/null +++ b/src/components/Layout.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { Box, Flex } from "@chakra-ui/react"; +import Sidebar from "./Sidebar"; + +interface LayoutProps { + children: React.ReactNode; +} + +const Layout: React.FC = ({ children }) => { + return ( + + + + {children} + + + ); +}; + +export default Layout; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 00000000..f07f424e --- /dev/null +++ b/src/components/Sidebar.tsx @@ -0,0 +1,162 @@ +import React from "react"; + +import { + Box, + Flex, + VStack, + Text, + Icon, + Heading, + Separator, + chakra, +} from "@chakra-ui/react"; + +import { NavLink as ReactRouterNavLink } from "react-router"; +import { useSettings } from "@trustgraph/react-state"; + +const ChakraNavLink = chakra(ReactRouterNavLink); + +import { + TestTube2, + Hammer, + Plug, + Bot, + MessageSquareText, + Search, + Waypoints, + Rotate3d, + // FileUp, + Workflow, + ScrollText, + LibraryBig, + BrainCircuit, + CircleArrowRight, + HandCoins, + MessageCircleCode, + Database, + Network, + FileSearch, + Settings, +} from "lucide-react"; + +interface NavItemProps { + to: string; + icon: React.ElementType; + label: string; +} + +const NavItem: React.FC = ({ to, icon, label }) => { + return ( + + {({ isActive }: { isActive: boolean }) => ( + + + {label} + + )} + + ); +}; + +const Sidebar = () => { + const { settings } = useSettings(); + + return ( + + + + + + + TrustGraph + + + TG + + + + + + + + + + + + {settings.featureSwitches.flowClasses && ( + + )} + + + {settings.featureSwitches.submissions && ( + + )} + {settings.featureSwitches.tokenCost && ( + + )} + + {settings.featureSwitches.schemas && ( + + )} + {settings.featureSwitches.structuredQuery && ( + + )} + {settings.featureSwitches.ontologyEditor && ( + + )} + {settings.featureSwitches.agentTools && ( + + )} + {settings.featureSwitches.mcpTools && ( + + )} + {settings.featureSwitches.llmModels && ( + + )} + + + + ); +}; + +export default Sidebar; diff --git a/src/components/agents/Controls.tsx b/src/components/agents/Controls.tsx new file mode 100644 index 00000000..b39a9f7f --- /dev/null +++ b/src/components/agents/Controls.tsx @@ -0,0 +1,38 @@ +import React, { useState } from "react"; + +import { Plus } from "lucide-react"; + +import { Button, Box } from "@chakra-ui/react"; + +import EditDialog from "./EditDialog"; + +const Controls = () => { + const [createOpen, setCreateOpen] = useState(false); + + const onComplete = () => { + setCreateOpen(false); + }; + + return ( + + + onComplete()} + /> + + ); +}; + +export default Controls; diff --git a/src/components/agents/EditDialog.tsx b/src/components/agents/EditDialog.tsx new file mode 100644 index 00000000..4c2f01ad --- /dev/null +++ b/src/components/agents/EditDialog.tsx @@ -0,0 +1,340 @@ +import React, { useEffect, useState, useRef } from "react"; + +import { Trash, SendHorizontal, Plus } from "lucide-react"; + +import { Portal, Button, Dialog, Box, CloseButton } from "@chakra-ui/react"; + +import { useSocket } from "@trustgraph/react-provider"; +import { useAgentTools } from "@trustgraph/react-state"; +import { useMcpTools } from "@trustgraph/react-state"; +import { usePrompts } from "@trustgraph/react-state"; +import SelectField from "../common/SelectField"; +import TextAreaField from "../common/TextAreaField"; +import TextField from "../common/TextField"; +import ChipInputField from "../common/ChipInputField"; +import { toaster } from "../ui/toaster"; +import EditableArgumentsTable from "./EditableArgumentsTable"; + +const EditDialog = ({ open, onOpenChange, onComplete, id, create }) => { + const socket = useSocket(); + const { updateTool, createTool, deleteTool } = useAgentTools(); + const { tools: mcpTools } = useMcpTools(); + const { prompts } = usePrompts(); + + const [newId, setNewId] = useState(""); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [type, setType] = useState("knowledge-query"); + const [args, setArgs] = useState([]); + const [templateId, setTemplateId] = useState(""); + const [mcpToolId, setMcpToolId] = useState(""); + const [collection, setCollection] = useState(""); + const [group, setGroup] = useState([]); + const [state, setState] = useState(""); + const [applicableStates, setApplicableStates] = useState([]); + + const [editArgIx, setEditArgIx] = useState(-1); + + useEffect(() => { + if (!id) return; + + socket + .config() + .getConfig([{ type: "tool", key: id }]) + .then((x) => { + return JSON.parse(x.values[0].value); + }) + .then((x) => { + // Store flow information + setName(x.name || ""); + setDescription(x.description); + setType(x.type); + setArgs(x.arguments || []); + // Handle both old 'template' and new 'template_id' attributes + setTemplateId(x.template_id || x.template || ""); + // Handle both old 'mcp-tool' and new 'mcp_tool_id' attributes + setMcpToolId(x.mcp_tool_id || x["mcp-tool"] || ""); + // Handle collection attribute for knowledge-query tools + setCollection(x.collection || ""); + // Handle new optional fields + setGroup(x.group || []); + setState(x.state || ""); + setApplicableStates(x["applicable-states"] || []); + }) + .catch((e) => { + console.log("Error:", e); + toaster.create({ + title: "Error: " + e.toString(), + type: "error", + }); + }); + }, [id, create, socket]); + + const typeOptions = [ + { + value: "text-completion", + label: "Text completion", + description: "Consults an LLM for a response with no further knowledge", + }, + { + value: "knowledge-query", + label: "Knowledge query", + description: "Uses the GraphRAG service for knowledge", + }, + { + value: "structured-query", + label: "Structured Query", + description: + "Execute natural language questions against records in a structured data / object store", + }, + { + value: "mcp-tool", + label: "MCP Tool", + description: "Uses the mcp-tool service to access a remote MCP tool", + }, + { + value: "prompt", + label: "Prompt Template", + description: "Executes a prompt template with variables", + }, + ]; + + const contentRef = useRef(null); + + // Create options for MCP tools select menu + const mcpToolOptions = mcpTools.map(([id]) => ({ + value: id, + label: id, + description: id, + })); + + // Create options for prompt templates select menu + const promptTemplateOptions = prompts.map(([id]) => ({ + value: id, + label: id, + description: id, + })); + + const onEdit = () => { + // Build the tool structure + const toolStruct = { + id: create ? newId : id, + name: name, + description: description, + type: type, + arguments: args, + ...(type === "prompt" && templateId && { template: templateId }), + ...(type === "mcp-tool" && mcpToolId && { "mcp-tool": mcpToolId }), + ...((type === "knowledge-query" || type === "structured-query") && + collection && { collection: collection }), + ...(group && group.length > 0 && { group: group }), + ...(state && { state: state }), + ...(applicableStates && + applicableStates.length > 0 && { + "applicable-states": applicableStates, + }), + }; + + if (create) { + createTool({ id: newId, tool: toolStruct, onSuccess: onComplete }); + } else { + updateTool({ id, tool: toolStruct, onSuccess: onComplete }); + } + }; + + const addArgument = () => { + setArgs((x) => [ + ...x, + { + name: "argname", + description: "???", + type: "string", + }, + ]); + }; + + const deleteArgument = (index) => { + setArgs((x) => x.filter((_, i) => i !== index)); + }; + + const setArgAttr = (id, key, value) => { + const newArgs = args.map((arg, ix) => { + if (id == ix) { + return { + ...arg, + [key]: value, + }; + } else { + return arg; + } + }); + setArgs(newArgs); + }; + + const onDelete = () => { + if (create) return; + deleteTool({ id, onSuccess: onComplete }); + }; + + return ( + { + onOpenChange(x.open); + }} + > + + + + + + {create && Create tool} + + {!create && ( + + Edit tool: {id} + + )} + + + {create && ( + setNewId(v)} + required={true} + /> + )} + + setName(v)} + required={true} + /> + + setDescription(v)} + required={true} + /> + + setType(v[0])} + contentRef={contentRef} + /> + + {type === "prompt" && ( + setTemplateId(v[0] || "")} + contentRef={contentRef} + /> + )} + + {type === "mcp-tool" && ( + setMcpToolId(v[0] || "")} + contentRef={contentRef} + /> + )} + + {(type === "knowledge-query" || type === "structured-query") && ( + setCollection(v)} + required={false} + /> + )} + + + + setState(v)} + required={false} + /> + + + + {(type === "prompt" || type === "mcp-tool") && ( + <> + + + + + + + )} + + + + { + // If a 'create' operation, there's nothing to delete, only + // present if an existing tool exists + } + {!create && ( + + )} + + + + + + + + + + ); +}; + +export default EditDialog; diff --git a/src/components/agents/EditableArgumentsTable.tsx b/src/components/agents/EditableArgumentsTable.tsx new file mode 100644 index 00000000..0a2ebb02 --- /dev/null +++ b/src/components/agents/EditableArgumentsTable.tsx @@ -0,0 +1,243 @@ +import React, { useMemo, useCallback, useRef, useEffect } from "react"; +import { + Table, + Editable, + Popover, + Text, + RadioGroup, + Stack, + Box, + IconButton, +} from "@chakra-ui/react"; +import { Trash } from "lucide-react"; +import { + createColumnHelper, + useReactTable, + getCoreRowModel, +} from "@tanstack/react-table"; +import { flexRender } from "@tanstack/react-table"; + +interface Argument { + name: string; + description: string; + type: "string" | "number"; +} + +interface EditableArgumentsTableProps { + args: Argument[]; + editArgIx: number; + setEditArgIx: (ix: number) => void; + setArgAttr: (ix: number, attr: keyof Argument, value: string) => void; + deleteArg: (ix: number) => void; +} + +const columnHelper = createColumnHelper(); + +export const EditableArgumentsTable: React.FC = ({ + args, + editArgIx, + setEditArgIx, + setArgAttr, + deleteArg, +}) => { + // Store latest function references to avoid stale closures + const setArgAttrRef = useRef(setArgAttr); + const setEditArgIxRef = useRef(setEditArgIx); + const deleteArgRef = useRef(deleteArg); + + useEffect(() => { + setArgAttrRef.current = setArgAttr; + setEditArgIxRef.current = setEditArgIx; + deleteArgRef.current = deleteArg; + }); + + // Create truly stable callback functions that never change reference + const handleNameChange = useCallback((index: number, value: string) => { + setArgAttrRef.current(index, "name", value); + }, []); + + const handleDescriptionChange = useCallback( + (index: number, value: string) => { + setArgAttrRef.current(index, "description", value); + }, + [], + ); + + const handleTypeChange = useCallback((index: number, value: string) => { + setArgAttrRef.current(index, "type", value); + setEditArgIxRef.current(-1); // Close popover after selection + }, []); + + const handleDelete = useCallback((index: number) => { + deleteArgRef.current(index); + }, []); + + const columns = useMemo( + () => [ + columnHelper.display({ + id: "name", + header: "Name", + size: 20, + cell: ({ row }) => ( + handleNameChange(row.index, v.value)} + > + + + + ), + }), + columnHelper.display({ + id: "description", + header: "Description", + size: 50, + cell: ({ row }) => ( + handleDescriptionChange(row.index, v.value)} + > + + + + ), + }), + columnHelper.display({ + id: "type", + header: "Type", + size: 30, + cell: ({ row }) => ( +
setEditArgIx(row.index)}> + {editArgIx === row.index && ( + { + // Close popover when selection changes + if (!e.open) setEditArgIx(-1); + }} + > + + {row.original.type} + + + + + + { + handleTypeChange(row.index, v.value); + }} + > + + + + + string + + + + + number + + + + + + + + )} + {editArgIx !== row.index && ( + {row.original.type} + )} +
+ ), + }), + columnHelper.display({ + id: "delete", + header: "", + size: 10, + cell: ({ row }) => ( + handleDelete(row.index)} + > + + + ), + }), + ], + // eslint-disable-next-line react-hooks/exhaustive-deps + [editArgIx], // Only editArgIx changes, callbacks are stable + ); + + const table = useReactTable({ + data: args, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + // Show helpful message if no arguments yet + if (args.length === 0) { + return ( + + No arguments defined yet. Click "add argument" below to create template + variables. + + ); + } + + return ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + + + ); +}; + +export default EditableArgumentsTable; diff --git a/src/components/agents/Tools.tsx b/src/components/agents/Tools.tsx new file mode 100644 index 00000000..92f23c5c --- /dev/null +++ b/src/components/agents/Tools.tsx @@ -0,0 +1,35 @@ +import React, { useState } from "react"; + +import { useAgentTools } from "@trustgraph/react-state"; +import EditDialog from "./EditDialog"; +import Controls from "./Controls"; +import ToolsTable from "./ToolsTable"; + +const Tools = () => { + const toolsState = useAgentTools(); + const [selected, setSelected] = useState(""); + + const onComplete = () => { + setSelected(""); + }; + + return ( + <> + setSelected("")} + onComplete={() => onComplete()} + create={false} + id={selected} + /> + + + + ); +}; + +export default Tools; diff --git a/src/components/agents/ToolsTable.tsx b/src/components/agents/ToolsTable.tsx new file mode 100644 index 00000000..685fe1c7 --- /dev/null +++ b/src/components/agents/ToolsTable.tsx @@ -0,0 +1,32 @@ +import { useMemo } from "react"; +import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; + +import { columns, type AgentTool } from "../../model/agent-tools-table"; +import ClickableTable from "../common/ClickableTable"; + +const ToolsTable = ({ setSelected, tools }) => { + // Transform the raw tools data to match our table structure + const tableData: AgentTool[] = useMemo(() => { + return tools.map(([id, config]) => ({ + id, + name: config?.name || "", + description: config?.description || "", + type: config?.type || "", + })); + }, [tools]); + + // Initialize React Table with tool data and column configuration + const table = useReactTable({ + data: tableData, + columns: columns, + getCoreRowModel: getCoreRowModel(), + }); + + const onSelect = (row) => { + setSelected(row.original.id); + }; + + return ; +}; + +export default ToolsTable; diff --git a/src/components/chat/ChatConversation.tsx b/src/components/chat/ChatConversation.tsx new file mode 100644 index 00000000..3f4324a5 --- /dev/null +++ b/src/components/chat/ChatConversation.tsx @@ -0,0 +1,28 @@ +import React from "react"; + +import ChatHistory from "./ChatHistory"; +import InputArea from "./InputArea"; +import EntityList from "../common/EntityList"; + +import { useConversation, useChat } from "@trustgraph/react-state"; + +const ChatConversation = () => { + const input = useConversation((state) => state.input); + const { submitMessage } = useChat(); + + const submit = () => { + if (input.trim()) { + submitMessage({ input }); + } + }; + + return ( + <> + + submit()} /> + + + ); +}; + +export default ChatConversation; diff --git a/src/components/chat/ChatHelp.tsx b/src/components/chat/ChatHelp.tsx new file mode 100644 index 00000000..668d88be --- /dev/null +++ b/src/components/chat/ChatHelp.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +import { Popover, Text, IconButton, Portal } from "@chakra-ui/react"; +import { CircleHelp } from "lucide-react"; + +const ChatHelp = () => { + return ( + + + + + + + + + + + + Chat assistant + + The Chat assistant lets you converse with the assistant in + natural language. The assistant has access to all of the + information in the knowledge graph and will use the knowledge + graph to provide information to you. + + + + + + + ); +}; + +export default ChatHelp; diff --git a/src/components/chat/ChatHistory.tsx b/src/components/chat/ChatHistory.tsx new file mode 100644 index 00000000..7a45bb22 --- /dev/null +++ b/src/components/chat/ChatHistory.tsx @@ -0,0 +1,63 @@ +import React, { useEffect, useRef } from "react"; + +import { Box, Text, VStack, HStack, Avatar, Spacer } from "@chakra-ui/react"; + +import { useConversation } from "@trustgraph/react-state"; +import ChatMessage from "./ChatMessage"; +import ChatModeSelector from "./ChatModeSelector"; + +const ChatHistory = () => { + const messages = useConversation((state) => state.messages); + + const scrollRef = useRef(null); + + const scrollToElement = () => { + const { current } = scrollRef; + if (current !== null) { + current.scrollIntoView({ behavior: "smooth" }); + } + }; + + useEffect(scrollToElement, [messages]); + + return ( + + + + + + + Assistant + + + + Online + + + + + + {messages.map((message, ix) => ( + + ))} +
+
+
+ ); +}; + +export default ChatHistory; diff --git a/src/components/chat/ChatMessage.tsx b/src/components/chat/ChatMessage.tsx new file mode 100644 index 00000000..ba7bc7fd --- /dev/null +++ b/src/components/chat/ChatMessage.tsx @@ -0,0 +1,95 @@ +import { Box, Flex, Avatar, Badge } from "@chakra-ui/react"; +import { Brain, Eye, CheckCircle } from "lucide-react"; +import Markdown from "react-markdown-it"; + +const ChatMessage = ({ message }) => { + const isUser = message.role === "human"; + const messageType = message.type || "normal"; + + // Define styles and icons for different message types + const getTypeStyles = () => { + switch (messageType) { + case "thinking": + return { + bg: "thinking.contrast", + borderColor: "thinking.muted", + borderWidth: "1px", + icon: , + badge: "Thinking", + badgeColor: "thinking", + color: "collout1.fg", + }; + case "observation": + return { + bg: "observing.contrast", + borderColor: "observing.muted", + borderWidth: "1px", + icon: , + badge: "Observation", + badgeColor: "observing", + color: "observing.fg", + }; + case "answer": + return { + bg: "insightful.contrast", + borderColor: "insightful.muted", + borderWidth: "1px", + icon: , + badge: "Answer", + badgeColor: "insightful", + color: "insightful.fg", + }; + default: + return { + bg: isUser ? "primary.solid" : "bg", + color: isUser ? "fg.inverted" : "fg", + }; + } + }; + + const typeStyles = getTypeStyles(); + + return ( + + {!isUser && ( + + + + )} + + + {typeStyles.badge && ( + + {typeStyles.icon} + + {typeStyles.badge} + + + )} + {message.text} + + + {isUser && ( + + + + )} + + ); +}; + +export default ChatMessage; diff --git a/src/components/chat/ChatModeSelector.tsx b/src/components/chat/ChatModeSelector.tsx new file mode 100644 index 00000000..9471228c --- /dev/null +++ b/src/components/chat/ChatModeSelector.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { Select, Portal, createListCollection } from "@chakra-ui/react"; +import { useConversation, ChatMode } from "@trustgraph/react-state"; + +const ChatModeSelector = () => { + const chatMode = useConversation((state) => state.chatMode); + const setChatMode = useConversation((state) => state.setChatMode); + + const chatModes = [ + { value: "graph-rag", label: "Graph RAG" }, + { value: "agent", label: "Agent" }, + { value: "basic-llm", label: "Basic LLM" }, + ]; + + const collection = createListCollection({ items: chatModes }); + + return ( + setChatMode(e.value[0] as ChatMode)} + size="sm" + width="150px" + > + + + + + + + + + + + + + {chatModes.map((mode) => ( + + {mode.label} + + + ))} + + + + + ); +}; + +export default ChatModeSelector; diff --git a/src/components/chat/InputArea.tsx b/src/components/chat/InputArea.tsx new file mode 100644 index 00000000..8193409a --- /dev/null +++ b/src/components/chat/InputArea.tsx @@ -0,0 +1,61 @@ +import React, { useRef } from "react"; + +import { Input, HStack } from "@chakra-ui/react"; + +import { + useProgressStateStore, + useConversation, +} from "@trustgraph/react-state"; +import ChatHelp from "./ChatHelp"; +import ProgressSubmitButton from "../common/ProgressSubmitButton"; + +interface InputAreaProps { + onSubmit: () => void; +} + +const InputArea: React.FC = ({ onSubmit }) => { + const input = useConversation((state) => state.input); + const setInput = useConversation((state) => state.setInput); + const activity = useProgressStateStore((state) => state.activity); + + const inputRef = useRef(null); + + const submit = () => { + onSubmit(); + if (inputRef.current) { + inputRef.current.focus(); + } + }; + + const onKeyDown = (event) => { + if (event.key == "Enter") { + onSubmit(); + } + }; + + return ( + <> + + setInput(e.target.value)} + onKeyDown={onKeyDown} + /> + + 0} + working={activity.size > 0} + onClick={() => submit()} + /> + + + + + ); +}; + +export default InputArea; diff --git a/src/components/chat/__tests__/ChatMessage.test.tsx b/src/components/chat/__tests__/ChatMessage.test.tsx new file mode 100644 index 00000000..7a585201 --- /dev/null +++ b/src/components/chat/__tests__/ChatMessage.test.tsx @@ -0,0 +1,353 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import ChatMessage from "../ChatMessage"; + +// Helper function to filter out Chakra UI props +const filterChakraProps = (props: Record) => { + const chakraProps = [ + "alignItems", + "justifyContent", + "direction", + "gap", + "p", + "px", + "py", + "pt", + "pb", + "pl", + "pr", + "m", + "mx", + "my", + "mt", + "mb", + "ml", + "mr", + "w", + "h", + "maxW", + "maxH", + "minW", + "minH", + "bg", + "color", + "borderRadius", + "borderWidth", + "borderColor", + "borderStyle", + "boxShadow", + "display", + "position", + "top", + "right", + "bottom", + "left", + "zIndex", + "overflow", + "textAlign", + "fontSize", + "fontWeight", + "lineHeight", + "letterSpacing", + "textTransform", + "textDecoration", + "opacity", + "visibility", + "cursor", + "pointerEvents", + "userSelect", + "resize", + "outline", + "transform", + "transformOrigin", + "transition", + "animation", + "colorPalette", + "variant", + "size", + "loading", + "disabled", + "checked", + "selected", + "active", + "focus", + "hover", + "flexDirection", + "flexWrap", + "flex", + "flexGrow", + "flexShrink", + "flexBasis", + "alignSelf", + "justifySelf", + "order", + "gridColumn", + "gridRow", + "gridArea", + "gridTemplateColumns", + "gridTemplateRows", + "gridGap", + "rowGap", + "columnGap", + "placeItems", + "placeContent", + "placeSelf", + "area", + "colSpan", + "rowSpan", + "start", + "end", + ]; + const filtered = { ...props }; + chakraProps.forEach((prop) => delete filtered[prop]); + return filtered; +}; + +// Mock Chakra UI components +vi.mock("@chakra-ui/react", () => ({ + Box: ({ + children, + ...props + }: React.PropsWithChildren>) => ( +
+ {children} +
+ ), + Flex: ({ + children, + ...props + }: React.PropsWithChildren>) => ( +
+ {children} +
+ ), + Text: ({ + children, + ...props + }: React.PropsWithChildren>) => ( +

+ {children} +

+ ), + Avatar: { + Root: ({ + children, + ...props + }: React.PropsWithChildren>) => ( +
+ {children} +
+ ), + Fallback: ({ + name, + ...props + }: { name?: string } & Record) => ( +
+ {name} +
+ ), + }, + Badge: ({ + children, + ...props + }: React.PropsWithChildren>) => ( + + {children} + + ), +})); + +// Mock react-markdown-it +vi.mock("react-markdown-it", () => ({ + default: ({ children }: React.PropsWithChildren) => ( +
{children}
+ ), +})); + +// Mock lucide-react icons +vi.mock("lucide-react", () => ({ + Brain: () =>
Brain
, + Eye: () =>
Eye
, + CheckCircle: () =>
CheckCircle
, +})); + +describe("ChatMessage", () => { + it("should render user message with correct styling", () => { + const message = { + role: "human", + text: "Hello, how are you?", + type: "normal", + }; + + render(); + + expect(screen.getByTestId("markdown")).toHaveTextContent( + "Hello, how are you?", + ); + expect(screen.getByTestId("avatar-fallback")).toHaveTextContent("User"); + }); + + it("should render AI message with correct styling", () => { + const message = { + role: "ai", + text: "I am doing well, thank you!", + type: "normal", + }; + + render(); + + expect(screen.getByTestId("markdown")).toHaveTextContent( + "I am doing well, thank you!", + ); + expect(screen.getByTestId("avatar-fallback")).toHaveTextContent("Bot"); + }); + + it("should render thinking message with correct badge and icon", () => { + const message = { + role: "ai", + text: "Let me think about this...", + type: "thinking", + }; + + render(); + + expect(screen.getByTestId("brain-icon")).toBeInTheDocument(); + expect(screen.getByTestId("badge")).toHaveTextContent("Thinking"); + expect(screen.getByTestId("markdown")).toHaveTextContent( + "Let me think about this...", + ); + }); + + it("should render observation message with correct badge and icon", () => { + const message = { + role: "ai", + text: "I observe that...", + type: "observation", + }; + + render(); + + expect(screen.getByTestId("eye-icon")).toBeInTheDocument(); + expect(screen.getByTestId("badge")).toHaveTextContent("Observation"); + expect(screen.getByTestId("markdown")).toHaveTextContent( + "I observe that...", + ); + }); + + it("should render answer message with correct badge and icon", () => { + const message = { + role: "ai", + text: "The answer is 42.", + type: "answer", + }; + + render(); + + expect(screen.getByTestId("check-circle-icon")).toBeInTheDocument(); + expect(screen.getByTestId("badge")).toHaveTextContent("Answer"); + expect(screen.getByTestId("markdown")).toHaveTextContent( + "The answer is 42.", + ); + }); + + it("should handle message without type (defaults to normal)", () => { + const message = { + role: "ai", + text: "Regular message", + }; + + render(); + + expect(screen.getByTestId("markdown")).toHaveTextContent( + "Regular message", + ); + expect(screen.queryByTestId("badge")).not.toBeInTheDocument(); + }); + + it("should handle empty message text", () => { + const message = { + role: "human", + text: "", + type: "normal", + }; + + render(); + + expect(screen.getByTestId("markdown")).toHaveTextContent(""); + }); + + it("should handle markdown content", () => { + const message = { + role: "ai", + text: "**Bold text** and *italic text*", + type: "normal", + }; + + render(); + + expect(screen.getByTestId("markdown")).toHaveTextContent( + "**Bold text** and *italic text*", + ); + }); + + it("should differentiate between user and AI avatar placement", () => { + const userMessage = { + role: "human", + text: "User message", + type: "normal", + }; + + const { rerender } = render(); + + expect(screen.getByTestId("avatar-fallback")).toHaveTextContent("User"); + + const aiMessage = { + role: "ai", + text: "AI message", + type: "normal", + }; + + rerender(); + + expect(screen.getByTestId("avatar-fallback")).toHaveTextContent("Bot"); + }); + + it("should handle all message types with correct styling", () => { + const messageTypes = ["normal", "thinking", "observation", "answer"]; + + messageTypes.forEach((type) => { + const message = { + role: "ai", + text: `Message of type ${type}`, + type: type, + }; + + const { unmount } = render(); + + expect(screen.getByTestId("markdown")).toHaveTextContent( + `Message of type ${type}`, + ); + + if (type !== "normal") { + expect(screen.getByTestId("badge")).toBeInTheDocument(); + } + + unmount(); + }); + }); + + it("should handle unknown message type (falls back to normal)", () => { + const message = { + role: "ai", + text: "Unknown type message", + type: "unknown", + }; + + render(); + + expect(screen.getByTestId("markdown")).toHaveTextContent( + "Unknown type message", + ); + expect(screen.queryByTestId("badge")).not.toBeInTheDocument(); + }); +}); diff --git a/src/components/color-mode-toggle.tsx b/src/components/color-mode-toggle.tsx new file mode 100644 index 00000000..7dde93f3 --- /dev/null +++ b/src/components/color-mode-toggle.tsx @@ -0,0 +1,17 @@ +import { IconButton } from "@chakra-ui/react"; +import { useTheme } from "next-themes"; +import { LuMoon, LuSun } from "react-icons/lu"; + +const ColorModeToggle = () => { + const { theme, setTheme } = useTheme(); + const toggleColorMode = () => { + setTheme(theme === "light" ? "dark" : "light"); + }; + return ( + + {theme === "light" ? : } + + ); +}; + +export default ColorModeToggle; diff --git a/src/components/common/AltCard.tsx b/src/components/common/AltCard.tsx new file mode 100644 index 00000000..54448ee5 --- /dev/null +++ b/src/components/common/AltCard.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { Box, Flex, Heading, Text } from "@chakra-ui/react"; + +interface AltCardProps { + title: string; + description?: string; + icon?: React.ReactNode; + children?: React.ReactNode; +} + +const AltCard: React.FC = ({ + title, + description, + icon, + children, +}) => { + return ( + + + {icon && ( + + {icon} + + )} + + {title} + + + {description && ( + + {description} + + )} + {children} + + ); +}; + +export default AltCard; diff --git a/src/components/common/BasicTable.tsx b/src/components/common/BasicTable.tsx new file mode 100644 index 00000000..64fb802c --- /dev/null +++ b/src/components/common/BasicTable.tsx @@ -0,0 +1,40 @@ +import { Table } from "@chakra-ui/react"; +import { flexRender } from "@tanstack/react-table"; + +const BasicTable = ({ table }) => { + return ( + <> + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + + + + ); +}; + +export default BasicTable; diff --git a/src/components/common/Card.tsx b/src/components/common/Card.tsx new file mode 100644 index 00000000..97c55a96 --- /dev/null +++ b/src/components/common/Card.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { Box, Flex, Heading, Text } from "@chakra-ui/react"; + +interface CardProps { + title: string; + description?: string; + icon?: React.ReactNode; + children?: React.ReactNode; +} + +const Card: React.FC = ({ title, description, icon, children }) => { + return ( + + + {icon && ( + + {icon} + + )} + + {title} + + + {description && ( + + {description} + + )} + {children} + + ); +}; + +export default Card; diff --git a/src/components/common/CenterSpinner.tsx b/src/components/common/CenterSpinner.tsx new file mode 100644 index 00000000..2873e28f --- /dev/null +++ b/src/components/common/CenterSpinner.tsx @@ -0,0 +1,28 @@ +import React from "react"; + +import { Box, Spinner } from "@chakra-ui/react"; + +import { useProgressStateStore } from "@trustgraph/react-state"; + +const CenterSpinner: React.FC = () => { + const activity = useProgressStateStore((state) => state.activity); + + if (activity.size < 1) { + return null; + } + + return ( + + + + ); +}; + +export default CenterSpinner; diff --git a/src/components/common/ChipInputField.tsx b/src/components/common/ChipInputField.tsx new file mode 100644 index 00000000..44cecd79 --- /dev/null +++ b/src/components/common/ChipInputField.tsx @@ -0,0 +1,118 @@ +import React, { useState } from "react"; + +import { Input, Tag, Wrap, Field } from "@chakra-ui/react"; + +// Represents a label added to the list. Highlighted with a close button for +// removal. +const Chip = ({ label, onCloseClick }) => ( + + {label} + + { + onCloseClick(label); + }} + /> + + +); + +// A horizontal stack of chips. Like a Pringles can on its side. +const ChipList = ({ items = [], onCloseClick }) => ( + + {items.map((item) => ( + + ))} + +); + +// Form field wrapper. +const ChipInput = ({ ...rest }) => ; + +// Field wrapping chip list and input +const ChipInputField: React.FC<{ + values: string[]; + onValuesChange: (v: string[]) => void; + label: string; +}> = ({ values, onValuesChange, label }) => { + const [inputValue, setInputValue] = useState(""); + + // Checks whether we've added this item already. + const itemChipExists = (item) => values.includes(item); + + // Add an item to the list, if it's valid and isn't already there. + const addItems = (itemsToAdd) => { + const validatedItems = itemsToAdd + .map((e) => e.trim()) + .filter((item) => !itemChipExists(item)); + + const newItems = [...values, ...validatedItems]; + + onValuesChange(newItems); + setInputValue(""); + }; + + // Remove an item from the list. + const removeItem = (item) => { + const index = values.findIndex((e) => e === item); + if (index !== -1) { + const newItems = [...values]; + newItems.splice(index, 1); + onValuesChange(newItems); + } + }; + + // Save input field contents in state when changed. + const handleChange = (e) => { + setInputValue(e.target.value); + }; + + // Validate and add the item if we press tab, enter or comma. + const handleKeyDown = (e) => { + if (["Enter", "Tab", ","].includes(e.key)) { + e.preventDefault(); + addItems([inputValue]); + } + }; + + // Split and add items when pasting. + const handlePaste = (e) => { + e.preventDefault(); + + const pastedData = e.clipboardData.getData("text"); + const pastedItems = pastedData.split(","); + addItems(pastedItems); + }; + + const handleCloseClick = (item) => { + removeItem(item); + }; + + const required = false; + + return ( + + + {label} {required && } + + + + + + + ); +}; + +export default ChipInputField; diff --git a/src/components/common/ClickableTable.tsx b/src/components/common/ClickableTable.tsx new file mode 100644 index 00000000..86bb2141 --- /dev/null +++ b/src/components/common/ClickableTable.tsx @@ -0,0 +1,40 @@ +import { Table } from "@chakra-ui/react"; +import { flexRender } from "@tanstack/react-table"; + +const ClickableTable = ({ table, onClick, ...tableProps }) => { + return ( + <> + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + onClick(row)}> + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + + + + ); +}; + +export default ClickableTable; diff --git a/src/components/common/ConfirmDialog.tsx b/src/components/common/ConfirmDialog.tsx new file mode 100644 index 00000000..8f292a3b --- /dev/null +++ b/src/components/common/ConfirmDialog.tsx @@ -0,0 +1,139 @@ +import React from "react"; +import { Box, VStack, HStack, Text, Button } from "@chakra-ui/react"; +import { AlertTriangle, X } from "lucide-react"; + +interface ConfirmDialogProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + title: string; + message: string; + confirmText?: string; + cancelText?: string; + variant?: "danger" | "warning" | "info"; +} + +export const ConfirmDialog: React.FC = ({ + isOpen, + onClose, + onConfirm, + title, + message, + confirmText = "Confirm", + cancelText = "Cancel", + variant = "warning", +}) => { + if (!isOpen) return null; + + const handleConfirm = () => { + onConfirm(); + onClose(); + }; + + const getVariantColors = () => { + switch (variant) { + case "danger": + return { + icon: "red.500", + confirmButton: "red", + bg: "red.50", + border: "red.200", + }; + case "warning": + return { + icon: "orange.500", + confirmButton: "orange", + bg: "orange.50", + border: "orange.200", + }; + case "info": + return { + icon: "blue.500", + confirmButton: "blue", + bg: "blue.50", + border: "blue.200", + }; + default: + return { + icon: "orange.500", + confirmButton: "orange", + bg: "orange.50", + border: "orange.200", + }; + } + }; + + const colors = getVariantColors(); + + return ( + + + {/* Header */} + + + + + + {title} + + + + + + + {/* Content */} + + + + + {message} + + + + + + {/* Footer */} + + + + + + + + + ); +}; diff --git a/src/components/common/ConnectionStatus.tsx b/src/components/common/ConnectionStatus.tsx new file mode 100644 index 00000000..7b6b918e --- /dev/null +++ b/src/components/common/ConnectionStatus.tsx @@ -0,0 +1,117 @@ +import React from "react"; +import { Box, HStack, Text, Tooltip } from "@chakra-ui/react"; +import { Info, Clock, Wifi, WifiOff, Shield, ShieldOff } from "lucide-react"; +import { useConnectionState } from "@trustgraph/react-provider"; +import type { ConnectionState } from "@trustgraph/client"; + +interface ConnectionStatusProps { + showDetails?: boolean; + size?: "sm" | "md" | "lg"; +} + +const getStatusDisplay = (state: ConnectionState) => { + switch (state.status) { + case "connecting": + return { + icon: Clock, + color: "yellow.500", + text: "Connecting...", + tooltip: "Establishing connection to server", + }; + + case "connected": + return { + icon: Wifi, + color: "green.500", + text: "Connected", + tooltip: "Connected to server", + }; + + case "authenticated": + return { + icon: Shield, + color: "green.500", + text: "Authenticated", + tooltip: "Connected with API key authentication", + }; + + case "unauthenticated": + return { + icon: ShieldOff, + color: "blue.500", + text: "Unauthenticated", + tooltip: "Connected but no API key provided (limited functionality)", + }; + + case "reconnecting": + return { + icon: Clock, + color: "orange.500", + text: `Reconnecting... (${state.reconnectAttempt}/${state.maxAttempts})`, + tooltip: `Attempting to reconnect. Try ${state.reconnectAttempt} of ${state.maxAttempts}`, + }; + + case "failed": + return { + icon: WifiOff, + color: "red.500", + text: "Connection Failed", + tooltip: + state.lastError || "Connection failed after maximum retry attempts", + }; + + default: + return { + icon: Info, + color: "gray.500", + text: "Unknown", + tooltip: "Unknown connection state", + }; + } +}; + +export const ConnectionStatus: React.FC = ({ + showDetails = false, + size = "md", +}) => { + const connectionState = useConnectionState(); + + if (!connectionState) { + return null; + } + + const { + icon: StatusIcon, + color, + text, + tooltip, + } = getStatusDisplay(connectionState); + + const iconSize = size === "sm" ? 16 : size === "lg" ? 24 : 20; + const fontSize = size === "sm" ? "xs" : size === "lg" ? "md" : "sm"; + + return ( + + + + + + + + {showDetails ? text : connectionState.status} + + {showDetails && connectionState.hasApiKey && ( + + (API Key) + + )} + + + + {tooltip} + + + ); +}; + +export default ConnectionStatus; diff --git a/src/components/common/EntityList.tsx b/src/components/common/EntityList.tsx new file mode 100644 index 00000000..51518a85 --- /dev/null +++ b/src/components/common/EntityList.tsx @@ -0,0 +1,39 @@ +import { useNavigate } from "react-router"; + +import { HStack, Tag } from "@chakra-ui/react"; + +import { Entity } from "@trustgraph/react-state"; +import { useWorkbenchStateStore } from "@trustgraph/react-state"; + +const EntityList = () => { + const entities = useWorkbenchStateStore((state) => state.entities); + const setSelected = useWorkbenchStateStore((state) => state.setSelected); + + const navigate = useNavigate(); + + const onSelect = (x: Entity) => { + setSelected(x); + navigate("/entity"); + }; + + return ( + + {entities.slice(0, 8).map((entity, ix) => ( + + + + ))} + + ); +}; + +export default EntityList; diff --git a/src/components/common/ExternalDocs.tsx b/src/components/common/ExternalDocs.tsx new file mode 100644 index 00000000..8aa03b59 --- /dev/null +++ b/src/components/common/ExternalDocs.tsx @@ -0,0 +1,17 @@ +import React, { PropsWithChildren } from "react"; + +import { Link } from "@chakra-ui/react"; + +const ExternalDocs: React.FC< + PropsWithChildren<{ + href: string; + }> +> = ({ href, children }) => { + return ( + + {children} + + ); +}; + +export default ExternalDocs; diff --git a/src/components/common/FlowSelector.tsx b/src/components/common/FlowSelector.tsx new file mode 100644 index 00000000..f45fc3d3 --- /dev/null +++ b/src/components/common/FlowSelector.tsx @@ -0,0 +1,255 @@ +import { useState } from "react"; + +import { Text, Box, Stack, HStack, Popover, Portal } from "@chakra-ui/react"; + +import { Database, Workflow } from "lucide-react"; + +import { useSessionStore } from "@trustgraph/react-state"; +import { useFlows } from "@trustgraph/react-state"; +import { useSettings } from "@trustgraph/react-state"; +import { useCollections } from "@trustgraph/react-state"; + +const FlowSelector = () => { + const flowState = useFlows(); + const flows = flowState.flows ? flowState.flows : []; + + const collectionsState = useCollections(); + const collections = collectionsState.collections || []; + + const flowId = useSessionStore((state) => state.flowId); + + const setFlowId = useSessionStore((state) => state.setFlowId); + const setFlow = useSessionStore((state) => state.setFlow); + + const { settings, updateSetting } = useSettings(); + + const [open, setOpen] = useState(false); + + return ( + setOpen(e.open)} + size="xl" + positioning={{ placement: "bottom-end" }} + > + + setOpen(true)} + cursor="pointer" + > + + + + {settings.collection} + + + + + + + {flowId || ""} + + + + + + + + + + + {/* Collection Selection */} + + + Select Collection + + + {collections.map((collection) => { + const isSelected = + settings.collection === collection.collection; + return ( + { + updateSetting("collection", collection.collection); + }} + > + + + {isSelected && ( + + )} + + + + {collection.name} + + + {collection.description} + + + + + ); + })} + + + + {/* Flow Selection */} + + + Select Flow + + + {flows.map((flow) => { + const isSelected = flowId === flow.id; + return ( + { + setFlowId(flow.id); + setFlow(flow); + }} + > + + + {isSelected && ( + + )} + + + + {flow.id} + + + {flow.description} + + + + + ); + })} + + + + + + + + + ); +}; + +export default FlowSelector; diff --git a/src/components/common/NumberField.tsx b/src/components/common/NumberField.tsx new file mode 100644 index 00000000..b08f8148 --- /dev/null +++ b/src/components/common/NumberField.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { Field, NumberInput } from "@chakra-ui/react"; + +interface NumberFieldProps { + label: string; + + minValue: number; + maxValue: number; + value: number; + onValueChange: (x: number) => void; +} + +const NumberField: React.FC = ({ + label, + minValue, + maxValue, + value, + onValueChange, +}) => { + return ( + + {label} + { + const numValue = + e.value === "" || e.value == null ? 0 : Number(e.value); + if (!isNaN(numValue)) { + onValueChange(numValue); + } + }} + > + + + + + + + + ); +}; + +export default NumberField; diff --git a/src/components/common/OptionWithImage.tsx b/src/components/common/OptionWithImage.tsx new file mode 100644 index 00000000..c47ae847 --- /dev/null +++ b/src/components/common/OptionWithImage.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { + Box, + Stack, + Image, + Flex, + Heading, + Text, + Center, +} from "@chakra-ui/react"; + +const OptionWithImage: React.FC<{ + image: string; + title: string; + description?: string | React.ReactNode; + badge?: React.ReactNode; +}> = ({ description, title, image, badge }) => { + return ( + + + +
+ {title} +
+
+ + + + {title} + + {badge && badge} + + + {description} + + +
+
+ ); +}; + +export default OptionWithImage; diff --git a/src/components/common/PageHeader.tsx b/src/components/common/PageHeader.tsx new file mode 100644 index 00000000..46f37f6c --- /dev/null +++ b/src/components/common/PageHeader.tsx @@ -0,0 +1,64 @@ +import React from "react"; + +import { Flex, Text, Box, HStack, VStack, Heading } from "@chakra-ui/react"; + +import ColorModeToggle from "../color-mode-toggle"; +import FlowSelector from "./FlowSelector"; +import ConnectionStatus from "./ConnectionStatus"; +import UserDisplay from "./UserDisplay"; + +interface PageHeaderProps { + title: string; + description: string; + icon?: React.ReactNode; +} + +const PageHeader: React.FC = ({ + title, + description, + icon, +}) => { + return ( + + + {icon && ( + + {icon} + + )} + + + {title} + + + {description} + + + + + + + + + + + + + + + ); +}; + +export default PageHeader; diff --git a/src/components/common/Progress.tsx b/src/components/common/Progress.tsx new file mode 100644 index 00000000..2aa1df39 --- /dev/null +++ b/src/components/common/Progress.tsx @@ -0,0 +1,41 @@ +import React from "react"; + +import { Box, Text } from "@chakra-ui/react"; + +import { useProgressStateStore } from "@trustgraph/react-state"; + +const Progress: React.FC = () => { + const activity = useProgressStateStore((state) => state.activity); + + return ( + <> + {activity.size > 0 && ( + + {Array.from(activity) + .slice(0, 4) + .map((a, ix) => ( + + {a}... + + ))} + + )} + + ); +}; + +export default Progress; diff --git a/src/components/common/ProgressSubmitButton.tsx b/src/components/common/ProgressSubmitButton.tsx new file mode 100644 index 00000000..4dface22 --- /dev/null +++ b/src/components/common/ProgressSubmitButton.tsx @@ -0,0 +1,33 @@ +import React from "react"; + +import { SendHorizontal } from "lucide-react"; + +import { Box, Button } from "@chakra-ui/react"; + +interface ProgressSubmitButtonProps { + disabled: boolean; + working: boolean; + onClick: () => void; +} + +const ProgressSubmitButton: React.FC = ({ + disabled, + working, + onClick, +}) => { + return ( + + + + ); +}; + +export default ProgressSubmitButton; diff --git a/src/components/common/RecommendedBadge.tsx b/src/components/common/RecommendedBadge.tsx new file mode 100644 index 00000000..8e45393b --- /dev/null +++ b/src/components/common/RecommendedBadge.tsx @@ -0,0 +1,11 @@ +import { Badge } from "@chakra-ui/react"; + +const RecommendedBadge = () => { + return ( + + recommended + + ); +}; + +export default RecommendedBadge; diff --git a/src/components/common/SelectField.tsx b/src/components/common/SelectField.tsx new file mode 100644 index 00000000..d9e2c08b --- /dev/null +++ b/src/components/common/SelectField.tsx @@ -0,0 +1,92 @@ +/* + * CRITICAL: DO NOT MODIFY THIS COMPONENT WITHOUT DESIGN AUTHORITY APPROVAL + * + * This SelectField component is used throughout the application by 15+ components + * across multiple domains (ontologies, flows, documents, etc.). Any changes to + * this component's interface or behavior will have extensive downstream impact. + * + * Changes to this component in September 2025 broke multiple features and required + * systematic updates across the entire application. Always prefer adapter patterns + * or feature-specific solutions over modifying this shared infrastructure. + * + * Required API contract: + * - value: string[] (arrays only) + * - onValueChange: (values: string[]) => void + * - items must include description fields with SelectOptionText/SelectOption + */ + +import React, { useMemo } from "react"; +import { + Field, + Select, + Portal, + Stack, + createListCollection, +} from "@chakra-ui/react"; + +export interface SelectFieldValue { + value: string; + label: string; + description?: string | React.ReactElement; +} + +interface SelectFieldProps { + label: string; + + items: SelectFieldValue[]; + + value: string[]; + onValueChange: (x: string[]) => void; + + contentRef?; +} + +const SelectField: React.FC = ({ + label, + items, + value, + onValueChange, + contentRef, +}) => { + // Only create new collection when items actually change + const collection = useMemo( + () => createListCollection({ items: items }), + [items], + ); + + return ( + + {label} + + onValueChange(e.value)} + > + + + + + + + + + + + + + {items.map((v) => ( + + {v.description && v.description} + + + ))} + + + + + + ); +}; + +export default SelectField; diff --git a/src/components/common/SelectOption.tsx b/src/components/common/SelectOption.tsx new file mode 100644 index 00000000..40422d13 --- /dev/null +++ b/src/components/common/SelectOption.tsx @@ -0,0 +1,34 @@ +/* + * CRITICAL: DO NOT MODIFY THIS COMPONENT WITHOUT DESIGN AUTHORITY APPROVAL + * + * This SelectOption component is used by SelectField throughout the application. + * Changes to this component's interface or styling will affect all dropdown + * options across multiple domains. Any modifications require extensive testing + * and approval from the application design authority. + */ + +import React, { PropsWithChildren } from "react"; +import { Box, Flex, Heading, Text } from "@chakra-ui/react"; + +const SelectOption: React.FC< + PropsWithChildren<{ + title: string; + badge?: React.ReactNode; + }> +> = ({ title, badge, children }) => { + return ( + + + + {title} + + {badge && badge} + + + {children} + + + ); +}; + +export default SelectOption; diff --git a/src/components/common/SelectOptionText.tsx b/src/components/common/SelectOptionText.tsx new file mode 100644 index 00000000..34d79069 --- /dev/null +++ b/src/components/common/SelectOptionText.tsx @@ -0,0 +1,23 @@ +/* + * CRITICAL: DO NOT MODIFY THIS COMPONENT WITHOUT DESIGN AUTHORITY APPROVAL + * + * This SelectOptionText component is used by SelectField throughout the application. + * Changes to this component's interface or styling will affect all dropdown + * options across multiple domains. Any modifications require extensive testing + * and approval from the application design authority. + */ + +import React from "react"; +import { Text } from "@chakra-ui/react"; + +const SelectOptionText: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { + return ( + + {children} + + ); +}; + +export default SelectOptionText; diff --git a/src/components/common/SelectableTable.tsx b/src/components/common/SelectableTable.tsx new file mode 100644 index 00000000..f080caa4 --- /dev/null +++ b/src/components/common/SelectableTable.tsx @@ -0,0 +1,40 @@ +import { Table } from "@chakra-ui/react"; +import { flexRender } from "@tanstack/react-table"; + +const SelectableTable = ({ table }) => { + return ( + <> + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + + + + ); +}; + +export default SelectableTable; diff --git a/src/components/common/SimplePage.tsx b/src/components/common/SimplePage.tsx new file mode 100644 index 00000000..adb35715 --- /dev/null +++ b/src/components/common/SimplePage.tsx @@ -0,0 +1,45 @@ +import React, { PropsWithChildren } from "react"; + +import { Box, Container, Flex, Heading, Stack } from "@chakra-ui/react"; + +import UnauthedHeader from "./UnauthedHeader"; + +const SimplePage: React.FC< + PropsWithChildren<{ + title: string; + }> +> = ({ title, children }) => { + return ( + <> + + + + + + + {title} + + + {children} + + + + + + ); +}; + +export default SimplePage; diff --git a/src/components/common/Slider.tsx b/src/components/common/Slider.tsx new file mode 100644 index 00000000..506b605a --- /dev/null +++ b/src/components/common/Slider.tsx @@ -0,0 +1,45 @@ +import React from "react"; + +import { Field, Slider as ChakraSlider } from "@chakra-ui/react"; + +interface SliderProps { + label: string; + minValue: number; + maxValue: number; + value: number; + step: number; + onValueChange: (x: number) => void; +} + +const Slider: React.FC = ({ + label, + minValue, + maxValue, + value, + onValueChange, + step, +}) => { + return ( + + {label} + onValueChange(e.value[0])} + width="100%" + > + + + + + + + + + + ); +}; + +export default Slider; diff --git a/src/components/common/StatusBadge.tsx b/src/components/common/StatusBadge.tsx new file mode 100644 index 00000000..29f5a936 --- /dev/null +++ b/src/components/common/StatusBadge.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { Badge, BadgeProps } from "@chakra-ui/react"; +type StatusType = "success" | "warning" | "error" | "info" | "default"; +interface StatusBadgeProps extends Omit { + status: StatusType; + label: string; +} +const StatusBadge: React.FC = ({ + status, + label, + ...rest +}) => { + const colorSchemes: Record = { + success: "#65c97a", + warning: "orange", + error: "red", + info: "#5285ed", + default: "gray", + }; + return ( + + {label} + + ); +}; +export default StatusBadge; diff --git a/src/components/common/TableStates.tsx b/src/components/common/TableStates.tsx new file mode 100644 index 00000000..d09a4bbc --- /dev/null +++ b/src/components/common/TableStates.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { Box, Text, Center } from "@chakra-ui/react"; + +interface ErrorStateProps { + error: Error | unknown; + title?: string; +} + +export const ErrorState: React.FC = ({ + error, + title = "Error loading data", +}) => ( + + + {title}: {error?.toString()} + + +); + +interface EmptyStateProps { + message?: string; +} + +export const EmptyState: React.FC = ({ + message = "No data found.", +}) => ( +
+ {message} +
+); diff --git a/src/components/common/TableWithStates.tsx b/src/components/common/TableWithStates.tsx new file mode 100644 index 00000000..02e0efff --- /dev/null +++ b/src/components/common/TableWithStates.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { Box } from "@chakra-ui/react"; +import { Table } from "@tanstack/react-table"; +import { ErrorState, EmptyState } from "./TableStates"; +import BasicTable from "./BasicTable"; +import ClickableTable from "./ClickableTable"; + +interface TableWithStatesProps { + table: Table; + data: T[]; + error?: Error | unknown; + onClick?: (row: T) => void; + emptyMessage?: string; + errorTitle?: string; + bordered?: boolean; +} + +const TableWithStates = ({ + table, + data, + error, + onClick, + emptyMessage = "No data found.", + errorTitle = "Error loading data", + bordered = true, +}: TableWithStatesProps) => { + // Handle error state + if (error) { + return ; + } + + // Handle empty state + if (data.length === 0) { + return ; + } + + // Render table with optional border wrapper + const TableComponent = onClick ? ( + onClick(row.original)} /> + ) : ( + + ); + + if (bordered) { + return ( + + {TableComponent} + + ); + } + + return TableComponent; +}; + +export default TableWithStates; diff --git a/src/components/common/TextAreaField.tsx b/src/components/common/TextAreaField.tsx new file mode 100644 index 00000000..44b79493 --- /dev/null +++ b/src/components/common/TextAreaField.tsx @@ -0,0 +1,40 @@ +import React from "react"; + +import { Field, Textarea } from "@chakra-ui/react"; + +interface TextFieldProps { + label: string; + placeholder?: string; + value: string; + onValueChange: (x: string) => void; + required?: boolean; + disabled?: boolean; +} + +const TextAreaField: React.FC = ({ + label, + placeholder, + value, + onValueChange, + required, + disabled, +}) => { + return ( + + + {label} {required && } + +