diff --git a/api/entrypoints/routers.py b/api/entrypoints/routers.py index 151c405fe..2f2c5f6dc 100644 --- a/api/entrypoints/routers.py +++ b/api/entrypoints/routers.py @@ -103,6 +103,9 @@ from oss.src.apis.fastapi.evaluations.router import EvaluationsRouter from oss.src.apis.fastapi.evaluations.router import SimpleEvaluationsRouter +from oss.src.core.ai_services.service import AIServicesService +from oss.src.apis.fastapi.ai_services.router import AIServicesRouter + from oss.src.routers import ( admin_router, @@ -431,6 +434,13 @@ async def lifespan(*args, **kwargs): annotations_service=annotations_service, ) +# AI SERVICES ------------------------------------------------------------------ + +ai_services_service = AIServicesService.from_env() +ai_services = AIServicesRouter( + ai_services_service=ai_services_service, +) + # MOUNTING ROUTERS TO APP ROUTES ----------------------------------------------- app.include_router( @@ -532,6 +542,12 @@ async def lifespan(*args, **kwargs): tags=["Workflows"], ) +app.include_router( + router=ai_services.router, + prefix="/preview/ai/services", + tags=["AI Services"], +) + app.include_router( router=evaluators.router, prefix="/preview/evaluators", diff --git a/api/oss/src/apis/fastapi/ai_services/__init__.py b/api/oss/src/apis/fastapi/ai_services/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/api/oss/src/apis/fastapi/ai_services/__init__.py @@ -0,0 +1 @@ + diff --git a/api/oss/src/apis/fastapi/ai_services/models.py b/api/oss/src/apis/fastapi/ai_services/models.py new file mode 100644 index 000000000..1d03c6fa6 --- /dev/null +++ b/api/oss/src/apis/fastapi/ai_services/models.py @@ -0,0 +1,17 @@ +from oss.src.core.ai_services.dtos import ( + AIServicesStatus, + ToolCallRequest, + ToolCallResponse, +) + + +class AIServicesStatusResponse(AIServicesStatus): + pass + + +class ToolCallRequestModel(ToolCallRequest): + pass + + +class ToolCallResponseModel(ToolCallResponse): + pass diff --git a/api/oss/src/apis/fastapi/ai_services/router.py b/api/oss/src/apis/fastapi/ai_services/router.py new file mode 100644 index 000000000..5a363468d --- /dev/null +++ b/api/oss/src/apis/fastapi/ai_services/router.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +from fastapi import APIRouter, HTTPException, Request, status +from pydantic import ValidationError + +from oss.src.utils.common import is_ee +from oss.src.utils.exceptions import intercept_exceptions +from oss.src.utils.throttling import check_throttle + +from oss.src.core.ai_services.dtos import TOOL_REFINE_PROMPT +from oss.src.core.ai_services.service import AIServicesService +from oss.src.apis.fastapi.ai_services.models import ( + AIServicesStatusResponse, + ToolCallRequestModel, + ToolCallResponseModel, +) + + +if is_ee(): + from ee.src.models.shared_models import Permission + from ee.src.utils.permissions import check_action_access, FORBIDDEN_EXCEPTION + + +_RATE_LIMIT_BURST = 10 +_RATE_LIMIT_PER_MIN = 30 + + +class AIServicesRouter: + def __init__( + self, + *, + ai_services_service: AIServicesService, + ): + self.service = ai_services_service + self.router = APIRouter() + + self.router.add_api_route( + "/status", + self.get_status, + methods=["GET"], + operation_id="ai_services_status", + status_code=status.HTTP_200_OK, + response_model=AIServicesStatusResponse, + response_model_exclude_none=True, + ) + + self.router.add_api_route( + "/tools/call", + self.call_tool, + methods=["POST"], + operation_id="ai_services_tools_call", + status_code=status.HTTP_200_OK, + response_model=ToolCallResponseModel, + response_model_exclude_none=True, + ) + + @intercept_exceptions() + async def get_status(self, request: Request) -> AIServicesStatusResponse: + allow_tools = True + + if is_ee(): + allow_tools = await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_WORKFLOWS, # type: ignore + ) + + return self.service.status(allow_tools=allow_tools) + + @intercept_exceptions() + async def call_tool( + self, + request: Request, + *, + tool_call: ToolCallRequestModel, + ) -> ToolCallResponseModel: + if not self.service.enabled: + raise HTTPException(status_code=503, detail="AI services are disabled") + + if is_ee(): + if not await check_action_access( # type: ignore + user_uid=request.state.user_id, + project_id=request.state.project_id, + permission=Permission.EDIT_WORKFLOWS, # type: ignore + ): + raise FORBIDDEN_EXCEPTION # type: ignore + + # Router-level rate limit + key = { + "ep": "ai_services", + "tool": tool_call.name, + "org": getattr(request.state, "organization_id", None), + "user": getattr(request.state, "user_id", None), + } + result = await check_throttle( + key, + max_capacity=_RATE_LIMIT_BURST, + refill_rate=_RATE_LIMIT_PER_MIN, + ) + if not result.allow: + retry_after = ( + int(result.retry_after_seconds) if result.retry_after_seconds else 1 + ) + raise HTTPException( + status_code=429, + detail="Rate limit exceeded", + headers={"Retry-After": str(retry_after)}, + ) + + # Tool routing + strict request validation + if tool_call.name != TOOL_REFINE_PROMPT: + raise HTTPException(status_code=400, detail="Unknown tool") + + try: + return await self.service.call_tool( + name=tool_call.name, + arguments=tool_call.arguments, + ) + except ValidationError as e: + raise HTTPException(status_code=400, detail=e.errors()) from e + except ValueError as e: + # Unknown tool or invalid argument shape + raise HTTPException(status_code=400, detail=str(e)) from e diff --git a/api/oss/src/core/ai_services/__init__.py b/api/oss/src/core/ai_services/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/api/oss/src/core/ai_services/__init__.py @@ -0,0 +1 @@ + diff --git a/api/oss/src/core/ai_services/client.py b/api/oss/src/core/ai_services/client.py new file mode 100644 index 000000000..fdfc2a658 --- /dev/null +++ b/api/oss/src/core/ai_services/client.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +from typing import Any, Dict, Optional, Tuple + +import httpx + +from oss.src.utils.logging import get_module_logger + + +log = get_module_logger(__name__) + + +class AgentaAIServicesClient: + """Thin HTTP client to call Agenta Cloud workflow invocation APIs.""" + + def __init__( + self, + *, + api_url: str, + api_key: str, + timeout_s: float = 20.0, + ): + self.api_url = api_url.rstrip("/") + self.api_key = api_key + self.timeout_s = timeout_s + + async def invoke_deployed_prompt( + self, + *, + application_slug: str, + environment_slug: str, + inputs: Dict[str, Any], + ) -> Tuple[Optional[Any], Optional[str]]: + """Invoke a deployed prompt by app/environment slug. + + NOTE: This targets the cloud completion runner endpoint. + + Returns: (raw_response, trace_id) + """ + + url = f"{self.api_url}/services/completion/run" + + payload: Dict[str, Any] = { + "inputs": inputs, + "environment": environment_slug, + "app": application_slug, + } + + headers = { + "Authorization": f"ApiKey {self.api_key}", + "Content-Type": "application/json", + "Accept": "application/json", + } + + try: + async with httpx.AsyncClient(timeout=self.timeout_s) as client: + res = await client.post(url, json=payload, headers=headers) + + # Non-2xx responses still carry useful error payloads + data: Any = None + try: + data = res.json() + except Exception: + data = None + + if res.status_code < 200 or res.status_code >= 300: + log.warning( + "[ai-services] Upstream invoke failed", + status_code=res.status_code, + url=url, + ) + # Surface as tool execution error (caller maps to isError) + return { + "_error": True, + "status_code": res.status_code, + "detail": data, + }, None + + trace_id = None + if isinstance(data, dict): + trace_id = data.get("trace_id") or data.get("traceId") + + return data, trace_id + + return None, None + + except httpx.TimeoutException: + log.warning("[ai-services] Upstream invoke timed out", url=url) + return { + "_error": True, + "status_code": 504, + "detail": "Upstream timeout", + }, None + + except Exception as e: # pylint: disable=broad-exception-caught + log.warning("[ai-services] Upstream invoke error", url=url, error=str(e)) + return { + "_error": True, + "status_code": 502, + "detail": "Upstream error", + }, None diff --git a/api/oss/src/core/ai_services/dtos.py b/api/oss/src/core/ai_services/dtos.py new file mode 100644 index 000000000..b41c07795 --- /dev/null +++ b/api/oss/src/core/ai_services/dtos.py @@ -0,0 +1,49 @@ +from typing import Any, Dict, List, Literal, Optional + + +from pydantic import BaseModel, ConfigDict, Field + + +TOOL_REFINE_PROMPT = "tools.agenta.api.refine_prompt" + + +class ToolDefinition(BaseModel): + name: str + title: str + description: str + inputSchema: Dict[str, Any] + outputSchema: Dict[str, Any] + + +class AIServicesStatus(BaseModel): + enabled: bool + tools: List[ToolDefinition] = Field(default_factory=list) + + +class ToolCallRequest(BaseModel): + name: str + arguments: Dict[str, Any] = Field(default_factory=dict) + + +class ToolCallTextContent(BaseModel): + type: Literal["text"] = "text" + text: str + + +class ToolCallMeta(BaseModel): + trace_id: Optional[str] = None + + +class ToolCallResponse(BaseModel): + content: List[ToolCallTextContent] = Field(default_factory=list) + structuredContent: Optional[Dict[str, Any]] = None + isError: bool = False + meta: Optional[ToolCallMeta] = None + + +class RefinePromptArguments(BaseModel): + prompt_template_json: str = Field(min_length=1, max_length=100_000) + guidelines: Optional[str] = Field(default=None, max_length=10_000) + context: Optional[str] = Field(default=None, max_length=10_000) + + model_config = ConfigDict(extra="forbid") diff --git a/api/oss/src/core/ai_services/service.py b/api/oss/src/core/ai_services/service.py new file mode 100644 index 000000000..e70f87abe --- /dev/null +++ b/api/oss/src/core/ai_services/service.py @@ -0,0 +1,278 @@ +from __future__ import annotations + +from typing import Any, Dict, Optional + +import json + +from oss.src.utils.logging import get_module_logger + +from oss.src.core.ai_services.client import AgentaAIServicesClient +from oss.src.core.ai_services.dtos import ( + AIServicesStatus, + RefinePromptArguments, + ToolCallMeta, + ToolCallResponse, + ToolCallTextContent, + ToolDefinition, + TOOL_REFINE_PROMPT, +) +from oss.src.utils.env import AIServicesConfig, env + + +log = get_module_logger(__name__) + + +_REFINE_PROMPT_INPUT_SCHEMA: Dict[str, Any] = { + "type": "object", + "additionalProperties": False, + "properties": { + "prompt_template_json": { + "type": "string", + "description": "The full prompt template as a stringified JSON object.", + }, + "guidelines": {"type": "string"}, + "context": {"type": "string"}, + }, + "required": ["prompt_template_json"], +} + +_REFINE_PROMPT_OUTPUT_SCHEMA: Dict[str, Any] = { + "type": "object", + "additionalProperties": False, + "properties": { + "messages": { + "type": "array", + "description": "The refined messages array (same count and roles as input).", + "items": { + "type": "object", + "additionalProperties": False, + "properties": { + "role": { + "type": "string", + "enum": ["system", "developer", "user", "assistant"], + "description": "Message role. Must match the original.", + }, + "content": { + "type": "string", + "description": "Refined message content.", + }, + }, + "required": ["role", "content"], + }, + }, + "summary": { + "type": "string", + "description": "A short summary describing what was changed in this refinement.", + }, + }, + "required": ["messages", "summary"], +} + + +def _extract_structured_output(outputs: Any) -> Optional[Dict[str, Any]]: + """Extract the structured output from the cloud completion runner response. + + The cloud returns ``{"data": "", "trace_id": "..."}`` + where ``data`` is a JSON string containing ``messages`` (refined array) + and ``summary`` (short description of changes). + + Returns the parsed dict or None on failure. + """ + + if not isinstance(outputs, dict): + return None + + data_val = outputs.get("data") + if not isinstance(data_val, str) or not data_val.strip(): + return None + + try: + parsed = json.loads(data_val) + except Exception: # pylint: disable=broad-exception-caught + return None + + if not isinstance(parsed, dict): + return None + + messages = parsed.get("messages") + if not isinstance(messages, list) or len(messages) == 0: + return None + + return parsed + + +def _validate_messages(messages: Any) -> bool: + """Validate that messages is a well-formed list of {role, content} dicts.""" + + if not isinstance(messages, list) or len(messages) == 0: + return False + + for msg in messages: + if not isinstance(msg, dict): + return False + if "role" not in msg or "content" not in msg: + return False + + return True + + +class AIServicesService: + @classmethod + def from_env(cls) -> "AIServicesService": + config = env.ai_services + + if not config.enabled: + return cls(config=config, client=None) + + api_url = config.normalized_api_url + api_key = config.api_key + + # enabled implies these exist, but keep this defensive. + if not api_url or not api_key: + return cls(config=config, client=None) + + client = AgentaAIServicesClient( + api_url=api_url, + api_key=api_key, + ) + + return cls(config=config, client=client) + + def __init__( + self, + *, + config: AIServicesConfig, + client: Optional[AgentaAIServicesClient] = None, + ): + self.config = config + self.client = client + + @property + def enabled(self) -> bool: + return self.config.enabled + + def status(self, *, allow_tools: bool) -> AIServicesStatus: + if not self.enabled: + return AIServicesStatus(enabled=False, tools=[]) + + if not allow_tools: + return AIServicesStatus(enabled=True, tools=[]) + + return AIServicesStatus( + enabled=True, + tools=[ + ToolDefinition( + name=TOOL_REFINE_PROMPT, + title="Refine Prompt", + description=( + "Refine a prompt template. Input is a stringified JSON prompt " + "template; output is a refined version with the same structure." + ), + inputSchema=_REFINE_PROMPT_INPUT_SCHEMA, + outputSchema=_REFINE_PROMPT_OUTPUT_SCHEMA, + ) + ], + ) + + async def call_tool( + self, *, name: str, arguments: Dict[str, Any] + ) -> ToolCallResponse: + if name != TOOL_REFINE_PROMPT: + raise ValueError(f"Unknown tool: {name}") + + args = RefinePromptArguments.model_validate(arguments) + + return await self.refine_prompt( + prompt_template_json=args.prompt_template_json, + guidelines=args.guidelines, + context=args.context, + ) + + async def refine_prompt( + self, + *, + prompt_template_json: str, + guidelines: Optional[str] = None, + context: Optional[str] = None, + ) -> ToolCallResponse: + if not self.enabled: + return ToolCallResponse( + isError=True, + content=[ToolCallTextContent(text="AI services are not configured.")], + ) + + if not self.client: + return ToolCallResponse( + isError=True, + content=[ + ToolCallTextContent(text="AI services client is not available.") + ], + ) + + outputs, trace_id = await self.client.invoke_deployed_prompt( + application_slug=str(self.config.refine_prompt_app), + environment_slug=str(self.config.environment), + inputs={ + "__ag_prompt_template_json": prompt_template_json, + "__ag_guidelines": guidelines or "", + "__ag_context": context or "", + }, + ) + + # Upstream failure is encoded into outputs as a dict with _error flag + if isinstance(outputs, dict) and outputs.get("_error"): + detail = outputs.get("detail") + msg = "AI refine failed." + if isinstance(detail, str) and detail.strip(): + msg = detail + elif isinstance(detail, dict): + msg = str(detail.get("detail") or detail.get("message") or msg) + + return ToolCallResponse( + isError=True, + content=[ToolCallTextContent(text=msg)], + meta=ToolCallMeta(trace_id=trace_id), + ) + + # Extract and validate structured output + structured = _extract_structured_output(outputs) + if not structured: + log.warning( + "[ai-services] Failed to extract structured output", + outputs_type=type(outputs).__name__, + ) + return ToolCallResponse( + isError=True, + content=[ + ToolCallTextContent( + text="AI refine returned an unexpected response format." + ) + ], + meta=ToolCallMeta(trace_id=trace_id), + ) + + messages = structured["messages"] + + # Validate the messages array + if not _validate_messages(messages): + log.warning( + "[ai-services] messages array is not valid", + ) + return ToolCallResponse( + isError=True, + content=[ + ToolCallTextContent( + text="AI refine returned an invalid messages array." + ) + ], + meta=ToolCallMeta(trace_id=trace_id), + ) + + summary = structured.get("summary", "Prompt refined successfully.") + + return ToolCallResponse( + isError=False, + content=[ToolCallTextContent(text=summary)], + structuredContent=structured, + meta=ToolCallMeta(trace_id=trace_id), + ) diff --git a/api/oss/src/utils/env.py b/api/oss/src/utils/env.py index 7298f5a00..386d9b4a2 100644 --- a/api/oss/src/utils/env.py +++ b/api/oss/src/utils/env.py @@ -571,6 +571,43 @@ class AlembicConfig(BaseModel): model_config = ConfigDict(extra="ignore") +class AIServicesConfig(BaseModel): + """AI services configuration. + + Feature is enabled only when all required env vars are present. + """ + + api_key: str | None = os.getenv("AGENTA_AI_SERVICES_API_KEY") + api_url: str | None = os.getenv("AGENTA_AI_SERVICES_API_URL") + environment: str | None = os.getenv("AGENTA_AI_SERVICES_ENVIRONMENT") + refine_prompt_app: str | None = os.getenv("AGENTA_AI_SERVICES_REFINE_PROMPT_APP") + + model_config = ConfigDict(extra="ignore") + + @property + def enabled(self) -> bool: + required = [ + self.api_key, + self.api_url, + self.environment, + self.refine_prompt_app, + ] + return all(isinstance(v, str) and v.strip() for v in required) + + @property + def normalized_api_url(self) -> str | None: + if not self.api_url or not self.api_url.strip(): + return None + url = self.api_url.rstrip("/") + # Some environments use a base API url like https://host/api, while + # AI services endpoints live at https://host/services/... + if url.endswith("/api"): + url = url[: -len("/api")] + if url.endswith("/services"): + url = url[: -len("/services")] + return url + + class EnvironSettings(BaseModel): """ Main environment settings container with nested Pydantic models. @@ -611,6 +648,7 @@ class EnvironSettings(BaseModel): otlp: OTLPConfig = OTLPConfig() redis: RedisConfig = RedisConfig() agenta: AgentaConfig = AgentaConfig() + ai_services: AIServicesConfig = AIServicesConfig() postgres: PostgresConfig = PostgresConfig() alembic: AlembicConfig = AlembicConfig() diff --git a/docs/design/ai-actions/README.md b/docs/design/ai-actions/README.md new file mode 100644 index 000000000..a35262c12 --- /dev/null +++ b/docs/design/ai-actions/README.md @@ -0,0 +1,18 @@ +# AI Services (Tool Calls) + +This workspace specifies a REST API for "tool call"-shaped AI services. + +Chapter 1 implements a single tool: + +- Refine Prompt (`tools.agenta.api.refine_prompt`) + +The backend calls a deployed prompt in an internal Agenta org (Bedrock credentials live in the Agenta app config, not backend env vars). + +## Files + +- `docs/design/ai-actions/context.md` - Problem statement, goals/non-goals, constraints +- `docs/design/ai-actions/spec.md` - API + tool contract (request/response schemas) +- `docs/design/ai-actions/plan.md` - Implementation plan (Chapter 1) +- `docs/design/ai-actions/research.md` - Codebase findings and relevant reference points +- `docs/design/ai-actions/status.md` - Living decisions, open questions, and progress log +- `docs/design/ai-actions/frontend-spec.md` - Frontend implementation specification (UI, components, state, flows) diff --git a/docs/design/ai-actions/cloud-prompt-config.json b/docs/design/ai-actions/cloud-prompt-config.json new file mode 100644 index 000000000..7fabe2ebe --- /dev/null +++ b/docs/design/ai-actions/cloud-prompt-config.json @@ -0,0 +1,63 @@ +{ + "prompt": { + "messages": [ + { + "role": "system", + "content": "You are an expert prompt engineer. You receive a prompt template as a stringified JSON object and you refine ONLY the message content fields.\n\nRules:\n- The input `prompt_template_json` is a stringified JSON object representing a prompt template.\n- Parse it mentally. It contains a `messages` array (each with `role` and `content`), and possibly other fields like `template_format`, `input_keys`, etc.\n- Refine ONLY the `content` of each message for clarity, specificity, and effectiveness.\n- Do NOT change: message count, message order, roles, variable tokens (e.g. {{var}}), template_format, input_keys, or any other field.\n- Do NOT rename or remove variables.\n\nOutput rules:\n- `refined_prompt` MUST be the full refined prompt template as a STRINGIFIED JSON object (same structure as input, only content fields changed).\n- `messages` MUST be the refined messages array (same count, same roles, same order as input) for verification." + }, + { + "role": "user", + "content": "Refine this prompt template.\n\n## Prompt template (stringified JSON)\n{{prompt_template_json}}\n\n## Guidelines (optional)\n{{guidelines}}\n\n## Context (optional)\n{{context}}" + } + ], + "template_format": "curly", + "llm_config": { + "model": "gpt-4o-mini", + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "RefinedPromptTemplate", + "description": "The refined prompt template. refined_prompt is the full prompt template JSON stringified. messages is a copy of just the messages array for quick verification.", + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "refined_prompt": { + "type": "string", + "description": "The full refined prompt template as a stringified JSON object. Must contain the same structure as the input (messages array with same count, roles, order). Only message content fields should be refined." + }, + "messages": { + "type": "array", + "description": "The refined messages array (same count and roles as input). This is a copy of the messages from refined_prompt for verification.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "role": { + "type": "string", + "enum": ["system", "developer", "user", "assistant"], + "description": "Message role. Must match the original." + }, + "content": { + "type": "string", + "description": "Refined message content." + } + }, + "required": ["role", "content"] + } + } + }, + "required": ["refined_prompt", "messages"] + }, + "strict": true + } + }, + "tools": [] + }, + "input_keys": [ + "prompt_template_json", + "guidelines", + "context" + ] + } +} diff --git a/docs/design/ai-actions/context.md b/docs/design/ai-actions/context.md new file mode 100644 index 000000000..e910b23c1 --- /dev/null +++ b/docs/design/ai-actions/context.md @@ -0,0 +1,45 @@ +# Context + +## Problem + +We want to ship a simple, non-agentic AI enhancement for prompt authoring. + +Chapter 1 ships a single AI "tool": + +1. Refine Prompt (`tools.agenta.api.refine_prompt`) + +They must be: + +- non-agentic +- single-step +- no orchestration + +This must also serve as a foundation for future tools (first-party and third-party). + +## Architecture Constraint + +The backend does **not** call the LLM provider directly. + +Instead, the backend calls a **deployed prompt** in an **internal Agenta org** via the Agenta API / completion service. + +- Bedrock credentials live in the Agenta app config (internal org) +- Backend needs only an Agenta API key to call the deployed prompt + +## Goals + +- Add backend endpoints in the **new FastAPI stack** (`api/oss/src/apis/fastapi/*` + `api/oss/src/core/*`). +- Use a stable tool-call contract: `name` + `arguments` in, `content` + `structuredContent` out. +- Feature-flag by env vars; when not configured, UI hides/disables the tool. +- Non-agentic and single-step; no streaming. + +## Non-goals (Chapter 1) + +- Multi-step agents and orchestration +- User consent UX (modal/toggles) +- Per-user billing / metering +- User-provided model credentials + +## Resolved Questions + +- **Cloud invocation contract**: `POST {API_URL}/services/completion/run` with `{"inputs": {...}, "environment": "...", "app": "..."}` and `Authorization: ApiKey ...` header. Response is `{"data": "", "trace_id": "..."}`. +- **Rate limit policy**: Composite key per user + per org, 10 burst / 30 per minute refill. diff --git a/docs/design/ai-actions/frontend-spec.md b/docs/design/ai-actions/frontend-spec.md new file mode 100644 index 000000000..3ef3a7c7b --- /dev/null +++ b/docs/design/ai-actions/frontend-spec.md @@ -0,0 +1,709 @@ +# Frontend Specification: Refine AI Feature + +This document specifies the frontend implementation for the Refine Prompt AI feature. + +## Table of Contents + +1. [Overview](#overview) +2. [Design Reference](#design-reference) +3. [Interaction Flows](#interaction-flows) +4. [Component Architecture](#component-architecture) +5. [State Management](#state-management) +6. [API Integration](#api-integration) +7. [Implementation Plan](#implementation-plan) + +--- + +## Overview + +The Refine AI feature allows users to improve their prompts by providing instructions (guidelines) to an AI. Users provide refinement instructions, and the AI returns a refined prompt along with an explanation of changes. + +### Key Insight: Not a Chat + +**Important**: This is NOT a chat conversation under the hood. The API takes: +- `prompt_template_json` - The prompt to refine +- `guidelines` - User's refinement instructions +- `context` - Optional additional context + +And returns: +- `messages` - The refined messages array +- `summary` - Short description of what changed + +The UI presents this as a chat-like interface for UX, but the state should model it as **refinement iterations**, not chat messages. + +### Key Features + +1. **Magic Wand Icon** - Added to the Prompt collapse header to trigger the Refine modal +2. **Two-Panel Modal** - Left panel for refinement instructions, right panel for refined prompt preview +3. **Diff View Toggle** - Compare original vs refined prompt as JSON diff +4. **Iterative Refinement** - Each subsequent refinement uses the previous result as starting point +5. **Editable Preview** - Users can edit the refined prompt before applying + +--- + +## Design Reference + +### 1. Prompt Collapse Header (Magic Wand Icon) + +**Location**: `PlaygroundVariantConfigPromptCollapseHeader.tsx` + +**Design Elements**: +- Magic wand icon (MagicWand from @phosphor-icons/react) +- Positioned to the left of the LLM model selector +- Icon-only button, small size (24x24px) +- On hover: subtle background highlight +- On click: Opens the Refine Prompt modal + +**Layout**: +``` +[Caret] [Prompt Label] [Magic Wand] [LLM Model Selector v] +``` + +### 2. Refine Prompt Modal + +**Dimensions**: ~900px wide, 90vh max height (two-column layout) + +#### Left Panel: Instructions + +**Header**: +- Title: "Instructions" +- Subtitle: "Chat with an LLM agent to improve your prompt" + +**Content Area** (using Ant Design X Bubble): +- Uses `@ant-design/x` `Bubble` and `Bubble.List` components +- User guidelines: `placement="end"` (right-aligned), gray background +- AI explanations: `placement="start"` (left-aligned), no background +- Font: IBM Plex Mono, 12px (monospace for consistency with code) +- Auto-scrolls to latest message + +**Footer**: +- Input field with placeholder "Enter refinement instructions..." +- Send button (PaperPlaneTilt icon, primary filled) +- Input spans full width minus button + +#### Right Panel: Refined Prompt Preview + +**Header**: +- Title: "Refine prompt" +- Diff toggle: Switch component with "Diff" label +- Close button (X icon) + +**Content Area** (Two Modes): + +**Normal Mode (Diff OFF)**: +- Displays editable message components (same as Playground message editor) +- Each message shows: Role dropdown (System/User/Assistant), message content +- Messages are editable before applying +- Uses `MessageEditor` component pattern + +**Diff Mode (Diff ON)**: +- Shows JSON diff between original and refined prompt +- Uses `DiffView` component +- Green = additions, Red = deletions +- Foldable unchanged sections + +**Footer**: +- "Cancel" button (text style) +- "Use refined prompt" button (primary filled) + +--- + +## Interaction Flows + +### Flow 1: Initial Open (Empty State) + +1. User clicks magic wand icon in prompt header +2. Modal opens with: + - **Left Panel**: Empty area with input field ready + - **Right Panel**: Empty state message "Submit instructions to see the refined prompt" +3. Diff toggle is disabled (nothing to compare yet) + +### Flow 2: First Refinement + +1. User types instructions in the input field +2. User clicks send (or presses Enter) +3. **Left Panel**: + - User's guidelines appear as a bubble (right-aligned) + - Loading indicator (Bubble with `loading={true}`) +4. **Right Panel**: + - Shows loading skeleton +5. API call to `/preview/ai/services/tools/call` with: + - `prompt_template_json`: Original prompt + - `guidelines`: User's instructions +6. On success: + - **Left Panel**: AI explanation appears as bubble (left-aligned) + - **Right Panel**: Refined prompt messages displayed in editable form + - Diff toggle becomes enabled +7. On error: + - Error toast notification + - Error explanation appears as bubble + - User can retry + +### Flow 3: Subsequent Refinement (Iterative) + +1. User types additional instructions +2. **Previous refined prompt** is used as input (not original) +3. Same loading/success/error flow as above +4. History of guidelines/explanations is preserved in bubble list + +### Flow 4: Diff View Toggle + +1. User toggles "Diff" switch ON +2. Right panel switches to `DiffView` component +3. Shows JSON diff: **original prompt** vs **current refined prompt** +4. Toggle OFF returns to editable message view + +### Flow 5: Edit Before Apply + +1. In normal mode (Diff OFF), user can edit any message content +2. Changes are local to the modal (not saved until "Use refined prompt") +3. Diff view reflects any manual edits made + +### Flow 6: Apply Refined Prompt + +1. User clicks "Use refined prompt" +2. Modal closes +3. Original prompt in Playground is replaced with refined version +4. Variant is marked as dirty (has unsaved changes) + +### Flow 7: Cancel + +1. User clicks "Cancel" or X button +2. Modal closes +3. No changes are applied +4. State is discarded + +### Error States + +| Scenario | UI Response | +|----------|-------------| +| AI Service disabled | Magic wand icon hidden or disabled | +| Rate limited (429) | Toast: "Too many requests. Please wait." | +| Auth error (401/403) | Toast: "Permission denied" | +| Service error (500) | Toast: "Service unavailable. Try again." | +| Invalid response | Bubble: "Failed to refine prompt. Please try different instructions." | + +### Loading States + +| Component | Loading State | +|-----------|---------------| +| Bubble list | Bubble with `loading={true}` | +| Right panel | Skeleton of message cards | +| Send button | Disabled with spinner | + +--- + +## Component Architecture + +### File Structure + +``` +web/oss/src/components/Playground/Components/ +├── PlaygroundVariantConfigPrompt/ +│ └── assets/ +│ └── PlaygroundVariantConfigPromptCollapseHeader.tsx # Add magic wand icon +│ +└── Modals/ + └── RefinePromptModal/ + ├── index.tsx # Modal wrapper with EnhancedModal + ├── types.ts # TypeScript interfaces + ├── store/ + │ └── refinePromptStore.ts # Jotai atoms for modal state + ├── hooks/ + │ └── useRefinePrompt.ts # API call hook + └── assets/ + ├── RefinePromptModalContent.tsx # Two-panel layout + ├── InstructionsPanel/ + │ ├── index.tsx # Left panel container + │ └── InstructionsInput.tsx # Input + send button + └── PreviewPanel/ + ├── index.tsx # Right panel container + ├── PreviewHeader.tsx # Title + diff toggle + close + ├── EmptyState.tsx # Initial empty state + ├── LoadingState.tsx # Skeleton loading + └── RefinedPromptView.tsx # Editable messages or diff +``` + +### Component Reuse Strategy + +| Need | Reuse From | Notes | +|------|------------|-------| +| Modal container | `EnhancedModal` from `@agenta/ui` | Two-column layout like CommitModal | +| Bubble list | `Bubble`, `Bubble.List` from `@ant-design/x` | For guidelines/explanations display | +| Message editor | `MessageEditor` from `ChatCommon` | For editable refined messages | +| Diff view | `DiffView` from `Editor` | For JSON diff display | +| Input + Send | `Sender` from `@ant-design/x` or custom | For refinement input | +| Switch toggle | Ant Design `Switch` | For diff toggle | +| Loading skeleton | Ant Design `Skeleton` | For loading states | + +### Key Component Props + +```typescript +// RefinePromptModal +interface RefinePromptModalProps { + open: boolean + onClose: () => void + variantId: string + promptId: string +} + +// InstructionsPanel +interface InstructionsPanelProps { + iterations: RefinementIteration[] + onSubmitGuidelines: (guidelines: string) => void + isLoading: boolean +} + +// RefinementIteration (NOT chat messages!) +interface RefinementIteration { + id: string + guidelines: string // User's refinement instructions + explanation: string // AI's explanation of changes + timestamp: number +} + +// PreviewPanel +interface PreviewPanelProps { + originalPrompt: PromptTemplate + refinedPrompt: PromptTemplate | null + showDiff: boolean + onToggleDiff: (show: boolean) => void + onClose: () => void + onUpdateMessage: (index: number, content: string) => void + isLoading: boolean +} + +// PromptTemplate (matches backend contract) +interface PromptTemplate { + messages: Array<{ + role: string + content: string + }> + template_format?: string + input_keys?: string[] + llm_config?: Record +} +``` + +--- + +## State Management + +### Data Model: Refinement Iterations (NOT Chat Messages) + +The key insight is that this is **not** a conversation. Each "turn" is a refinement request: +- User provides `guidelines` +- AI returns `messages` (refined) + `summary` (what changed) + +```typescript +// A single refinement iteration +interface RefinementIteration { + id: string + guidelines: string // What the user asked for + explanation: string // Summary of what the AI changed (from backend response) + timestamp: number +} +``` + +### Modal State Atoms + +```typescript +// store/refinePromptStore.ts + +import { atom } from 'jotai' +import { atomFamily } from 'jotai/utils' + +// Modal open state (scoped per prompt) +export const refineModalOpenAtomFamily = atomFamily((promptId: string) => + atom(false) +) + +// Refinement iterations history (guidelines + explanations) +export const refineIterationsAtomFamily = atomFamily((promptId: string) => + atom([]) +) + +// Current working prompt (starts as original, updated after each refinement) +// This is what gets sent to the next refinement API call +export const workingPromptAtomFamily = atomFamily((promptId: string) => + atom(null) +) + +// Loading state +export const refineLoadingAtomFamily = atomFamily((promptId: string) => + atom(false) +) + +// Diff view toggle +export const refineDiffViewAtomFamily = atomFamily((promptId: string) => + atom(false) +) + +// Original prompt snapshot (captured when modal opens, never changes) +// Used for diff comparison +export const originalPromptSnapshotAtomFamily = atomFamily((promptId: string) => + atom(null) +) + +// Pending guidelines (what user is currently typing) +export const pendingGuidelinesAtomFamily = atomFamily((promptId: string) => + atom(null) +) +``` + +### State Flow + +``` +Modal Opens + ↓ +Capture original prompt → originalPromptSnapshotAtomFamily + → workingPromptAtomFamily (copy) + ↓ +User submits guidelines → pendingGuidelinesAtomFamily (for display) + → refineLoadingAtomFamily = true + ↓ +API call with: + - prompt_template_json: workingPromptAtomFamily + - guidelines: user input + ↓ +API returns → refineIterationsAtomFamily (add iteration with guidelines + explanation) + → workingPromptAtomFamily (update to refined prompt) + → refineLoadingAtomFamily = false + → pendingGuidelinesAtomFamily = null + ↓ +User edits in preview → workingPromptAtomFamily (update) + ↓ +User applies → Copy workingPromptAtomFamily to playground state +``` + +### Bubble List Data Transformation + +Transform `RefinementIteration[]` to Bubble.List items: + +```typescript +const bubbleItems = useMemo(() => { + const items: BubbleItem[] = [] + + for (const iteration of iterations) { + // User's guidelines (right-aligned) + items.push({ + key: `${iteration.id}-guidelines`, + placement: 'end', + content: iteration.guidelines, + }) + + // AI's explanation (left-aligned) + items.push({ + key: `${iteration.id}-explanation`, + placement: 'start', + content: iteration.explanation, + }) + } + + // Add pending guidelines if loading + if (pendingGuidelines && isLoading) { + items.push({ + key: 'pending-guidelines', + placement: 'end', + content: pendingGuidelines, + }) + items.push({ + key: 'loading', + placement: 'start', + loading: true, + }) + } + + return items +}, [iterations, pendingGuidelines, isLoading]) +``` + +### Integration with Playground State + +When user clicks "Use refined prompt": + +```typescript +// 1. Get working prompt from modal state (includes all refinements + edits) +const workingPrompt = get(workingPromptAtomFamily(promptId)) + +// 2. Convert to playground format (EnhancedObjectConfig) +const enhancedMessages = workingPrompt.messages.map((msg, idx) => ({ + __id: `msg-${idx}`, + role: { value: msg.role }, + content: { value: msg.content }, +})) + +// 3. Update via molecule reducer +set(legacyAppRevisionMolecule.reducers.mutateEnhancedPrompts, revisionId, (draft) => { + const promptIndex = draft.findIndex(p => p.__id === promptId) + if (promptIndex !== -1) { + draft[promptIndex].messages.value = enhancedMessages + } +}) + +// 4. Clean up modal state +set(workingPromptAtomFamily(promptId), null) +set(refineIterationsAtomFamily(promptId), []) +set(originalPromptSnapshotAtomFamily(promptId), null) +set(refineModalOpenAtomFamily(promptId), false) +``` + +--- + +## API Integration + +### API Client + +```typescript +// web/oss/src/services/aiServices/api.ts + +import axios from '@/oss/lib/api/assets/axiosConfig' + +export interface RefinePromptResponse { + content: Array<{ type: 'text'; text: string }> + structuredContent?: { + messages: Array<{ role: string; content: string }> + summary?: string // Short description of what changed + } + isError: boolean + meta?: { trace_id?: string } +} + +export interface AIServicesStatus { + enabled: boolean + tools: Array<{ + name: string + title: string + description: string + }> +} + +export const aiServicesApi = { + async getStatus(): Promise { + const { data } = await axios.get('/preview/ai/services/status') + return data + }, + + async refinePrompt( + promptTemplate: PromptTemplate, + guidelines: string, + context?: string + ): Promise { + const { data } = await axios.post('/preview/ai/services/tools/call', { + name: 'tools.agenta.api.refine_prompt', + arguments: { + prompt_template_json: JSON.stringify(promptTemplate), + guidelines, + context, + }, + }) + return data + }, +} +``` + +### Hook for Refinement + +```typescript +// hooks/useRefinePrompt.ts + +import { useCallback } from 'react' +import { useAtom, useAtomValue, useSetAtom } from 'jotai' +import { aiServicesApi } from '@/oss/services/aiServices/api' +import { + refineIterationsAtomFamily, + workingPromptAtomFamily, + refineLoadingAtomFamily, + pendingGuidelinesAtomFamily, +} from '../store/refinePromptStore' + +export function useRefinePrompt(promptId: string) { + const [iterations, setIterations] = useAtom(refineIterationsAtomFamily(promptId)) + const [workingPrompt, setWorkingPrompt] = useAtom(workingPromptAtomFamily(promptId)) + const [isLoading, setIsLoading] = useAtom(refineLoadingAtomFamily(promptId)) + const setPendingGuidelines = useSetAtom(pendingGuidelinesAtomFamily(promptId)) + + const refine = useCallback(async (guidelines: string) => { + if (!workingPrompt || !guidelines.trim()) return + + // Show pending state + setPendingGuidelines(guidelines) + setIsLoading(true) + + try { + const response = await aiServicesApi.refinePrompt(workingPrompt, guidelines) + + if (response.isError) { + throw new Error(response.content[0]?.text || 'Refinement failed') + } + + // Parse refined prompt from messages array + const refined = { + messages: response.structuredContent!.messages, + template_format: workingPrompt.template_format, + } + + // Add iteration to history + const iteration: RefinementIteration = { + id: `iter-${Date.now()}`, + guidelines, + explanation: response.structuredContent?.summary || + 'Prompt refined based on your instructions.', + timestamp: Date.now(), + } + setIterations(prev => [...prev, iteration]) + + // Update working prompt for next iteration + setWorkingPrompt(refined) + + return refined + } catch (error) { + // Add error iteration + const errorIteration: RefinementIteration = { + id: `iter-error-${Date.now()}`, + guidelines, + explanation: `Error: ${error.message}. Please try different instructions.`, + timestamp: Date.now(), + } + setIterations(prev => [...prev, errorIteration]) + throw error + } finally { + setIsLoading(false) + setPendingGuidelines(null) + } + }, [workingPrompt, setIterations, setWorkingPrompt, setIsLoading, setPendingGuidelines]) + + return { iterations, workingPrompt, isLoading, refine, setWorkingPrompt } +} +``` + +--- + +## Implementation Plan + +### Phase 1: Foundation (API + State + Dependencies) + +**Tasks:** +1. Install `@ant-design/x` package if not already present +2. Create `web/oss/src/services/aiServices/api.ts` - API client +3. Create `RefinePromptModal/store/refinePromptStore.ts` - State atoms +4. Create `RefinePromptModal/types.ts` - TypeScript interfaces + +### Phase 2: Modal Shell + +**Files to create:** +- `RefinePromptModal/index.tsx` +- `RefinePromptModal/assets/RefinePromptModalContent.tsx` + +**Tasks:** +1. Create modal wrapper using `EnhancedModal` +2. Implement two-column layout (following CommitModal pattern) +3. Add right panel header with diff toggle and close button +4. Add footer with Cancel and "Use refined prompt" buttons + +### Phase 3: Instructions Panel (Left) + +**Files to create:** +- `InstructionsPanel/index.tsx` +- `InstructionsPanel/InstructionsInput.tsx` + +**Tasks:** +1. Create panel with header (title + subtitle) +2. Integrate `Bubble.List` from `@ant-design/x` for iteration display +3. Transform `RefinementIteration[]` to bubble items +4. Create input component with send button +5. Handle loading state with `loading` prop on Bubble + +### Phase 4: Preview Panel (Right) + +**Files to create:** +- `PreviewPanel/index.tsx` +- `PreviewPanel/PreviewHeader.tsx` +- `PreviewPanel/EmptyState.tsx` +- `PreviewPanel/LoadingState.tsx` +- `PreviewPanel/RefinedPromptView.tsx` + +**Tasks:** +1. Create preview header with diff toggle +2. Create empty state for initial view +3. Create loading skeleton +4. Create refined prompt view using `MessageEditor` components +5. Integrate `DiffView` for diff mode + +### Phase 5: Integration + +**Files to modify:** +- `PlaygroundVariantConfigPromptCollapseHeader.tsx` + +**Tasks:** +1. Add magic wand icon button to collapse header +2. Check AI services status to show/hide icon +3. Wire up modal open state +4. Implement "Use refined prompt" action to update playground state + +### Phase 6: Refinement Hook + +**Files to create:** +- `RefinePromptModal/hooks/useRefinePrompt.ts` + +**Tasks:** +1. Create hook for API calls with loading/error handling +2. Implement iterative refinement (use workingPrompt, not original) +3. Manage iterations history + +### Phase 7: Polish & Edge Cases + +**Tasks:** +1. Add keyboard shortcuts (Enter to send, Escape to close) +2. Handle rate limiting gracefully +3. Add error boundaries +4. Test with various prompt structures +5. Ensure proper cleanup on modal close + +--- + +## Backend Contract + +The backend response returns `messages` (refined array) and `summary` (change description) in `structuredContent`: + +```json +{ + "structuredContent": { + "messages": [ + {"role": "system", "content": "Refined system content."}, + {"role": "user", "content": "Refined user content."} + ], + "summary": "Added explicit extraction format and improved role clarity." + } +} +``` + +The `summary` is displayed as the AI's response bubble in the left panel. + +--- + +## Notes + +1. **Feature Flag**: Magic wand icon should only appear when AI services are enabled (check via `GET /preview/ai/services/status`) + +2. **Permissions**: The refine feature requires `EDIT_WORKFLOWS` permission (EE only) + +3. **Rate Limiting**: Backend enforces 10 burst / 30 per minute. Frontend should show appropriate feedback. + +4. **Iterative Context**: Each refinement uses the **workingPrompt** (not original) to build on previous improvements. + +5. **Not a Chat**: The backend API is NOT a chat. It takes `guidelines` and returns `messages` + `summary`. The state models this as `RefinementIteration[]`, not chat messages. + +--- + +## Dependencies + +| Package | Purpose | +|---------|---------| +| `@ant-design/x` | Bubble, Bubble.List, Sender components | +| `@phosphor-icons/react` | MagicWand icon | +| `jotai` | State management (already in project) | + +Sources: +- [Bubble - Ant Design X](https://x.ant.design/components/bubble/) +- [Ant Design X Overview](https://x.ant.design/components/overview/) diff --git a/docs/design/ai-actions/openai-schema-refine-prompt-option-b.json b/docs/design/ai-actions/openai-schema-refine-prompt-option-b.json new file mode 100644 index 000000000..3e1eff8d4 --- /dev/null +++ b/docs/design/ai-actions/openai-schema-refine-prompt-option-b.json @@ -0,0 +1,36 @@ +{ + "name": "RefinedPromptTemplate", + "description": "The refined prompt template. refined_prompt is the full prompt template JSON stringified. messages is a copy of just the messages array for quick verification.", + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "refined_prompt": { + "type": "string", + "description": "The full refined prompt template as a stringified JSON object. Must contain the same structure as the input (messages array with same count, roles, order). Only message content fields should be refined." + }, + "messages": { + "type": "array", + "description": "The refined messages array (same count and roles as input). This is a copy of the messages from refined_prompt for verification.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "role": { + "type": "string", + "enum": ["system", "developer", "user", "assistant"], + "description": "Message role. Must match the original." + }, + "content": { + "type": "string", + "description": "Refined message content." + } + }, + "required": ["role", "content"] + } + } + }, + "required": ["refined_prompt", "messages"] + }, + "strict": true +} diff --git a/docs/design/ai-actions/plan.md b/docs/design/ai-actions/plan.md new file mode 100644 index 000000000..035883b32 --- /dev/null +++ b/docs/design/ai-actions/plan.md @@ -0,0 +1,95 @@ +# Plan (Chapter 1) + +Chapter 1 implements `tools.agenta.api.refine_prompt` behind a REST "tool call" API. + +The contract is defined in `docs/design/ai-actions/spec.md`. + +## Phase 0: Ops Setup (out of repo) + +- Create internal org in cloud: `agenta-internal`. +- Create deployed prompt app: `ai-refine-prompt`. +- Configure Bedrock model + credentials inside the app. + +## Phase 1: Backend (new stack) + +### 1.1 Configuration + +- Add env vars (presence enables AI services): + - `AGENTA_AI_SERVICES_API_KEY` + - `AGENTA_AI_SERVICES_API_URL` + - `AGENTA_AI_SERVICES_ENVIRONMENT` + - `AGENTA_AI_SERVICES_REFINE_PROMPT_APP` + +### 1.2 Core + +- Add `api/oss/src/core/ai_services/*`: + - DTOs for `ToolCallRequest`, `ToolCallResponse`, and tool-specific args/results + - `AgentaAIServicesClientInterface` (invoke deployed prompt via Agenta API) + - `AIServicesService.refine_prompt(...)` + +### 1.3 API + +- Add `api/oss/src/apis/fastapi/ai_services/*`: + - `GET /preview/ai/services/status` + - `POST /preview/ai/services/tools/call` + +### 1.4 Dependency Wiring + +- Wire concrete implementations in `api/entrypoints/routers.py`. + +### 1.5 Access control + rate limiting + +- Reuse existing auth/session middleware (request.state user/org/project). +- In EE, require `EDIT_WORKFLOWS` for the refine prompt tool. +- Add a router-level rate limit (HTTP 429). + +## Phase 2: Frontend + +Full specification in `docs/design/ai-actions/frontend-spec.md`. + +### 2.1 Foundation (API + State) + +- Create `web/oss/src/services/aiServices/api.ts` - API client with `getStatus()` and `refinePrompt()` +- Create `RefinePromptModal/store/refinePromptStore.ts` - Jotai atoms for modal state +- Create `RefinePromptModal/types.ts` - TypeScript interfaces + +### 2.2 Modal Shell + +- Create `RefinePromptModal/index.tsx` - Modal wrapper using `EnhancedModal` +- Create `RefinePromptModalContent.tsx` - Two-column layout (like CommitModal) +- Header: title, diff toggle, close button +- Footer: Cancel and "Use refined prompt" buttons + +### 2.3 Instructions Panel (Left Column) + +- `InstructionsPanel/index.tsx` - Container with header + chat + input +- `ChatHistory.tsx` - Scrollable message list +- `ChatMessage.tsx` - User messages (right-aligned, gray bg) vs AI messages (left-aligned) +- `ChatInput.tsx` - Text input + send button (PaperPlaneTilt icon) + +### 2.4 Preview Panel (Right Column) + +- `PreviewPanel/index.tsx` - Container for preview content +- `PreviewHeader.tsx` - "Refine prompt" title + Diff toggle switch +- `EmptyState.tsx` - Initial state before first refinement +- `LoadingState.tsx` - Skeleton loading during API call +- `RefinedPromptView.tsx` - Editable messages using `MessageEditor` or `DiffView` + +### 2.5 Integration + +- Modify `PlaygroundVariantConfigPromptCollapseHeader.tsx` - Add magic wand icon button +- Check AI services status to conditionally show/hide icon +- Wire modal open state +- Implement "Use refined prompt" action to update playground via molecule reducers + +### 2.6 Refinement Hook + +- Create `useRefinePrompt.ts` - Hook for API calls + iterative refinement logic +- Manage chat history and loading states +- Handle errors gracefully + +## Phase 3: Hardening + +- Strict input validation (max prompt length; context length). +- Strict output validation (ensure `messages` array is well-formed; otherwise `isError=true`). +- Add logging for tool usage; propagate optional `trace_id`. diff --git a/docs/design/ai-actions/research.md b/docs/design/ai-actions/research.md new file mode 100644 index 000000000..5e96ac79e --- /dev/null +++ b/docs/design/ai-actions/research.md @@ -0,0 +1,31 @@ +# Research Notes (Repo) + +## New backend stack conventions (confirmed) + +- New endpoints should live under `api/oss/src/apis/fastapi/*` and core logic under `api/oss/src/core/*`. +- Composition root wiring happens in `api/entrypoints/routers.py`. + +## Existing invocation / completion primitives + +### Workflows service (FastAPI) + +- `api/oss/src/apis/fastapi/workflows/router.py` implements `invoke_workflow(...)` and calls `WorkflowsService.invoke_workflow(...)`. +- `api/oss/src/core/workflows/service.py` wraps invocation by signing a `Secret ` and calling the SDK runner (`_invoke_workflow`). + +This path is used for invoking workflows within the current instance; it is not a direct "call cloud.agenta.ai with ApiKey" client. + +### Legacy LLM app invocation service + +- `api/oss/src/services/llm_apps_service.py` posts to `{uri}/test` using `Authorization: Secret `. +- This is part of the legacy stack and should not be used as a template for new AI action endpoints. + +## Frontend API patterns + +- API client wrapper: `web/oss/src/services/api.ts`. +- Existing domain APIs live under `web/oss/src/services/*/api/*` (e.g. `promptVersioning`, `testsets`). + +## Likely UI integration surfaces (Chapter 1) + +- Prompt editing lives in Playground components under `web/oss/src/components/Playground/*`. + +No existing `ai/services` endpoints or UI hooks were found in OSS. diff --git a/docs/design/ai-actions/spec.md b/docs/design/ai-actions/spec.md new file mode 100644 index 000000000..f85958a44 --- /dev/null +++ b/docs/design/ai-actions/spec.md @@ -0,0 +1,220 @@ +# Spec: AI Services Tool Calls (Chapter 1) + +## Scope + +Chapter 1 ships one tool via a REST API: + +- `tools.agenta.api.refine_prompt` + +The REST contract is intentionally shaped like MCP `tools/call` (name + arguments; content + structuredContent) so we can add more tools later without changing the interface. + +This is **not** MCP JSON-RPC. There is no `initialize`, no session headers, and no JSON-RPC envelope. + +## Tool Naming + +Tool names are globally namespaced: + +- `tools.agenta.api.` for first-party tools +- Future third-party examples: `tools.composio.gmail.send_email` + +## Feature Flag + +AI services are enabled when all required env vars are present. + +If disabled: + +- `GET /preview/ai/services/status` returns `enabled: false` +- `POST /preview/ai/services/tools/call` returns HTTP 503 + +## HTTP API + +Base path: `/preview/ai/services` + +### GET /preview/ai/services/status + +Used by the frontend to decide whether to show AI actions. + +Response (200): + +```json +{ + "enabled": true, + "tools": [ + { + "name": "tools.agenta.api.refine_prompt", + "title": "Refine Prompt", + "description": "Refine a prompt template. Input is a stringified JSON prompt template; output is a refined version with the same structure.", + "inputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "prompt_template_json": {"type": "string", "description": "The full prompt template as a stringified JSON object."}, + "guidelines": {"type": "string"}, + "context": {"type": "string"} + }, + "required": ["prompt_template_json"] + }, + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "messages": { + "type": "array", + "description": "The refined messages array (same count and roles as input).", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "role": {"type": "string", "enum": ["system", "developer", "user", "assistant"]}, + "content": {"type": "string"} + }, + "required": ["role", "content"] + } + }, + "summary": {"type": "string", "description": "A short summary describing what was changed in this refinement."} + }, + "required": ["messages", "summary"] + } + } + ] +} +``` + +When disabled: + +```json +{ + "enabled": false, + "tools": [] +} +``` + +### POST /preview/ai/services/tools/call + +Request: + +```json +{ + "name": "tools.agenta.api.refine_prompt", + "arguments": { + "prompt_template_json": "{\"messages\":[{\"role\":\"system\",\"content\":\"You are a helpful assistant.\"},{\"role\":\"user\",\"content\":\"Extract entities from: {{input_text}}\"}],\"template_format\":\"curly\",\"input_keys\":[\"input_text\"]}", + "guidelines": "Optional guidelines for refinement.", + "context": "Optional context." + } +} +``` + +Success response (200): + +```json +{ + "content": [ + { + "type": "text", + "text": "Added explicit extraction format and improved role clarity." + } + ], + "structuredContent": { + "messages": [ + {"role": "system", "content": "Refined system content."}, + {"role": "user", "content": "Refined user content with {{input_text}}."} + ], + "summary": "Added explicit extraction format and improved role clarity." + }, + "isError": false, + "meta": { + "trace_id": "optional" + } +} +``` + +Notes: +- `content[0].text` contains the summary of what was changed (same as `structuredContent.summary`). +- `structuredContent.messages` is the refined messages array (same count, same roles, same order as input). +- `structuredContent.summary` is a short human-readable description of the refinement changes, displayed in the chat UI. +- The backend validates that `messages` is a well-formed array of `{role, content}` objects before returning success. + +Tool execution error response (200): + +```json +{ + "content": [ + { + "type": "text", + "text": "Actionable error message." + } + ], + "isError": true, + "meta": { + "trace_id": "optional" + } +} +``` + +Error status codes: + +- 400: invalid request shape (unknown tool name, invalid args types) +- 401: unauthenticated (handled by existing auth middleware) +- 403: forbidden (EE permission checks) +- 429: rate limited +- 503: AI services disabled / not configured + +## Env Vars (Backend) + +Required (Chapter 1): + +```bash +AGENTA_AI_SERVICES_API_KEY=ag-xxx +AGENTA_AI_SERVICES_API_URL=https://eu.cloud.agenta.ai +AGENTA_AI_SERVICES_ENVIRONMENT=production + +AGENTA_AI_SERVICES_REFINE_PROMPT_APP=ai-refine +``` + +Notes: + +- Bedrock credentials are configured in the internal Agenta app. +- Backend uses only `AGENTA_AI_SERVICES_API_KEY` to call the deployed prompt. + +## Backend Call (Cloud Invocation) + +The backend implements a thin client that calls the Agenta API using `ApiKey` auth. + +Target endpoint is the Agenta services completion runner: + +- `POST {AGENTA_AI_SERVICES_API_URL}/services/completion/run` + +Invocation payload: + +```json +{ + "inputs": { + "prompt_template_json": "", + "guidelines": "...", + "context": "..." + }, + "environment": "production", + "app": "ai-refine" +} +``` + +Expected response: + +```json +{ + "version": "3.0", + "data": "{\"messages\":[{\"role\":\"system\",\"content\":\"...\"},{\"role\":\"user\",\"content\":\"...\"}],\"summary\":\"Added explicit extraction format.\"}", + "content_type": "text/plain", + "trace_id": "...", + "tree_id": "...", + "span_id": "..." +} +``` + +The `data` field is a JSON string (structured output from the model). The backend parses it, validates `messages` is a well-formed array, and maps it into the `ToolCallResponse`. + +## Permission + Rate Limits + +- Auth context comes from existing middleware (request.state). +- In EE, the refine tool should require the closest existing permission for prompt editing (recommendation: `EDIT_WORKFLOWS`). +- Add a simple rate limit at the router boundary (return HTTP 429). diff --git a/docs/design/ai-actions/status.md b/docs/design/ai-actions/status.md new file mode 100644 index 000000000..634b8da20 --- /dev/null +++ b/docs/design/ai-actions/status.md @@ -0,0 +1,79 @@ +# Status + +## Current State + +### Completed + +- **Research** — backend layering conventions, existing workflow invocation path, frontend integration surface for prompt authoring (Playground). +- **Design** — spec, plan, and context docs finalized. +- **Phase 1: Backend** — fully implemented and wired: + - Configuration (`AIServicesConfig` in `api/oss/src/utils/env.py`) with four env vars; feature enabled only when all are present. + - Core layer (`api/oss/src/core/ai_services/`): + - DTOs (`dtos.py`): `ToolCallRequest`, `ToolCallResponse`, `RefinePromptArguments`, `ToolDefinition`, etc. + - HTTP client (`client.py`): `AgentaAIServicesClient.invoke_deployed_prompt()` via httpx. + - Service (`service.py`): `AIServicesService` with `status()`, `call_tool()`, `refine_prompt()` plus output extraction and validation. + - API layer (`api/oss/src/apis/fastapi/ai_services/`): + - `GET /preview/ai/services/status` — returns enabled flag + available tools. + - `POST /preview/ai/services/tools/call` — executes a tool call. + - Wiring in `api/entrypoints/routers.py`. + - EE permission check (`EDIT_WORKFLOWS`) on both endpoints. + - Rate limiting via `check_throttle` (burst 10 / refill 30 per min). + - Input validation: max lengths on `RefinePromptArguments` fields. + - Output validation: `_validate_messages` ensures well-formed messages array with role+content. +- **Phase 2: Frontend** — core implementation complete: + - **Phase 2.1 Foundation** — completed: + - Installed `@ant-design/x` for Bubble components + - Created `web/oss/src/services/aiServices/api.ts` - API client + - Created `RefinePromptModal/types.ts` - TypeScript interfaces + - Created `RefinePromptModal/store/refinePromptStore.ts` - Jotai atoms with atomFamily + - **Phase 2.2 Modal Shell** — completed: + - Created `RefinePromptModal/index.tsx` - Main modal component with EnhancedModal + - Created `RefinePromptModal/assets/RefinePromptModalContent.tsx` - Two-column layout + - **Phase 2.3 Instructions Panel** — completed: + - Created `RefinePromptModal/assets/InstructionsPanel.tsx` - Left panel with @ant-design/x Bubble component + - Chat-like display for guidelines and AI explanations + - Input area with auto-resize text area + - **Phase 2.4 Preview Panel** — completed: + - Created `RefinePromptModal/assets/PreviewPanel.tsx` - Right panel with editable prompt + - MessageEditor integration for editing refined messages + - DiffView toggle for comparing original vs refined prompt + - **Phase 2.5 Integration** — completed: + - Added magic wand icon (lucide-react Wand2) to PlaygroundVariantConfigPromptCollapseHeader + - Tooltip on hover: "Refine prompt with AI" + - Modal opens on click with current prompt context + - **Phase 2.6 Refinement Hook** — completed: + - Created `RefinePromptModal/hooks/useRefinePrompt.ts` + - Extracts prompt template from enhanced prompts + - Calls refine API and updates working prompt state + - Manages iterations history and loading state + +### In Progress + +- **Phase 2.7 Polish & Edge Cases**: + - [x] Implement "Use refined prompt" button to apply changes to playground state + - [x] Check AI services status before showing magic wand icon + - [x] Cmd+Enter keyboard shortcut for submitting refinement + - [x] Replaced Input.TextArea with @ant-design/x Sender + Prompts components + - [x] Simplified output schema: removed redundant `refined_prompt` field, added `summary` field + - [ ] Add error handling and toast notifications + - [ ] Handle empty prompt edge cases + +## Decisions + +- REST-only implementation with an MCP-shaped tool-call contract (see `docs/design/ai-actions/spec.md`). +- Tool names are namespaced (Chapter 1 uses `tools.agenta.api.refine_prompt`). +- Env var prefix is `AGENTA_AI_SERVICES_*`. +- Rate limit is per-user + per-org composite key, 10 burst / 30 per minute. +- State modeled as `RefinementIteration[]` (not chat messages) - each iteration has `guidelines` (user input) and `explanation` (AI response summary). +- Working prompt is iteratively refined - each refinement uses the previous result as starting point. +- Output schema uses `messages` + `summary` (no redundant `refined_prompt` string). The `summary` is a short human-readable description of what changed, displayed in the chat bubbles. + +## Open Questions + +- Confirm the exact cloud invocation contract for calling the deployed prompt by slug + environment. + +## Next + +- Complete Phase 2.7: Polish & edge cases. +- Phase 3 hardening: structured logging for tool usage, trace_id propagation. +- Consider adding AI services status check to conditionally show the magic wand icon. diff --git a/web/oss/package.json b/web/oss/package.json index 725c19e24..42a9ae344 100644 --- a/web/oss/package.json +++ b/web/oss/package.json @@ -30,6 +30,7 @@ "@ant-design/colors": "^7.2.1", "@ant-design/cssinjs": "^2.0.1", "@ant-design/icons": "^6.1.0", + "@ant-design/x": "^2.2.2", "@cloudflare/stream-react": "^1.9.3", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", diff --git a/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/assets/InstructionsPanel.tsx b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/assets/InstructionsPanel.tsx new file mode 100644 index 000000000..9567e98fd --- /dev/null +++ b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/assets/InstructionsPanel.tsx @@ -0,0 +1,206 @@ +/** + * InstructionsPanel - Left panel for entering refinement guidelines + * + * Uses @ant-design/x Bubble for conversation display, Sender for input, + * and Prompts for predefined quick-actions. + */ + +import {type MutableRefObject, useCallback, useEffect, useMemo, useRef, useState} from "react" + +import {BulbOutlined} from "@ant-design/icons" +import {Bubble, Prompts, Sender} from "@ant-design/x" +import {Spin} from "antd" +import {useAtomValue} from "jotai" + +import {useRefinePrompt} from "../hooks/useRefinePrompt" +import { + pendingGuidelinesAtomFamily, + refineIterationsAtomFamily, + refineLoadingAtomFamily, +} from "../store/refinePromptStore" + +interface InstructionsPanelProps { + variantId: string + promptId: string + submitRef: MutableRefObject<(() => void) | null> +} + +const PREDEFINED_PROMPTS = [ + { + key: "optimize", + icon: , + label: "Optimize the prompt using best practices", + }, +] + +const InstructionsPanel: React.FC = ({variantId, promptId, submitRef}) => { + const [inputValue, setInputValue] = useState("") + const scrollRef = useRef(null) + + const iterations = useAtomValue(refineIterationsAtomFamily(promptId)) + const isLoading = useAtomValue(refineLoadingAtomFamily(promptId)) + const pendingGuidelines = useAtomValue(pendingGuidelinesAtomFamily(promptId)) + + const {refine} = useRefinePrompt({variantId, promptId}) + + const scrollToBottom = useCallback(() => { + setTimeout(() => { + scrollRef.current?.scrollTo({ + top: scrollRef.current.scrollHeight, + behavior: "smooth", + }) + }, 100) + }, []) + + const handleSubmit = useCallback( + async (message: string) => { + const guidelines = message.trim() + if (!guidelines || isLoading) return + + setInputValue("") + await refine(guidelines) + scrollToBottom() + }, + [isLoading, refine, scrollToBottom], + ) + + // For Cmd+Enter: submit whatever is currently in the input + const handleCmdEnterSubmit = useCallback(() => { + const guidelines = inputValue.trim() + if (!guidelines || isLoading) return + handleSubmit(guidelines) + }, [inputValue, isLoading, handleSubmit]) + + // Expose submit to parent for Cmd+Enter handling + useEffect(() => { + submitRef.current = handleCmdEnterSubmit + return () => { + submitRef.current = null + } + }, [handleCmdEnterSubmit, submitRef]) + + const handlePromptClick = useCallback( + (info: {data: {key: string; label?: React.ReactNode}}) => { + if (isLoading) return + const text = typeof info.data.label === "string" ? info.data.label : "" + if (text) { + handleSubmit(text) + } + }, + [isLoading, handleSubmit], + ) + + // Memoize bubble items to avoid re-creation on every render + const bubbleItems = useMemo(() => { + const items: { + key: string + content: string + placement: "start" | "end" + variant?: "filled" | "outlined" | "shadow" | "borderless" + }[] = [] + + for (const iteration of iterations) { + items.push({ + key: `${iteration.id}-guidelines`, + content: iteration.guidelines, + placement: "end", + variant: "filled", + }) + items.push({ + key: `${iteration.id}-explanation`, + content: iteration.explanation, + placement: "start", + variant: "outlined", + }) + } + + // Add pending guidelines while waiting for response + if (pendingGuidelines) { + items.push({ + key: "pending-guidelines", + content: pendingGuidelines, + placement: "end", + variant: "filled", + }) + } + + return items + }, [iterations, pendingGuidelines]) + + const hasContent = bubbleItems.length > 0 || isLoading + + return ( +
+ {/* Conversation area */} +
+ {!hasContent ? ( +
+
+

+ Describe how you want to refine your prompt +

+

Or pick a suggestion below

+
+
+ ) : ( + + )} + + {/* Loading indicator */} + {isLoading ? ( +
+ + Refining prompt… +
+ ) : null} +
+ + {/* Predefined prompts + Sender input */} +
+ {/* Predefined prompts */} +
+ +
+ + {/* Sender input */} + +
+
+ ) +} + +export default InstructionsPanel diff --git a/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/assets/PreviewPanel.tsx b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/assets/PreviewPanel.tsx new file mode 100644 index 000000000..082009e9c --- /dev/null +++ b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/assets/PreviewPanel.tsx @@ -0,0 +1,113 @@ +/** + * PreviewPanel - Right panel for previewing the refined prompt + * + * Shows: + * - Editable message list (same as original prompt format) + * - Or diff view when diff toggle is on (controlled by parent) + * + * The `promptVersion` prop is used as part of editor keys to force + * remount when a new AI refinement result arrives (SharedEditor uses + * initialValue and does not update reactively). + */ + +import {useCallback, useMemo} from "react" + +import {Typography} from "antd" +import {useAtom, useAtomValue} from "jotai" + +import DiffView from "@/oss/components/Editor/DiffView" +import MessageEditor from "@/oss/components/Playground/Components/ChatCommon/MessageEditor" + +import { + originalPromptSnapshotAtomFamily, + refineDiffViewAtomFamily, + workingPromptAtomFamily, +} from "../store/refinePromptStore" + +interface PreviewPanelProps { + variantId: string + promptId: string + promptVersion: number +} + +const PreviewPanel: React.FC = ({promptId, promptVersion}) => { + const showDiff = useAtomValue(refineDiffViewAtomFamily(promptId)) + const [workingPrompt, setWorkingPrompt] = useAtom(workingPromptAtomFamily(promptId)) + const originalPrompt = useAtomValue(originalPromptSnapshotAtomFamily(promptId)) + + const handleMessageChange = useCallback( + (index: number, field: "role" | "content", value: string) => { + setWorkingPrompt((prev) => { + if (!prev) return prev + + const newMessages = [...prev.messages] + newMessages[index] = { + ...newMessages[index], + [field]: value, + } + + return { + ...prev, + messages: newMessages, + } + }) + }, + [setWorkingPrompt], + ) + + // Only diff messages — template_format and other fields are not refined + const originalJson = useMemo(() => { + if (!originalPrompt) return "[]" + return JSON.stringify(originalPrompt.messages, null, 2) + }, [originalPrompt]) + + const workingJson = useMemo(() => { + if (!workingPrompt) return "[]" + return JSON.stringify(workingPrompt.messages, null, 2) + }, [workingPrompt]) + + if (!workingPrompt) { + return ( +
+ + No refined prompt yet + +
+ ) + } + + return ( +
+ {showDiff ? ( + + ) : ( +
+ {workingPrompt.messages.map((message, index) => ( + handleMessageChange(index, "role", v)} + onChangeText={(v) => handleMessageChange(index, "content", v)} + placeholder="Enter message content…" + /> + ))} +
+ )} +
+ ) +} + +export default PreviewPanel diff --git a/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/assets/RefinePromptModalContent.tsx b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/assets/RefinePromptModalContent.tsx new file mode 100644 index 000000000..594ebdaaa --- /dev/null +++ b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/assets/RefinePromptModalContent.tsx @@ -0,0 +1,175 @@ +/** + * RefinePromptModalContent - Two-panel layout for prompt refinement + * + * Left panel: Instructions input with conversation-like bubble display + * Right panel: Refined prompt preview with diff toggle + * + * Layout follows the Figma design: + * - Left: "Instructions" header + bubbles + input at bottom + * - Right: "Refine prompt" header with diff toggle + close + content + footer + */ + +import {useCallback, useEffect, useRef} from "react" + +import {generateId} from "@agenta/shared/utils" +import {CloseOutlined} from "@ant-design/icons" +import {Button, Switch, Typography} from "antd" +import {useAtom, useAtomValue, useSetAtom} from "jotai" + +import {moleculeBackedPromptsAtomFamily} from "@/oss/state/newPlayground/legacyEntityBridge" + +import { + refineDiffViewAtomFamily, + refineIterationsAtomFamily, + workingPromptVersionAtomFamily, + workingPromptAtomFamily, +} from "../store/refinePromptStore" + +import InstructionsPanel from "./InstructionsPanel" +import PreviewPanel from "./PreviewPanel" + +interface RefinePromptModalContentProps { + variantId: string + promptId: string + onClose: () => void +} + +const RefinePromptModalContent: React.FC = ({ + variantId, + promptId, + onClose, +}) => { + const iterations = useAtomValue(refineIterationsAtomFamily(promptId)) + const workingPrompt = useAtomValue(workingPromptAtomFamily(promptId)) + const promptVersion = useAtomValue(workingPromptVersionAtomFamily(promptId)) + const [showDiff, setShowDiff] = useAtom(refineDiffViewAtomFamily(promptId)) + const setPrompts = useSetAtom(moleculeBackedPromptsAtomFamily(variantId)) + + const hasRefinedPrompt = workingPrompt !== null && iterations.length > 0 + + // Ref to allow InstructionsPanel to trigger refine via Cmd+Enter + const submitRef = useRef<(() => void) | null>(null) + + // Cmd+Enter handler scoped to modal — only swallow the event when submit proceeds + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && submitRef.current) { + submitRef.current() + e.preventDefault() + e.stopPropagation() + } + } + document.addEventListener("keydown", handler, {capture: true}) + return () => document.removeEventListener("keydown", handler, {capture: true}) + }, []) + + const handleUseRefinedPrompt = useCallback(() => { + if (!workingPrompt) return + + setPrompts((draft: any[]) => { + for (const prompt of draft) { + if (prompt?.__id !== promptId && prompt?.__name !== promptId) continue + + prompt.messages.value = workingPrompt.messages.map((msg) => ({ + __id: generateId(), + role: {value: msg.role}, + content: {value: msg.content}, + })) + } + }) + + onClose() + }, [workingPrompt, promptId, setPrompts, onClose]) + + return ( +
+ {/* Left Panel - Instructions */} +
+ {/* Left Header */} +
+ + Instructions + + + Chat with an LLM agent to improve your prompt + +
+ + {/* Left Content + Input */} +
+ +
+
+ + {/* Right Panel - Preview */} +
+ {/* Right Header */} +
+ + Refine prompt + +
+
+ + Diff + + +
+
+
+ + {/* Right Content */} +
+ {hasRefinedPrompt ? ( + + ) : ( +
+ + Submit instructions to see the refined prompt + +
+ )} +
+ + {/* Right Footer */} +
+ + +
+
+
+ ) +} + +export default RefinePromptModalContent diff --git a/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/hooks/useRefinePrompt.ts b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/hooks/useRefinePrompt.ts new file mode 100644 index 000000000..f59661096 --- /dev/null +++ b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/hooks/useRefinePrompt.ts @@ -0,0 +1,227 @@ +/** + * useRefinePrompt - Hook for refining prompts using AI + * + * This hook handles: + * 1. Capturing the original prompt on first refinement + * 2. Calling the refine prompt API + * 3. Updating iterations history and working prompt state + * + * Uses refs for values only needed inside callbacks (workingPrompt, + * originalSnapshot) to keep the `refine` callback stable and avoid + * stale closures without re-creating it on every state change. + */ + +import {useCallback, useRef} from "react" + +import {generateId} from "@agenta/shared/utils" +import {useAtomValue, useSetAtom} from "jotai" + +import {aiServicesApi} from "@/oss/services/aiServices/api" +import {moleculeBackedPromptsAtomFamily} from "@/oss/state/newPlayground/legacyEntityBridge" + +import { + originalPromptSnapshotAtomFamily, + pendingGuidelinesAtomFamily, + refineIterationsAtomFamily, + refineLoadingAtomFamily, + workingPromptVersionAtomFamily, + workingPromptAtomFamily, +} from "../store/refinePromptStore" +import type {PromptTemplate, RefinementIteration} from "../types" + +interface UseRefinePromptParams { + variantId: string + promptId: string +} + +interface UseRefinePromptReturn { + refine: (guidelines: string) => Promise + isLoading: boolean +} + +/** + * Extract a simple PromptTemplate from the enhanced prompt structure. + */ +function extractPromptTemplate(enhancedPrompt: any): PromptTemplate | null { + if (!enhancedPrompt) return null + + const messagesValue = enhancedPrompt?.messages?.value + if (!Array.isArray(messagesValue)) return null + + const messages = messagesValue.map((msg: any) => { + const role = msg?.role?.value || msg?.role || "user" + let content = "" + + const contentValue = msg?.content?.value ?? msg?.content + if (typeof contentValue === "string") { + content = contentValue + } else if (Array.isArray(contentValue)) { + content = contentValue + .map((part: any) => part?.text?.value ?? part?.text ?? "") + .filter(Boolean) + .join("\n") + } else if (contentValue && typeof contentValue === "object") { + content = JSON.stringify(contentValue) + } + + return {role, content} + }) + + return { + messages, + template_format: enhancedPrompt?.template_format?.value || "", + } +} + +/** + * Parse the refined prompt from API response. + * + * The backend returns { messages: [...], summary: "..." } in structuredContent. + * We preserve the original template_format since refinement only changes message content. + */ +function parseRefineResponse( + response: any, + originalTemplateFormat: string, +): { + refinedPrompt: PromptTemplate | null + explanation: string +} { + const structured = response?.structuredContent + const explanation = structured?.summary || "Prompt refined successfully." + + if (structured?.messages && Array.isArray(structured.messages)) { + return { + refinedPrompt: { + messages: structured.messages, + template_format: originalTemplateFormat, + }, + explanation, + } + } + + return {refinedPrompt: null, explanation} +} + +export function useRefinePrompt({ + variantId, + promptId, +}: UseRefinePromptParams): UseRefinePromptReturn { + const enhancedPrompts = useAtomValue(moleculeBackedPromptsAtomFamily(variantId)) + + // Setters (stable references from Jotai) + const setOriginalSnapshot = useSetAtom(originalPromptSnapshotAtomFamily(promptId)) + const setWorkingPrompt = useSetAtom(workingPromptAtomFamily(promptId)) + const setWorkingPromptVersion = useSetAtom(workingPromptVersionAtomFamily(promptId)) + const setIterations = useSetAtom(refineIterationsAtomFamily(promptId)) + const setLoading = useSetAtom(refineLoadingAtomFamily(promptId)) + const setPendingGuidelines = useSetAtom(pendingGuidelinesAtomFamily(promptId)) + + const isLoading = useAtomValue(refineLoadingAtomFamily(promptId)) + + // Use refs for values only read inside callbacks to keep refine stable + const workingPromptRef = useRef(null) + const originalSnapshotRef = useRef(null) + const enhancedPromptsRef = useRef(enhancedPrompts) + + // Sync refs on each render + const workingPrompt = useAtomValue(workingPromptAtomFamily(promptId)) + const originalSnapshot = useAtomValue(originalPromptSnapshotAtomFamily(promptId)) + workingPromptRef.current = workingPrompt + originalSnapshotRef.current = originalSnapshot + enhancedPromptsRef.current = enhancedPrompts + + const refine = useCallback( + async (guidelines: string) => { + if (!guidelines.trim()) return + + setLoading(true) + setPendingGuidelines(guidelines) + + try { + // Find the prompt by ID + const promptList = Array.isArray(enhancedPromptsRef.current) + ? enhancedPromptsRef.current + : [] + const enhancedPrompt = + promptList.find((p: any) => p?.__id === promptId) || + promptList.find((p: any) => p?.__name === promptId) || + promptList[0] + + if (!enhancedPrompt) { + throw new Error("Prompt not found") + } + + // Use working prompt if we have one, else extract from enhanced + let promptToRefine = workingPromptRef.current + if (!promptToRefine) { + promptToRefine = extractPromptTemplate(enhancedPrompt) + } + + if (!promptToRefine) { + throw new Error("Could not extract prompt template") + } + + // Capture original snapshot on first refinement + if (!originalSnapshotRef.current) { + setOriginalSnapshot(promptToRefine) + } + + const response = await aiServicesApi.refinePrompt(promptToRefine, guidelines) + + if (response.isError) { + const errorText = response.content?.[0]?.text || "Failed to refine prompt" + throw new Error(errorText) + } + + const {refinedPrompt, explanation} = parseRefineResponse( + response, + promptToRefine.template_format || "", + ) + + if (!refinedPrompt) { + throw new Error("No refined prompt in response") + } + + setWorkingPrompt(refinedPrompt) + setWorkingPromptVersion((prev) => prev + 1) + + const iteration: RefinementIteration = { + id: generateId(), + guidelines, + explanation, + timestamp: Date.now(), + } + + setIterations((prev) => [...prev, iteration]) + } catch (error) { + console.error("Failed to refine prompt:", error) + + const iteration: RefinementIteration = { + id: generateId(), + guidelines, + explanation: + error instanceof Error + ? `Error: ${error.message}` + : "An unexpected error occurred", + timestamp: Date.now(), + } + + setIterations((prev) => [...prev, iteration]) + } finally { + setLoading(false) + setPendingGuidelines(null) + } + }, + [ + promptId, + setOriginalSnapshot, + setWorkingPrompt, + setWorkingPromptVersion, + setIterations, + setLoading, + setPendingGuidelines, + ], + ) + + return {refine, isLoading} +} diff --git a/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/index.tsx b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/index.tsx new file mode 100644 index 000000000..5be721ae5 --- /dev/null +++ b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/index.tsx @@ -0,0 +1,71 @@ +/** + * RefinePromptModal - Modal for AI-powered prompt refinement + * + * This modal provides a two-panel interface: + * - Left panel: Instructions/guidelines input with conversation-like display + * - Right panel: Refined prompt preview with diff toggle + */ + +import {useCallback, useEffect} from "react" + +import {EnhancedModal} from "@agenta/ui" +import {useAtom, useSetAtom} from "jotai" + +import RefinePromptModalContent from "./assets/RefinePromptModalContent" +import {refineModalOpenAtomFamily, resetRefineModalAtomFamily} from "./store/refinePromptStore" +import type {RefinePromptModalProps} from "./types" + +const RefinePromptModal: React.FC = ({ + open, + onClose, + variantId, + promptId, +}) => { + const [isOpen, setIsOpen] = useAtom(refineModalOpenAtomFamily(promptId)) + const resetModal = useSetAtom(resetRefineModalAtomFamily(promptId)) + + // Sync external open prop with internal state + useEffect(() => { + setIsOpen(open) + }, [open, setIsOpen]) + + const handleClose = useCallback(() => { + onClose() + }, [onClose]) + + const handleAfterClose = useCallback(() => { + resetModal() + }, [resetModal]) + + return ( + + + + ) +} + +export default RefinePromptModal diff --git a/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/store/refinePromptStore.ts b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/store/refinePromptStore.ts new file mode 100644 index 000000000..34a971bb2 --- /dev/null +++ b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/store/refinePromptStore.ts @@ -0,0 +1,87 @@ +/** + * State management for the Refine Prompt Modal + * + * Key insight: This is NOT a chat. Each "turn" is a refinement request: + * - User provides `guidelines` + * - AI returns `messages` + `summary` + * + * The state models this as RefinementIteration[], not chat messages. + */ + +import {atom} from "jotai" +import {atomFamily} from "jotai/utils" + +import type {PromptTemplate, RefinementIteration} from "../types" + +/** + * Modal open state (scoped per prompt) + */ +export const refineModalOpenAtomFamily = atomFamily((promptId: string) => atom(false)) + +/** + * Refinement iterations history (guidelines + explanations) + * Each iteration contains what the user asked for and what the AI explained it changed. + */ +export const refineIterationsAtomFamily = atomFamily((promptId: string) => + atom([]), +) + +/** + * Current working prompt (starts as original, updated after each refinement) + * This is what gets sent to the next refinement API call. + */ +export const workingPromptAtomFamily = atomFamily((promptId: string) => + atom(null), +) + +/** + * Editor remount version. + * + * Increments only when AI returns a new refined prompt so MessageEditor + * can reinitialize from the latest AI output without remounting on + * each manual keystroke. + */ +export const workingPromptVersionAtomFamily = atomFamily((promptId: string) => atom(0)) + +/** + * Loading state for refinement API calls + */ +export const refineLoadingAtomFamily = atomFamily((promptId: string) => atom(false)) + +/** + * Diff view toggle state + */ +export const refineDiffViewAtomFamily = atomFamily((promptId: string) => atom(false)) + +/** + * Original prompt snapshot (captured when modal opens, never changes) + * Used for diff comparison against the current working prompt. + */ +export const originalPromptSnapshotAtomFamily = atomFamily((promptId: string) => + atom(null), +) + +/** + * Pending guidelines (what user is currently typing/submitting) + * Used to show the user's message immediately while waiting for API response. + */ +export const pendingGuidelinesAtomFamily = atomFamily((promptId: string) => + atom(null), +) + +/** + * Helper atom to reset all modal state for a given promptId + * Usage: set(resetRefineModalAtomFamily(promptId)) + */ +export const resetRefineModalAtomFamily = atomFamily((promptId: string) => + atom(null, (get, set) => { + set(refineModalOpenAtomFamily(promptId), false) + set(refineIterationsAtomFamily(promptId), []) + set(workingPromptAtomFamily(promptId), null) + set(workingPromptVersionAtomFamily(promptId), 0) + set(refineLoadingAtomFamily(promptId), false) + set(refineDiffViewAtomFamily(promptId), false) + set(originalPromptSnapshotAtomFamily(promptId), null) + set(pendingGuidelinesAtomFamily(promptId), null) + }), +) diff --git a/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/types.ts b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/types.ts new file mode 100644 index 000000000..9faf291c7 --- /dev/null +++ b/web/oss/src/components/Playground/Components/Modals/RefinePromptModal/types.ts @@ -0,0 +1,82 @@ +/** + * Types for the Refine Prompt Modal + * + * Key insight: This is NOT a chat conversation. The API takes: + * - prompt_template_json: The prompt to refine + * - guidelines: User's refinement instructions + * + * And returns: + * - messages: The refined messages array + * - summary: Short description of what changed + * + * The UI presents this as a chat-like interface, but the state models it as refinement iterations. + */ + +/** + * A single refinement iteration - represents one round of user guidelines + AI response + */ +export interface RefinementIteration { + /** Unique identifier for this iteration */ + id: string + /** User's refinement instructions (what they asked for) */ + guidelines: string + /** AI's explanation of what changes were made */ + explanation: string + /** When this iteration was created */ + timestamp: number +} + +/** + * Prompt template structure (matches backend contract) + */ +export interface PromptTemplate { + messages: { + role: string + content: string + }[] + template_format?: string + input_keys?: string[] + llm_config?: Record +} + +/** + * Props for the RefinePromptModal component + */ +export interface RefinePromptModalProps { + open: boolean + onClose: () => void + variantId: string + promptId: string +} + +/** + * Props for the InstructionsPanel (left side) + */ +export interface InstructionsPanelProps { + iterations: RefinementIteration[] + pendingGuidelines: string | null + isLoading: boolean + onSubmitGuidelines: (guidelines: string) => void +} + +/** + * Props for the PreviewPanel (right side) + */ +export interface PreviewPanelProps { + originalPrompt: PromptTemplate | null + workingPrompt: PromptTemplate | null + showDiff: boolean + isLoading: boolean + onToggleDiff: (show: boolean) => void + onClose: () => void + onUpdateMessage: (index: number, field: "role" | "content", value: string) => void +} + +/** + * Props for the modal content (two-column layout) + */ +export interface RefinePromptModalContentProps { + variantId: string + promptId: string + onClose: () => void +} diff --git a/web/oss/src/components/Playground/Components/PlaygroundVariantConfigPrompt/assets/PlaygroundVariantConfigPromptCollapseHeader.tsx b/web/oss/src/components/Playground/Components/PlaygroundVariantConfigPrompt/assets/PlaygroundVariantConfigPromptCollapseHeader.tsx index a77843b1d..edcccfc49 100644 --- a/web/oss/src/components/Playground/Components/PlaygroundVariantConfigPrompt/assets/PlaygroundVariantConfigPromptCollapseHeader.tsx +++ b/web/oss/src/components/Playground/Components/PlaygroundVariantConfigPrompt/assets/PlaygroundVariantConfigPromptCollapseHeader.tsx @@ -1,11 +1,14 @@ -import {useMemo} from "react" +import {useCallback, useMemo, useState} from "react" -import {Typography} from "antd" +import {Button, Tooltip, Typography} from "antd" import clsx from "clsx" +import {useAtomValue} from "jotai" +import {Wand2} from "lucide-react" import dynamic from "next/dynamic" import {getPromptById} from "@/oss/components/Playground/context/promptShape" import {usePromptsSource} from "@/oss/components/Playground/context/PromptsSource" +import {aiServicesStatusQueryAtom} from "@/oss/services/aiServices/atoms" import type {PromptCollapseHeaderProps} from "../types" @@ -16,6 +19,9 @@ const PlaygroundVariantModelConfig = dynamic(() => import("../../PlaygroundVaria ssr: false, }) +// Load refine prompt modal dynamically +const RefinePromptModal = dynamic(() => import("../../Modals/RefinePromptModal"), {ssr: false}) + /** * PlaygroundVariantConfigPromptCollapseHeader renders the header section of a prompt configuration collapse. * @@ -39,20 +45,74 @@ const PlaygroundVariantConfigPromptCollapseHeader: React.FC { + const [refineModalOpen, setRefineModalOpen] = useState(false) + const statusQuery = useAtomValue(aiServicesStatusQueryAtom) + const aiServicesEnabled = statusQuery.data?.enabled ?? null const prompts = usePromptsSource(variantId) const promptName = useMemo(() => { const item = getPromptById(prompts, promptId) return (item?.__name as string | undefined) ?? "Prompt" }, [prompts, promptId]) + + const handleRefineClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() // Prevent collapse toggle + if (aiServicesEnabled) { + setRefineModalOpen(true) + } + }, + [aiServicesEnabled], + ) + + const handleRefineClose = useCallback(() => { + setRefineModalOpen(false) + }, []) + + const isLoading = statusQuery.isPending + const isDisabled = !aiServicesEnabled + const tooltipTitle = isLoading + ? "Checking AI services…" + : isDisabled + ? "AI services not available" + : "Refine prompt with AI" + return ( -
- {promptName || "Prompt"} - +
+ {promptName || "Prompt"} +
+ {!viewOnly && ( + +
+
+ -
+ ) } diff --git a/web/oss/src/services/aiServices/api.ts b/web/oss/src/services/aiServices/api.ts new file mode 100644 index 000000000..17f52bfe8 --- /dev/null +++ b/web/oss/src/services/aiServices/api.ts @@ -0,0 +1,79 @@ +/** + * AI Services API Client + * + * Client for interacting with the AI Services backend endpoints: + * - GET /preview/ai/services/status - Check if AI services are enabled + * - POST /preview/ai/services/tools/call - Execute a tool call (e.g., refine prompt) + */ + +import type {PromptTemplate} from "@/oss/components/Playground/Components/Modals/RefinePromptModal/types" +import axios from "@/oss/lib/api/assets/axiosConfig" + +// Tool name constant +export const TOOL_REFINE_PROMPT = "tools.agenta.api.refine_prompt" + +/** + * Response from GET /preview/ai/services/status + */ +export interface AIServicesStatus { + enabled: boolean + tools: { + name: string + title: string + description: string + inputSchema?: Record + outputSchema?: Record + }[] +} + +/** + * Response from POST /preview/ai/services/tools/call + */ +export interface ToolCallResponse { + content: {type: "text"; text: string}[] + structuredContent?: { + messages: {role: string; content: string}[] + summary?: string + } + isError: boolean + meta?: {trace_id?: string} +} + +/** + * AI Services API methods + */ +export const aiServicesApi = { + /** + * Check if AI services are enabled and get available tools + */ + async getStatus(): Promise { + const {data} = await axios.get("/preview/ai/services/status") + return data + }, + + /** + * Refine a prompt template using AI + * + * @param promptTemplate - The current prompt template to refine + * @param guidelines - User's instructions for how to refine the prompt + * @param context - Optional additional context + * @returns The refined prompt and explanation + */ + async refinePrompt( + promptTemplate: PromptTemplate, + guidelines: string, + context?: string, + ): Promise { + const {data} = await axios.post("/preview/ai/services/tools/call", { + name: TOOL_REFINE_PROMPT, + arguments: { + prompt_template_json: JSON.stringify(promptTemplate), + guidelines, + context: context || "", + }, + }) + return data + }, +} + +export default aiServicesApi diff --git a/web/oss/src/services/aiServices/atoms.ts b/web/oss/src/services/aiServices/atoms.ts new file mode 100644 index 000000000..97ae51226 --- /dev/null +++ b/web/oss/src/services/aiServices/atoms.ts @@ -0,0 +1,17 @@ +/** + * AI Services status atom — deduplicates status API calls across all components. + * + * Uses atomWithQuery so multiple PlaygroundVariantConfigPromptCollapseHeader + * instances share a single cached request instead of N×M calls. + */ + +import {atomWithQuery} from "jotai-tanstack-query" + +import {aiServicesApi} from "./api" + +export const aiServicesStatusQueryAtom = atomWithQuery(() => ({ + queryKey: ["ai-services-status"], + queryFn: () => aiServicesApi.getStatus(), + staleTime: 5 * 60_000, // Cache for 5 minutes + refetchOnWindowFocus: false, +})) diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 6b16b2e30..6ee26f1a2 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -355,6 +355,9 @@ importers: '@ant-design/icons': specifier: ^6.1.0 version: 6.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@ant-design/x': + specifier: ^2.2.2 + version: 2.2.2(antd@6.1.4(date-fns@3.6.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@cloudflare/stream-react': specifier: ^1.9.3 version: 1.9.3(react@19.0.0) @@ -1317,6 +1320,16 @@ packages: react: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@ant-design/x@2.2.2': + resolution: {integrity: sha512-fNXMfCKBT3goPUHgm3DE863IgWlFe233EAeR4CZKChVVc/oUdj19uRURNMV6LL1l535FToNZAiaCz7BXSxxZZg==} + peerDependencies: + antd: ^6.1.1 + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -1396,6 +1409,24 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + + '@chevrotain/gast@11.0.3': + resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + + '@chevrotain/types@11.0.3': + resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + + '@chevrotain/utils@11.0.3': + resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@cloudflare/stream-react@1.9.3': resolution: {integrity: sha512-ocr7B+zHk/jq0r/wgtFeyoz7Hr+v3Qn0Ho6yUBkWT07ahr/0GU1dhliYJESwNXMuMYCU1sZ02sFH9pW5r1/BIA==} engines: {node: '>=10'} @@ -1754,6 +1785,12 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} @@ -2017,6 +2054,9 @@ packages: peerDependencies: yjs: '>=13.5.22' + '@mermaid-js/parser@0.6.3': + resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@monaco-editor/loader@1.5.0': resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==} @@ -2673,30 +2713,96 @@ packages: '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + '@types/d3-color@3.1.3': resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + '@types/d3-ease@3.0.2': resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} '@types/d3-path@3.1.1': resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + '@types/d3-scale@4.0.9': resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + '@types/d3-shape@3.1.7': resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + '@types/d3-time@3.0.4': resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} '@types/d3-timer@3.0.2': resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/diff@5.2.3': resolution: {integrity: sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==} @@ -2709,6 +2815,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -2779,6 +2888,12 @@ packages: '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -3293,6 +3408,20 @@ packages: character-entities-legacy@3.0.0: resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chevrotain-allstar@0.3.1: + resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -3344,6 +3473,10 @@ packages: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + common-tags@1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} @@ -3354,6 +3487,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} @@ -3371,6 +3507,12 @@ packages: core-js@3.41.0: resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -3398,34 +3540,129 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + d3-path@3.1.0: resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} engines: {node: '>=12'} + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + d3-scale@4.0.2: resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} engines: {node: '>=12'} + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + d3-shape@3.2.0: resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} engines: {node: '>=12'} @@ -3442,6 +3679,23 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.13: + resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -3467,6 +3721,9 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -3490,6 +3747,9 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -3501,6 +3761,9 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -3545,6 +3808,9 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dotenv@16.5.0: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} @@ -3842,6 +4108,9 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -3906,6 +4175,10 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -4022,6 +4295,9 @@ packages: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + has-ansi@2.0.0: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} @@ -4053,18 +4329,30 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + hast-util-to-html@9.0.5: resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + highlightjs-vue@1.0.0: + resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -4082,6 +4370,10 @@ packages: hyphenate-style-name@1.1.0: resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4119,6 +4411,9 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} @@ -4126,6 +4421,12 @@ packages: intl-tel-input@17.0.21: resolution: {integrity: sha512-TfyPxLe41QZPOf6RqBxRE2dpQ0FThB/PBD/gRbxVhGW7IuYg30QD90x/vjmEo4vkZw7j8etxpVcjIZVRcG+Otw==} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -4165,6 +4466,9 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -4185,6 +4489,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-in-browser@1.1.3: resolution: {integrity: sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==} @@ -4451,9 +4758,20 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + katex@0.16.28: + resolution: {integrity: sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + langium@3.3.1: + resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} + engines: {node: '>=16.0.0'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -4461,6 +4779,12 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + leven@4.0.0: resolution: {integrity: sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4492,12 +4816,21 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash-es@4.17.23: + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} @@ -4512,6 +4845,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -4535,6 +4871,11 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -4559,6 +4900,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@11.12.2: + resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -4621,6 +4965,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + monaco-editor@0.52.2: resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} @@ -4806,6 +5153,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + papaparse@5.5.3: resolution: {integrity: sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==} @@ -4813,10 +5163,16 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4840,6 +5196,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -4859,6 +5218,9 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + playwright-core@1.57.0: resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} @@ -4869,6 +5231,12 @@ packages: engines: {node: '>=18'} hasBin: true + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -5097,6 +5465,12 @@ packages: 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 + react-syntax-highlighter@16.1.0: + resolution: {integrity: sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==} + engines: {node: '>= 16.20.2'} + peerDependencies: + react: '>= 0.14.0' + react-transition-group@4.4.5: resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -5157,6 +5531,9 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} + refractor@5.0.0: + resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -5215,9 +5592,18 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -5233,6 +5619,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.25.0: resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} @@ -5551,6 +5940,10 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -5578,6 +5971,10 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -5684,6 +6081,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -5769,6 +6169,26 @@ packages: victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + vue-eslint-parser@9.4.3: resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} engines: {node: ^14.17.0 || >=16.0.0} @@ -5951,6 +6371,30 @@ snapshots: react-dom: 19.0.0(react@19.0.0) throttle-debounce: 5.0.2 + '@ant-design/x@2.2.2(antd@6.1.4(date-fns@3.6.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@ant-design/colors': 8.0.0 + '@ant-design/cssinjs': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@ant-design/cssinjs-utils': 2.0.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@ant-design/fast-color': 3.0.0 + '@ant-design/icons': 6.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@babel/runtime': 7.28.4 + '@rc-component/motion': 1.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@rc-component/resize-observer': 1.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@rc-component/util': 1.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + antd: 6.1.4(date-fns@3.6.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + clsx: 2.1.1 + lodash.throttle: 4.1.1 + mermaid: 11.12.2 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-syntax-highlighter: 16.1.0(react@19.0.0) + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -6059,6 +6503,25 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@braintree/sanitize-url@7.1.2': {} + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + '@cloudflare/stream-react@1.9.3(react@19.0.0)': dependencies: react: 19.0.0 @@ -6376,6 +6839,14 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.0 + '@img/colour@1.0.0': optional: true @@ -6706,6 +7177,10 @@ snapshots: lexical: 0.38.2 yjs: 13.6.24 + '@mermaid-js/parser@0.6.3': + dependencies: + langium: 3.3.1 + '@monaco-editor/loader@1.5.0': dependencies: state-local: 1.0.7 @@ -7410,28 +7885,121 @@ snapshots: '@types/d3-array@3.2.1': {} + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + '@types/d3-color@3.1.3': {} + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.1 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + '@types/d3-ease@3.0.2': {} + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 '@types/d3-path@3.1.1': {} + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + '@types/d3-scale@4.0.9': dependencies: '@types/d3-time': 3.0.4 + '@types/d3-selection@3.0.11': {} + '@types/d3-shape@3.1.7': dependencies: '@types/d3-path': 3.1.1 + '@types/d3-time-format@4.0.3': {} + '@types/d3-time@3.0.4': {} '@types/d3-timer@3.0.2': {} + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.1 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/diff@5.2.3': {} '@types/eslint-scope@3.7.7': @@ -7446,6 +8014,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/geojson@7946.0.16': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -7524,6 +8094,11 @@ snapshots: '@types/semver@7.7.1': {} + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@2.0.11': {} + '@types/unist@3.0.3': {} '@types/use-sync-external-store@0.0.6': {} @@ -8129,6 +8704,24 @@ snapshots: character-entities-legacy@3.0.0: {} + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.23 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -8171,12 +8764,16 @@ snapshots: commander@7.2.0: {} + commander@8.3.0: {} + common-tags@1.8.2: {} compute-scroll-into-view@3.1.1: {} concat-map@0.0.1: {} + confbox@0.1.8: {} + config-chain@1.1.13: dependencies: ini: 1.3.8 @@ -8194,6 +8791,14 @@ snapshots: core-js@3.41.0: {} + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + create-require@1.1.1: {} crisp-sdk-web@1.0.26: {} @@ -8212,7 +8817,7 @@ snapshots: css-vendor@2.0.8: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 is-in-browser: 1.1.3 cssesc@3.0.0: {} @@ -8221,22 +8826,107 @@ snapshots: csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + d3-array@3.2.4: dependencies: internmap: 2.0.3 + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + d3-color@3.1.0: {} + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + d3-ease@3.0.1: {} + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + d3-format@3.1.0: {} + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 + d3-path@1.0.9: {} + d3-path@3.1.0: {} + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + d3-scale@4.0.2: dependencies: d3-array: 3.2.4 @@ -8245,6 +8935,12 @@ snapshots: d3-time: 3.1.0 d3-time-format: 4.1.0 + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + d3-shape@3.2.0: dependencies: d3-path: 3.1.0 @@ -8259,6 +8955,61 @@ snapshots: d3-timer@3.0.1: {} + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.23 + damerau-levenshtein@1.0.8: {} data-uri-to-buffer@4.0.1: {} @@ -8285,6 +9036,8 @@ snapshots: dayjs@1.11.13: {} + dayjs@1.11.19: {} + debounce@1.2.1: {} debug@3.2.7: @@ -8297,6 +9050,10 @@ snapshots: decimal.js-light@2.5.1: {} + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -8311,6 +9068,10 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + delayed-stream@1.0.0: {} depd@1.1.2: {} @@ -8347,6 +9108,10 @@ snapshots: '@babel/runtime': 7.28.4 csstype: 3.2.3 + dompurify@3.3.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dotenv@16.5.0: {} dunder-proto@1.0.1: @@ -8840,6 +9605,10 @@ snapshots: dependencies: reusify: 1.1.0 + fault@1.0.4: + dependencies: + format: 0.2.2 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -8900,6 +9669,8 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + format@0.2.2: {} + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -9027,6 +9798,8 @@ snapshots: dependencies: duplexer: 0.1.2 + hachure-fill@0.5.2: {} + has-ansi@2.0.0: dependencies: ansi-regex: 2.1.1 @@ -9053,6 +9826,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.4 @@ -9071,12 +9848,24 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + hermes-estree@0.25.1: {} hermes-parser@0.25.1: dependencies: hermes-estree: 0.25.1 + highlight.js@10.7.3: {} + + highlightjs-vue@1.0.0: {} + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -9089,6 +9878,10 @@ snapshots: hyphenate-style-name@1.1.0: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -9119,10 +9912,19 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@1.0.1: {} + internmap@2.0.3: {} intl-tel-input@17.0.21: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -9171,6 +9973,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-decimal@2.0.1: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -9190,6 +9994,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-in-browser@1.1.3: {} is-map@2.0.3: {} @@ -9368,69 +10174,69 @@ snapshots: jss-plugin-camel-case@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 hyphenate-style-name: 1.1.0 jss: 10.10.0 jss-plugin-compose@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 tiny-warning: 1.0.3 jss-plugin-default-unit@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 jss-plugin-expand@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 jss-plugin-extend@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 tiny-warning: 1.0.3 jss-plugin-global@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 jss-plugin-nested@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 tiny-warning: 1.0.3 jss-plugin-props-sort@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 jss-plugin-rule-value-function@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 tiny-warning: 1.0.3 jss-plugin-rule-value-observable@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 symbol-observable: 1.2.0 jss-plugin-template@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 jss: 10.10.0 tiny-warning: 1.0.3 jss-plugin-vendor-prefixer@10.10.0: dependencies: - '@babel/runtime': 7.27.6 + '@babel/runtime': 7.28.4 css-vendor: 2.0.8 jss: 10.10.0 @@ -9465,16 +10271,34 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + katex@0.16.28: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + language-subtag-registry@0.3.23: {} language-tags@1.0.9: dependencies: language-subtag-registry: 0.3.23 + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + leven@4.0.0: {} levn@0.4.1: @@ -9498,10 +10322,16 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.21: {} + + lodash-es@4.17.23: {} + lodash.debounce@4.0.8: {} lodash.merge@4.6.2: {} + lodash.throttle@4.1.1: {} + lodash@4.17.23: {} loglevel-colored-level-prefix@1.0.0: @@ -9515,6 +10345,11 @@ snapshots: dependencies: js-tokens: 4.0.0 + lowlight@1.20.0: + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -9533,6 +10368,8 @@ snapshots: make-error@1.3.6: {} + marked@16.4.2: {} + math-intrinsics@1.1.0: {} mdast-util-to-hast@13.2.1: @@ -9557,6 +10394,29 @@ snapshots: merge2@1.4.1: {} + mermaid@11.12.2: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.0 + '@mermaid-js/parser': 0.6.3 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.19 + dompurify: 3.3.1 + katex: 0.16.28 + khroma: 2.1.0 + lodash-es: 4.17.23 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + methods@1.1.2: {} micromark-util-character@2.1.1: @@ -9611,6 +10471,13 @@ snapshots: minipass@7.1.2: {} + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + monaco-editor@0.52.2: {} motion-dom@12.23.23: @@ -9796,14 +10663,28 @@ snapshots: package-json-from-dist@1.0.1: {} + package-manager-detector@1.6.0: {} + papaparse@5.5.3: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parseurl@1.3.3: {} + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -9819,6 +10700,8 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -9829,6 +10712,12 @@ snapshots: pirates@4.0.6: {} + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + playwright-core@1.57.0: {} playwright@1.57.0: @@ -9837,6 +10726,13 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + possible-typed-array-names@1.1.0: {} postcss-import@15.1.0(postcss@8.5.6): @@ -10067,6 +10963,16 @@ snapshots: react-dom: 19.0.0(react@19.0.0) react-transition-group: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react-syntax-highlighter@16.1.0(react@19.0.0): + dependencies: + '@babel/runtime': 7.28.4 + highlight.js: 10.7.3 + highlightjs-vue: 1.0.0 + lowlight: 1.20.0 + prismjs: 1.30.0 + react: 19.0.0 + refractor: 5.0.0 + react-transition-group@4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@babel/runtime': 7.28.4 @@ -10152,6 +11058,13 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 + refractor@5.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/prismjs': 1.26.5 + hastscript: 9.0.1 + parse-entities: 4.0.2 + regenerator-runtime@0.14.1: {} regex-recursion@6.0.2: @@ -10205,10 +11118,21 @@ snapshots: dependencies: glob: 7.2.3 + robust-predicates@3.0.2: {} + + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -10230,6 +11154,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + scheduler@0.25.0: {} schema-utils@4.3.3: @@ -10633,6 +11559,8 @@ snapshots: tiny-warning@1.0.3: {} + tinyexec@1.0.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -10654,6 +11582,8 @@ snapshots: dependencies: typescript: 5.8.3 + ts-dedent@2.2.0: {} + ts-interface-checker@0.1.13: {} ts-node@10.9.2(@swc/core@1.11.8(@swc/helpers@0.5.17))(@types/node@20.17.24)(typescript@5.8.3): @@ -10803,6 +11733,8 @@ snapshots: typescript@5.8.3: {} + ufo@1.6.3: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -10942,6 +11874,23 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + vue-eslint-parser@9.4.3(eslint@8.57.1): dependencies: debug: 4.4.3