diff --git a/burr/tracking/server/run.py b/burr/tracking/server/run.py index 2e9759659..b9c85d531 100644 --- a/burr/tracking/server/run.py +++ b/burr/tracking/server/run.py @@ -26,6 +26,7 @@ # TODO -- remove this, just for testing from burr.log_setup import setup_logging +from burr.tracking.server import workspace from burr.tracking.server.backend import ( AnnotationsBackendMixin, BackendBase, @@ -137,6 +138,7 @@ async def lifespan(app: FastAPI): global initialized initialized = True yield + await workspace.cleanup_processes() await backend.lifespan(app).__anext__() @@ -151,6 +153,7 @@ def _get_app_spec() -> BackendSpec: snapshotting=is_snapshotting_backend, supports_demos=supports_demos, supports_annotations=is_annotations_backend, + supports_workspace=True, ) @@ -342,6 +345,7 @@ async def version() -> dict: burr_version = "unknown" return {"version": burr_version} + ui_app.include_router(workspace.router, prefix="/api/v0/workspace") # Examples -- todo -- put them behind `if` statements ui_app.include_router(chatbot.router, prefix="/api/v0/chatbot") ui_app.include_router(email_assistant.router, prefix="/api/v0/email_assistant") diff --git a/burr/tracking/server/schema.py b/burr/tracking/server/schema.py index 749f1e3a2..78b277f8d 100644 --- a/burr/tracking/server/schema.py +++ b/burr/tracking/server/schema.py @@ -200,6 +200,7 @@ class BackendSpec(pydantic.BaseModel): snapshotting: bool supports_demos: bool supports_annotations: bool + supports_workspace: bool class AnnotationDataPointer(pydantic.BaseModel): diff --git a/burr/tracking/server/workspace.py b/burr/tracking/server/workspace.py new file mode 100644 index 000000000..4ab11de3b --- /dev/null +++ b/burr/tracking/server/workspace.py @@ -0,0 +1,664 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +"""Workspace API router for the Burr tracking server. + +Provides endpoints for: +- File browsing and content reading within a workspace directory +- Python script execution with SSE output streaming +- Process lifecycle management (start, stop, list) +- Workspace-to-project linking (stored in ~/.burr/workspace_links.json) +- Builder project persistence (stored in ~/.burr/builder_projects/) +- Burr ApplicationBuilder usage scanning across .py files +""" + +import asyncio +import json as json_module +import logging +import os +import re +import signal +import time +from typing import Dict, List, Optional + +from fastapi import APIRouter, HTTPException, Query +from fastapi.responses import StreamingResponse +from pydantic import BaseModel + +logger = logging.getLogger(__name__) + +router = APIRouter() + +MAX_FILE_SIZE = 1_048_576 # 1MB + +LANGUAGE_MAP = { + ".py": "python", + ".js": "javascript", + ".ts": "typescript", + ".tsx": "tsx", + ".jsx": "jsx", + ".json": "json", + ".yaml": "yaml", + ".yml": "yaml", + ".toml": "toml", + ".md": "markdown", + ".html": "html", + ".css": "css", + ".sql": "sql", + ".sh": "bash", + ".txt": "text", +} + +BURR_APP_PATTERN = re.compile(r"ApplicationBuilder") + + +# --- Pydantic Models --- + + +class WorkspaceLinkRequest(BaseModel): + """Request body for linking a workspace directory to a project.""" + + project_id: str + workspace_path: str + + +class WorkspaceLinkInfo(BaseModel): + """Response for workspace link queries. workspace_path is None if not linked.""" + + project_id: str + workspace_path: Optional[str] + + +class BuilderProjectSave(BaseModel): + """Request body for saving a builder project.""" + + name: str + graph_json: str # JSON-serialized tree + + +class BuilderProjectSummary(BaseModel): + """Summary of a saved builder project (used in list responses).""" + + id: str + name: str + updated_at: float + + +class BuilderProjectFull(BaseModel): + """Full builder project including the serialized graph.""" + + id: str + name: str + graph_json: str + updated_at: float + + +class WorkspaceOpenRequest(BaseModel): + """Request body for opening/validating a workspace directory.""" + + path: str + + +class WorkspaceInfo(BaseModel): + """Basic info about an opened workspace.""" + + path: str + name: str + + +class FileEntry(BaseModel): + """A single file or directory entry in a workspace listing.""" + + name: str + path: str # relative to workspace root + is_dir: bool + size: int + modified: float + is_python: bool + has_burr_app: bool + + +class FileContent(BaseModel): + """Contents of a single file with detected language.""" + + path: str + content: str + language: str + size: int + + +class ProcessInfo(BaseModel): + """Status of a running or completed Python process.""" + + pid: int + script_path: str + started_at: float + status: str # "running" | "stopped" | "exited" + exit_code: Optional[int] + + +class RunRequest(BaseModel): + """Request body to start a Python script.""" + + workspace: str + script: str + + +# --- Security --- + + +def _validate_path(workspace: str, relative: str) -> str: + """Resolve and validate a file path within a workspace. + + Prevents path traversal by ensuring the resolved target stays within + the workspace directory. Uses os.sep suffix check to prevent /foo + matching /foobar. + + Args: + workspace: Absolute path to the workspace root. + relative: Relative path within the workspace (may be empty). + + Returns: + The resolved absolute path to the target. + + Raises: + HTTPException: 403 if path traversal is detected. + """ + workspace_real = os.path.realpath(workspace) + if relative: + target = os.path.realpath(os.path.join(workspace_real, relative)) + else: + target = workspace_real + # Use os.sep suffix to prevent /foo matching /foobar + if target != workspace_real and not target.startswith(workspace_real + os.sep): + raise HTTPException(status_code=403, detail="Path traversal detected") + return target + + +def _validate_workspace(workspace: str) -> str: + """Validate that a workspace path is registered via the /link endpoint. + + Checks the workspace against ~/.burr/workspace_links.json to ensure + only explicitly linked directories can be accessed. + + Args: + workspace: Absolute path to validate. + + Returns: + The resolved absolute path. + + Raises: + HTTPException: 400 if directory doesn't exist, 403 if not registered. + """ + workspace_real = os.path.realpath(workspace) + if not os.path.isdir(workspace_real): + raise HTTPException(status_code=400, detail="Workspace directory does not exist") + links = _read_links() + allowed = {os.path.realpath(p) for p in links.values()} + if workspace_real not in allowed: + raise HTTPException(status_code=403, detail="Workspace not registered") + return workspace_real + + +def _is_binary(file_path: str) -> bool: + """Detect if a file is binary by checking for null bytes in the first 8KB.""" + try: + with open(file_path, "rb") as f: + chunk = f.read(8192) + return b"\x00" in chunk + except OSError: + return True + + +# --- Workspace Links --- + +_LINKS_PATH = os.path.join(os.path.expanduser("~/.burr"), "workspace_links.json") + + +def _read_links() -> dict: + """Read workspace-to-project links from ~/.burr/workspace_links.json.""" + if os.path.exists(_LINKS_PATH): + with open(_LINKS_PATH, "r") as f: + return json_module.load(f) + return {} + + +def _write_links(data: dict): + """Write workspace-to-project links to ~/.burr/workspace_links.json.""" + os.makedirs(os.path.dirname(_LINKS_PATH), exist_ok=True) + with open(_LINKS_PATH, "w") as f: + json_module.dump(data, f, indent=2) + + +# --- Process Management --- +# Uses asyncio.create_subprocess_exec which takes explicit argv (no shell injection). + +_processes: Dict[int, dict] = {} + + +async def cleanup_processes(): + """Terminate all tracked subprocesses. Called during FastAPI lifespan shutdown.""" + for pid, info in list(_processes.items()): + proc = info.get("process") + if proc and proc.returncode is None: + try: + proc.terminate() + await asyncio.wait_for(proc.wait(), timeout=5) + except (ProcessLookupError, asyncio.TimeoutError): + try: + proc.kill() + except ProcessLookupError: + pass + _processes.clear() + + +# --- Endpoints --- + + +@router.post("/open", response_model=WorkspaceInfo) +async def open_workspace(request: WorkspaceOpenRequest): + """Validate and return info about a workspace directory.""" + path = os.path.realpath(request.path) + if not os.path.isdir(path): + raise HTTPException(status_code=400, detail="Directory does not exist") + return WorkspaceInfo(path=path, name=os.path.basename(path)) + + +@router.get("/link", response_model=WorkspaceLinkInfo) +async def get_workspace_link(project_id: str = Query(...)): + """Get the linked workspace path for a project, or null if not linked.""" + links = _read_links() + return WorkspaceLinkInfo( + project_id=project_id, + workspace_path=links.get(project_id), + ) + + +@router.post("/link", response_model=WorkspaceLinkInfo) +async def set_workspace_link(request: WorkspaceLinkRequest): + """Link a workspace directory to a project.""" + path = os.path.realpath(request.workspace_path) + if not os.path.isdir(path): + raise HTTPException(status_code=400, detail="Directory does not exist") + links = _read_links() + links[request.project_id] = path + _write_links(links) + return WorkspaceLinkInfo(project_id=request.project_id, workspace_path=path) + + +@router.delete("/link") +async def remove_workspace_link(project_id: str = Query(...)): + """Remove the workspace link for a project.""" + links = _read_links() + links.pop(project_id, None) + _write_links(links) + return {"ok": True} + + +@router.get("/tree", response_model=List[FileEntry]) +async def get_tree( + workspace: str = Query(...), + relative_path: str = Query(""), +): + """List one level of a directory within a workspace.""" + _validate_workspace(workspace) + target = _validate_path(workspace, relative_path) + if not os.path.isdir(target): + raise HTTPException(status_code=400, detail="Not a directory") + + entries = [] + try: + for item in sorted(os.listdir(target)): + if item.startswith("."): + continue + full_path = os.path.join(target, item) + rel = os.path.relpath(full_path, os.path.realpath(workspace)) + try: + stat = os.stat(full_path) + except OSError: + continue + is_dir = os.path.isdir(full_path) + is_python = item.endswith(".py") + has_burr = False + if is_python and not is_dir: + try: + with open(full_path, "r", errors="ignore") as f: + content = f.read(65536) + has_burr = bool(BURR_APP_PATTERN.search(content)) + except OSError: + pass + entries.append( + FileEntry( + name=item, + path=rel, + is_dir=is_dir, + size=stat.st_size if not is_dir else 0, + modified=stat.st_mtime, + is_python=is_python, + has_burr_app=has_burr, + ) + ) + except PermissionError: + raise HTTPException(status_code=403, detail="Permission denied") + return entries + + +@router.get("/file", response_model=FileContent) +async def get_file( + workspace: str = Query(...), + relative_path: str = Query(...), +): + """Read the contents of a file within a workspace. Max 1MB, no binaries.""" + _validate_workspace(workspace) + target = _validate_path(workspace, relative_path) + if not os.path.isfile(target): + raise HTTPException(status_code=404, detail="File not found") + size = os.path.getsize(target) + if size > MAX_FILE_SIZE: + raise HTTPException(status_code=413, detail="File too large (max 1MB)") + if _is_binary(target): + raise HTTPException(status_code=415, detail="Binary file not supported") + + ext = os.path.splitext(target)[1].lower() + language = LANGUAGE_MAP.get(ext, "text") + + with open(target, "r", errors="replace") as f: + content = f.read() + + return FileContent( + path=relative_path, + content=content, + language=language, + size=size, + ) + + +@router.post("/run", response_model=ProcessInfo) +async def run_script(request: RunRequest): + """Start a Python script as a subprocess within a workspace.""" + _validate_workspace(request.workspace) + target = _validate_path(request.workspace, request.script) + if not os.path.isfile(target): + raise HTTPException(status_code=404, detail="Script not found") + if not target.endswith(".py"): + raise HTTPException(status_code=400, detail="Only Python scripts supported") + + # asyncio.create_subprocess_exec takes an explicit argv list, + # so there is no shell interpretation and no injection risk. + proc = await asyncio.create_subprocess_exec( + "python", + target, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + cwd=os.path.realpath(request.workspace), + ) + + started_at = time.time() + info = { + "process": proc, + "script_path": request.script, + "started_at": started_at, + "workspace": request.workspace, + } + _processes[proc.pid] = info + + return ProcessInfo( + pid=proc.pid, + script_path=request.script, + started_at=started_at, + status="running", + exit_code=None, + ) + + +@router.get("/run/{pid}/output") +async def stream_output(pid: int): + """Stream stdout/stderr from a running process via Server-Sent Events.""" + if pid not in _processes: + raise HTTPException(status_code=404, detail="Process not found") + + proc = _processes[pid]["process"] + + async def event_generator(): + async def read_stream(stream, stream_type): + while True: + line = await stream.readline() + if not line: + break + text = line.decode("utf-8", errors="replace") + yield f'data: {{"type": "{stream_type}", "data": {_json_escape(text)}}}\n\n' + + stdout_gen = read_stream(proc.stdout, "stdout") + stderr_gen = read_stream(proc.stderr, "stderr") + + stdout_done = False + stderr_done = False + + while not stdout_done or not stderr_done: + tasks = [] + if not stdout_done: + tasks.append(("stdout", stdout_gen)) + if not stderr_done: + tasks.append(("stderr", stderr_gen)) + + for name, gen in tasks: + try: + line = await asyncio.wait_for(gen.__anext__(), timeout=0.1) + yield line + except StopAsyncIteration: + if name == "stdout": + stdout_done = True + else: + stderr_done = True + except asyncio.TimeoutError: + continue + + exit_code = await proc.wait() + yield f'data: {{"type": "exit", "data": "{exit_code}"}}\n\n' + + return StreamingResponse( + event_generator(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "X-Accel-Buffering": "no", + }, + ) + + +@router.post("/run/{pid}/stop", response_model=ProcessInfo) +async def stop_process(pid: int): + """Send SIGTERM to a running process.""" + if pid not in _processes: + raise HTTPException(status_code=404, detail="Process not found") + + info = _processes[pid] + proc = info["process"] + + if proc.returncode is None: + try: + proc.send_signal(signal.SIGTERM) + await asyncio.wait_for(proc.wait(), timeout=5) + except (ProcessLookupError, asyncio.TimeoutError): + try: + proc.kill() + except ProcessLookupError: + pass + + return ProcessInfo( + pid=pid, + script_path=info["script_path"], + started_at=info["started_at"], + status="stopped" if proc.returncode is None else "exited", + exit_code=proc.returncode, + ) + + +@router.get("/processes", response_model=List[ProcessInfo]) +async def list_processes(workspace: str = Query(...)): + """List all tracked processes for a workspace.""" + _validate_workspace(workspace) + result = [] + for pid, info in _processes.items(): + if info["workspace"] != workspace: + continue + proc = info["process"] + if proc.returncode is None: + status = "running" + else: + status = "exited" + result.append( + ProcessInfo( + pid=pid, + script_path=info["script_path"], + started_at=info["started_at"], + status=status, + exit_code=proc.returncode, + ) + ) + return result + + +@router.get("/scan", response_model=List[FileEntry]) +async def scan_burr_apps(workspace: str = Query(...)): + """Scan a workspace for .py files containing ApplicationBuilder usage.""" + _validate_workspace(workspace) + workspace_real = os.path.realpath(workspace) + if not os.path.isdir(workspace_real): + raise HTTPException(status_code=400, detail="Workspace not found") + + results = [] + for root, dirs, filenames in os.walk(workspace_real): + dirs[:] = [ + d for d in dirs if not d.startswith(".") and d != "__pycache__" and d != "node_modules" + ] + for fname in filenames: + if not fname.endswith(".py"): + continue + full_path = os.path.join(root, fname) + rel = os.path.relpath(full_path, workspace_real) + try: + with open(full_path, "r", errors="ignore") as f: + content = f.read(65536) + if BURR_APP_PATTERN.search(content): + stat = os.stat(full_path) + results.append( + FileEntry( + name=fname, + path=rel, + is_dir=False, + size=stat.st_size, + modified=stat.st_mtime, + is_python=True, + has_burr_app=True, + ) + ) + except OSError: + continue + return results + + +def _json_escape(s: str) -> str: + """Escape a string for embedding in SSE JSON data.""" + import json + + return json.dumps(s) + + +# --- Builder Projects --- + +_BUILDER_DIR = os.path.join(os.path.expanduser("~/.burr"), "builder_projects") + + +def _ensure_builder_dir(): + """Create the builder projects directory if it doesn't exist.""" + os.makedirs(_BUILDER_DIR, exist_ok=True) + + +@router.get("/builder/projects", response_model=List[BuilderProjectSummary]) +async def list_builder_projects(): + """List all saved builder projects from ~/.burr/builder_projects/.""" + _ensure_builder_dir() + projects = [] + for fname in sorted(os.listdir(_BUILDER_DIR)): + if not fname.endswith(".json"): + continue + fpath = os.path.join(_BUILDER_DIR, fname) + try: + with open(fpath, "r") as f: + data = json_module.load(f) + projects.append( + BuilderProjectSummary( + id=fname.replace(".json", ""), + name=data.get("name", fname), + updated_at=os.path.getmtime(fpath), + ) + ) + except (OSError, json_module.JSONDecodeError): + continue + return sorted(projects, key=lambda p: p.updated_at, reverse=True) + + +@router.post("/builder/projects", response_model=BuilderProjectFull) +async def save_builder_project(request: BuilderProjectSave): + """Save a builder project graph to ~/.burr/builder_projects/.""" + _ensure_builder_dir() + # Generate ID from name + project_id = re.sub(r"[^a-zA-Z0-9_-]", "_", request.name).lower() + if not project_id: + project_id = "untitled" + fpath = os.path.join(_BUILDER_DIR, f"{project_id}.json") + data = { + "name": request.name, + "graph_json": request.graph_json, + } + with open(fpath, "w") as f: + json_module.dump(data, f, indent=2) + return BuilderProjectFull( + id=project_id, + name=request.name, + graph_json=request.graph_json, + updated_at=time.time(), + ) + + +@router.get("/builder/projects/{project_id}", response_model=BuilderProjectFull) +async def get_builder_project(project_id: str): + """Load a saved builder project by ID.""" + safe_id = re.sub(r"[^a-zA-Z0-9_-]", "", project_id) + fpath = os.path.join(_BUILDER_DIR, f"{safe_id}.json") + if not os.path.isfile(fpath): + raise HTTPException(status_code=404, detail="Project not found") + with open(fpath, "r") as f: + data = json_module.load(f) + return BuilderProjectFull( + id=safe_id, + name=data.get("name", safe_id), + graph_json=data.get("graph_json", "{}"), + updated_at=os.path.getmtime(fpath), + ) + + +@router.delete("/builder/projects/{project_id}") +async def delete_builder_project(project_id: str): + """Delete a saved builder project by ID.""" + safe_id = re.sub(r"[^a-zA-Z0-9_-]", "", project_id) + fpath = os.path.join(_BUILDER_DIR, f"{safe_id}.json") + if os.path.isfile(fpath): + os.remove(fpath) + return {"ok": True} diff --git a/telemetry/ui/package-lock.json b/telemetry/ui/package-lock.json index 95f8310a7..1656fa1e5 100644 --- a/telemetry/ui/package-lock.json +++ b/telemetry/ui/package-lock.json @@ -11,10 +11,11 @@ "@headlessui/react": "^2.1.9", "@heroicons/react": "^2.1.1", "@microsoft/fetch-event-source": "^2.0.1", + "@monaco-editor/react": "^4.7.0", + "@tanstack/react-query": "^5.95.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", - "@tisoap/react-flow-smart-edge": "^3.0.0", "@types/fuse": "^2.6.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.82", @@ -23,6 +24,7 @@ "@types/react-select": "^5.0.1", "@types/react-syntax-highlighter": "^15.5.11", "@uiw/react-json-view": "^2.0.0-alpha.12", + "@xyflow/react": "^12.10.1", "clsx": "^2.1.0", "dagre": "^0.8.5", "es-abstract": "^1.22.4", @@ -31,13 +33,12 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", + "react-joyride": "^2.9.3", "react-markdown": "^9.0.1", - "react-query": "^3.39.3", "react-router-dom": "^6.22.1", "react-scripts": "5.0.1", "react-select": "^5.8.1", "react-syntax-highlighter": "^15.5.0", - "reactflow": "^11.10.4", "remark-gfm": "^4.0.0", "string.prototype.matchall": "^4.0.10", "tailwindcss-question-mark": "^0.4.0", @@ -3245,20 +3246,22 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.1" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react": { @@ -3276,11 +3279,12 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.0.0" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -3288,9 +3292,16 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@gilbarbara/deep-equal": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz", + "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==", + "license": "MIT" }, "node_modules/@graphql-codegen/add": { "version": "5.0.3", @@ -5560,6 +5571,29 @@ "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==" }, + "node_modules/@monaco-editor/loader": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", + "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@next/env": { "version": "14.2.14", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.14.tgz", @@ -6258,102 +6292,6 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@reactflow/background": { - "version": "11.3.9", - "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.9.tgz", - "integrity": "sha512-byj/G9pEC8tN0wT/ptcl/LkEP/BBfa33/SvBkqE4XwyofckqF87lKp573qGlisfnsijwAbpDlf81PuFL41So4Q==", - "dependencies": { - "@reactflow/core": "11.10.4", - "classcat": "^5.0.3", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@reactflow/controls": { - "version": "11.2.9", - "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.9.tgz", - "integrity": "sha512-e8nWplbYfOn83KN1BrxTXS17+enLyFnjZPbyDgHSRLtI5ZGPKF/8iRXV+VXb2LFVzlu4Wh3la/pkxtfP/0aguA==", - "dependencies": { - "@reactflow/core": "11.10.4", - "classcat": "^5.0.3", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@reactflow/core": { - "version": "11.10.4", - "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.10.4.tgz", - "integrity": "sha512-j3i9b2fsTX/sBbOm+RmNzYEFWbNx4jGWGuGooh2r1jQaE2eV+TLJgiG/VNOp0q5mBl9f6g1IXs3Gm86S9JfcGw==", - "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/minimap": { - "version": "11.7.9", - "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.9.tgz", - "integrity": "sha512-le95jyTtt3TEtJ1qa7tZ5hyM4S7gaEQkW43cixcMOZLu33VAdc2aCpJg/fXcRrrf7moN2Mbl9WIMNXUKsp5ILA==", - "dependencies": { - "@reactflow/core": "11.10.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/node-resizer": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.9.tgz", - "integrity": "sha512-HfickMm0hPDIHt9qH997nLdgLt0kayQyslKE0RS/GZvZ4UMQJlx/NRRyj5y47Qyg0NnC66KYOQWDM9LLzRTnUg==", - "dependencies": { - "@reactflow/core": "11.10.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-toolbar": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.9.tgz", - "integrity": "sha512-VmgxKmToax4sX1biZ9LXA7cj/TBJ+E5cklLGwquCCVVxh+lxpZGTBF3a5FJGVHiUNBBtFsC8ldcSZIK4cAlQww==", - "dependencies": { - "@reactflow/core": "11.10.4", - "classcat": "^5.0.3", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, "node_modules/@remix-run/router": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.1.tgz", @@ -6988,6 +6926,32 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.95.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.95.2.tgz", + "integrity": "sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.95.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.95.2.tgz", + "integrity": "sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.95.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.10.8", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.8.tgz", @@ -7315,24 +7279,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@tisoap/react-flow-smart-edge": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@tisoap/react-flow-smart-edge/-/react-flow-smart-edge-3.0.0.tgz", - "integrity": "sha512-XtEQT0IrOqPwJvCzgEoj3Y16/EK4SOcjZO7FmOPU+qJWmgYjeTyv7J35CGm6dFeJYdZ2gHDrvQ1zwaXuo23/8g==", - "dependencies": { - "pathfinding": "0.4.18" - }, - "engines": { - "node": ">=16", - "npm": "^8.0.0" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17", - "reactflow": ">=11", - "typescript": ">=4.6" - } - }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -7431,93 +7377,11 @@ "@types/node": "*" } }, - "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==", - "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.1", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", - "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" - }, - "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==", - "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==", - "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==" - }, "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==" }, - "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==", - "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==" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", - "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" - }, "node_modules/@types/d3-drag": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", @@ -7526,47 +7390,6 @@ "@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==" - }, - "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==" - }, - "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==", - "dependencies": { - "@types/d3-dsv": "*" - } - }, - "node_modules/@types/d3-force": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", - "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==" - }, - "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==" - }, - "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==", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz", - "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==" - }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", @@ -7575,67 +7398,11 @@ "@types/d3-color": "*" } }, - "node_modules/@types/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" - }, - "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==" - }, - "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==" - }, - "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==" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", - "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", - "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==" - }, "node_modules/@types/d3-selection": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==" }, - "node_modules/@types/d3-shape": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", - "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", - "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" - }, - "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==" - }, - "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==" - }, "node_modules/@types/d3-transition": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", @@ -7730,11 +7497,6 @@ "fuse": "*" } }, - "node_modules/@types/geojson": { - "version": "7946.0.14", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", - "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" - }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -8868,6 +8630,38 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "node_modules/@xyflow/react": { + "version": "12.10.1", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.1.tgz", + "integrity": "sha512-5eSWtIK/+rkldOuFbOOz44CRgQRjtS9v5nufk77DV+XBnfCGL9HAQ8PG00o2ZYKqkEU/Ak6wrKC95Tu+2zuK3Q==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.75", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.75", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.75.tgz", + "integrity": "sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -9865,14 +9659,6 @@ "node": ">= 8.0.0" } }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -10016,21 +9802,6 @@ "node": ">=8" } }, - "node_modules/broadcast-channel": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", - "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "detect-node": "^2.1.0", - "js-sha3": "0.8.0", - "microseconds": "0.2.0", - "nano-time": "1.0.0", - "oblivious-set": "1.0.0", - "rimraf": "3.0.2", - "unload": "2.2.0" - } - }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -10218,9 +9989,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001587", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", - "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==", + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", "funding": [ { "type": "opencollective", @@ -10234,7 +10005,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/capital-case": { "version": "1.0.4", @@ -11512,6 +11284,13 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" }, + "node_modules/deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -11863,6 +11642,16 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -15211,11 +15000,6 @@ "tslib": "^2.0.3" } }, - "node_modules/heap": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.5.tgz", - "integrity": "sha512-G7HLD+WKcrOyJP5VQwYZNC3Z6FcQ7YYjEFiFoIj8PfEr73mu421o8B1N5DKUcc8K37EsJ2XXWA8DtrDz/2dReg==" - }, "node_modules/heroicons": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/heroicons/-/heroicons-2.1.1.tgz", @@ -16092,6 +15876,12 @@ "node": ">=8" } }, + "node_modules/is-lite": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz", + "integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==", + "license": "MIT" + }, "node_modules/is-lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-2.0.2.tgz", @@ -18516,11 +18306,6 @@ "url": "https://github.com/sponsors/panva" } }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -19526,13 +19311,17 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/match-sorter": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", - "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", - "dependencies": { - "@babel/runtime": "^7.23.8", - "remove-accents": "0.5.0" + "node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" } }, "node_modules/math-intrinsics": { @@ -20568,11 +20357,6 @@ "node": ">=8.6" } }, - "node_modules/microseconds": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", - "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -20730,6 +20514,17 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "license": "MIT", + "peer": true, + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -20770,14 +20565,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nano-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", - "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", - "dependencies": { - "big-integer": "^1.6.16" - } - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -21189,11 +20976,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/oblivious-set": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", - "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" - }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -21672,14 +21454,6 @@ "node": ">=8" } }, - "node_modules/pathfinding": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/pathfinding/-/pathfinding-0.4.18.tgz", - "integrity": "sha512-R0TGEQ9GRcFCDvAWlJAWC+KGJ9SLbW4c0nuZRcioVlXVTlw+F5RvXQ8SQgSqI9KXWC1ew95vgmIiyaWTlCe9Ag==", - "dependencies": { - "heap": "0.2.5" - } - }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -21855,6 +21629,17 @@ "node": ">=4" } }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/postcss": { "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", @@ -23541,6 +23326,45 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-floater": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz", + "integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "is-lite": "^0.8.2", + "popper.js": "^1.16.0", + "prop-types": "^15.8.1", + "tree-changes": "^0.9.1" + }, + "peerDependencies": { + "react": "15 - 18", + "react-dom": "15 - 18" + } + }, + "node_modules/react-floater/node_modules/@gilbarbara/deep-equal": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz", + "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==", + "license": "MIT" + }, + "node_modules/react-floater/node_modules/is-lite": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz", + "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==", + "license": "MIT" + }, + "node_modules/react-floater/node_modules/tree-changes": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz", + "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==", + "license": "MIT", + "dependencies": { + "@gilbarbara/deep-equal": "^0.1.1", + "is-lite": "^0.8.2" + } + }, "node_modules/react-icons": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", @@ -23549,11 +23373,62 @@ "react": "*" } }, + "node_modules/react-innertext": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz", + "integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": ">=0.0.0 <=99", + "react": ">=0.0.0 <=99" + } + }, "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==" }, + "node_modules/react-joyride": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.9.3.tgz", + "integrity": "sha512-1+Mg34XK5zaqJ63eeBhqdbk7dlGCFp36FXwsEvgpjqrtyywX2C6h9vr3jgxP0bGHCw8Ilsp/nRDzNVq6HJ3rNw==", + "license": "MIT", + "dependencies": { + "@gilbarbara/deep-equal": "^0.3.1", + "deep-diff": "^1.0.2", + "deepmerge": "^4.3.1", + "is-lite": "^1.2.1", + "react-floater": "^0.7.9", + "react-innertext": "^1.1.5", + "react-is": "^16.13.1", + "scroll": "^3.0.1", + "scrollparent": "^2.1.0", + "tree-changes": "^0.11.2", + "type-fest": "^4.27.0" + }, + "peerDependencies": { + "react": "15 - 18", + "react-dom": "15 - 18" + } + }, + "node_modules/react-joyride/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-joyride/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-json-tree": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.18.0.tgz", @@ -23602,31 +23477,6 @@ "@types/unist": "*" } }, - "node_modules/react-query": { - "version": "3.39.3", - "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", - "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "broadcast-channel": "^3.4.1", - "match-sorter": "^6.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -23787,23 +23637,6 @@ "react-dom": ">=16.6.0" } }, - "node_modules/reactflow": { - "version": "11.10.4", - "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.10.4.tgz", - "integrity": "sha512-0CApYhtYicXEDg/x2kvUHiUk26Qur8lAtTtiSlptNKuyEuGti6P1y5cS32YGaUoDMoCqkm/m+jcKkfMOvSCVRA==", - "dependencies": { - "@reactflow/background": "11.3.9", - "@reactflow/controls": "11.2.9", - "@reactflow/core": "11.10.4", - "@reactflow/minimap": "11.7.9", - "@reactflow/node-resizer": "2.2.9", - "@reactflow/node-toolbar": "1.3.9" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -24083,11 +23916,6 @@ "node": "*" } }, - "node_modules/remove-accents": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", - "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" - }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -24550,6 +24378,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==", + "license": "MIT" + }, + "node_modules/scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==", + "license": "ISC" + }, "node_modules/scuid": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz", @@ -25138,6 +24978,12 @@ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, "node_modules/static-eval": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", @@ -26127,6 +25973,16 @@ "node": ">=8" } }, + "node_modules/tree-changes": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.3.tgz", + "integrity": "sha512-r14mvDZ6tqz8PRQmlFKjhUVngu4VZ9d92ON3tp0EGpFBE6PAHOq8Bx8m8ahbNoGE3uI/npjYcJiqVydyOiYXag==", + "license": "MIT", + "dependencies": { + "@gilbarbara/deep-equal": "^0.3.1", + "is-lite": "^1.2.1" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -26631,15 +26487,6 @@ "node": ">=0.10.0" } }, - "node_modules/unload": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", - "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", - "dependencies": { - "@babel/runtime": "^7.6.2", - "detect-node": "^2.0.4" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/telemetry/ui/package.json b/telemetry/ui/package.json index 1a391fcc0..36fcd5269 100644 --- a/telemetry/ui/package.json +++ b/telemetry/ui/package.json @@ -6,10 +6,11 @@ "@headlessui/react": "^2.1.9", "@heroicons/react": "^2.1.1", "@microsoft/fetch-event-source": "^2.0.1", + "@monaco-editor/react": "^4.7.0", + "@tanstack/react-query": "^5.95.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", - "@tisoap/react-flow-smart-edge": "^3.0.0", "@types/fuse": "^2.6.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.82", @@ -18,6 +19,7 @@ "@types/react-select": "^5.0.1", "@types/react-syntax-highlighter": "^15.5.11", "@uiw/react-json-view": "^2.0.0-alpha.12", + "@xyflow/react": "^12.10.1", "clsx": "^2.1.0", "dagre": "^0.8.5", "es-abstract": "^1.22.4", @@ -26,13 +28,12 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", + "react-joyride": "^2.9.3", "react-markdown": "^9.0.1", - "react-query": "^3.39.3", "react-router-dom": "^6.22.1", "react-scripts": "5.0.1", "react-select": "^5.8.1", "react-syntax-highlighter": "^15.5.0", - "reactflow": "^11.10.4", "remark-gfm": "^4.0.0", "string.prototype.matchall": "^4.0.10", "tailwindcss-question-mark": "^0.4.0", diff --git a/telemetry/ui/src/App.tsx b/telemetry/ui/src/App.tsx index 121337ae3..c3978561c 100644 --- a/telemetry/ui/src/App.tsx +++ b/telemetry/ui/src/App.tsx @@ -22,7 +22,7 @@ import './App.css'; import { ProjectList } from './components/routes/ProjectList'; import { AppList } from './components/routes/AppList'; import { AppViewContainer } from './components/routes/app/AppView'; -import { QueryClient, QueryClientProvider } from 'react-query'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AppContainer } from './components/nav/appcontainer'; import { ChatbotWithTelemetry } from './examples/Chatbot'; import { Counter } from './examples/Counter'; @@ -31,6 +31,8 @@ import { StreamingChatbotWithTelemetry } from './examples/StreamingChatbot'; import { AdminView } from './components/routes/AdminView'; import { AnnotationsViewContainer } from './components/routes/app/AnnotationsView'; import { DeepResearcherWithTelemetry } from './examples/DeepResearcher'; +import { BuilderView } from './components/routes/builder/BuilderView'; +import { WorkspaceSelector } from './components/routes/workspace/WorkspaceSelector'; /** * Basic application. We have an AppContainer -- this has a breadcrumb and a sidebar. @@ -65,6 +67,9 @@ const App = () => { } /> } /> } /> + } /> + } /> + } /> } /> diff --git a/telemetry/ui/src/api/models/BackendSpec.ts b/telemetry/ui/src/api/models/BackendSpec.ts index b9fd7ede6..61a3ce163 100644 --- a/telemetry/ui/src/api/models/BackendSpec.ts +++ b/telemetry/ui/src/api/models/BackendSpec.ts @@ -29,4 +29,5 @@ export type BackendSpec = { snapshotting: boolean; supports_demos: boolean; supports_annotations: boolean; + supports_workspace: boolean; }; diff --git a/telemetry/ui/src/api/workspaceApi.ts b/telemetry/ui/src/api/workspaceApi.ts new file mode 100644 index 000000000..36be4826d --- /dev/null +++ b/telemetry/ui/src/api/workspaceApi.ts @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import { fetchEventSource } from '@microsoft/fetch-event-source'; + +const BASE = '/api/v0/workspace'; + +export interface WorkspaceInfo { + path: string; + name: string; +} + +export interface FileEntry { + name: string; + path: string; + is_dir: boolean; + size: number; + modified: number; + is_python: boolean; + has_burr_app: boolean; +} + +export interface FileContent { + path: string; + content: string; + language: string; + size: number; +} + +export interface ProcessInfo { + pid: number; + script_path: string; + started_at: number; + status: string; + exit_code: number | null; +} + +export interface ProcessOutputEvent { + type: 'stdout' | 'stderr' | 'exit'; + data: string; +} + +export interface WorkspaceLinkInfo { + project_id: string; + workspace_path: string | null; +} + +export interface BuilderProjectSummary { + id: string; + name: string; + updated_at: number; +} + +export interface BuilderProjectFull { + id: string; + name: string; + graph_json: string; + updated_at: number; +} + +async function apiPost(path: string, body: unknown): Promise { + const res = await fetch(`${BASE}${path}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + if (!res.ok) { + const detail = await res.json().catch(() => ({ detail: res.statusText })); + throw new Error(detail.detail || res.statusText); + } + return res.json(); +} + +async function apiGet(path: string, params?: Record): Promise { + const url = new URL(`${BASE}${path}`, window.location.origin); + if (params) { + Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v)); + } + const res = await fetch(url.toString()); + if (!res.ok) { + const detail = await res.json().catch(() => ({ detail: res.statusText })); + throw new Error(detail.detail || res.statusText); + } + return res.json(); +} + +export const workspaceApi = { + getWorkspaceLink(projectId: string): Promise { + return apiGet('/link', { project_id: projectId }); + }, + + setWorkspaceLink(projectId: string, workspacePath: string): Promise { + return apiPost('/link', { project_id: projectId, workspace_path: workspacePath }); + }, + + removeWorkspaceLink(projectId: string): Promise { + const url = new URL(`${BASE}/link`, window.location.origin); + url.searchParams.set('project_id', projectId); + return fetch(url.toString(), { method: 'DELETE' }).then(() => undefined); + }, + + openWorkspace(path: string): Promise { + return apiPost('/open', { path }); + }, + + getFileTree(workspace: string, relativePath = ''): Promise { + return apiGet('/tree', { workspace, relative_path: relativePath }); + }, + + getFileContent(workspace: string, relativePath: string): Promise { + return apiGet('/file', { workspace, relative_path: relativePath }); + }, + + runScript(workspace: string, script: string): Promise { + return apiPost('/run', { workspace, script }); + }, + + stopProcess(pid: number): Promise { + return apiPost(`/run/${pid}/stop`, {}); + }, + + getProcesses(workspace: string): Promise { + return apiGet('/processes', { workspace }); + }, + + scanBurrApps(workspace: string): Promise { + return apiGet('/scan', { workspace }); + }, + + listBuilderProjects(): Promise { + return apiGet('/builder/projects'); + }, + + saveBuilderProject(name: string, graphJson: string): Promise { + return apiPost('/builder/projects', { name, graph_json: graphJson }); + }, + + getBuilderProject(id: string): Promise { + return apiGet(`/builder/projects/${id}`); + }, + + deleteBuilderProject(id: string): Promise { + return fetch(`${BASE}/builder/projects/${id}`, { method: 'DELETE' }).then(() => undefined); + }, + + streamProcessOutput( + pid: number, + onEvent: (event: ProcessOutputEvent) => void, + onError: (err: Error) => void + ): AbortController { + const ctrl = new AbortController(); + fetchEventSource(`${BASE}/run/${pid}/output`, { + signal: ctrl.signal, + onmessage(ev) { + try { + const parsed: ProcessOutputEvent = JSON.parse(ev.data); + onEvent(parsed); + } catch { + // ignore parse errors + } + }, + onerror(err) { + onError(err instanceof Error ? err : new Error(String(err))); + }, + openWhenHidden: true + }); + return ctrl; + } +}; diff --git a/telemetry/ui/src/components/common/chip.tsx b/telemetry/ui/src/components/common/chip.tsx index 488cd0779..8e39e9e44 100644 --- a/telemetry/ui/src/components/common/chip.tsx +++ b/telemetry/ui/src/components/common/chip.tsx @@ -83,8 +83,7 @@ export const Chip = (props: { className={`relative grid select-none items-center whitespace-nowrap rounded-lg p-1 px-3 font-sans text-xs font-semibold text-white ${bgColor} ${clickable ? 'cursor-pointer hover:underline' : ''} ${props.className ? props.className : ''}`} style={{ backgroundColor: bgStyle }} - onClick={props.onClick} - > + onClick={props.onClick}> {props.label} ); diff --git a/telemetry/ui/src/components/common/drawer.tsx b/telemetry/ui/src/components/common/drawer.tsx index e60b5bec0..e9ce7f58d 100644 --- a/telemetry/ui/src/components/common/drawer.tsx +++ b/telemetry/ui/src/components/common/drawer.tsx @@ -45,15 +45,13 @@ export const Drawer = (props: {
+ className="pointer-events-auto relative w-screen max-w-5xl transform transition duration-500 ease-in-out data-[closed]:translate-x-full sm:duration-700">
); @@ -124,8 +121,7 @@ export function PaginationGap({ className={clsx( className, 'w-[2.25rem] select-none text-center text-sm/6 font-semibold text-zinc-950 dark:text-white' - )} - > + )}> {children} ); @@ -150,8 +146,7 @@ export const Paginator = (props: { key={page} href={getPageURL(page)} current={page === currentPage} - className={classNames} - > + className={classNames}> {page} ); diff --git a/telemetry/ui/src/components/common/switch.tsx b/telemetry/ui/src/components/common/switch.tsx index 61f803d6d..fbee23f62 100644 --- a/telemetry/ui/src/components/common/switch.tsx +++ b/telemetry/ui/src/components/common/switch.tsx @@ -214,8 +214,7 @@ export function Switch({ // Color specific styles colors[color] )} - {...props} - > + {...props}>