diff --git a/debezium-platform-conductor/pom.xml b/debezium-platform-conductor/pom.xml index a1067d18..6876b8c3 100644 --- a/debezium-platform-conductor/pom.xml +++ b/debezium-platform-conductor/pom.xml @@ -71,6 +71,33 @@ import + + + io.quarkiverse.langchain4j + quarkus-langchain4j-bom + 1.4.2 + pom + import + + + + + dev.langchain4j + langchain4j-bom + 1.8.0 + pom + import + + + + + io.quarkiverse.mcp + quarkus-mcp-server-bom + 1.8.0 + pom + import + + org.testcontainers testcontainers-bom @@ -79,6 +106,13 @@ import + + + dev.langchain4j + langchain4j-core + 1.9.1 + + com.blazebit blaze-persistence-integration-jackson-jakarta @@ -475,6 +509,38 @@ quarkus-test-kafka-companion test + + + + + io.quarkiverse.langchain4j + quarkus-langchain4j-core + + + io.quarkiverse.langchain4j + quarkus-langchain4j-anthropic + + + io.quarkiverse.langchain4j + quarkus-langchain4j-mcp + + + + + io.quarkiverse.mcp + quarkus-mcp-server-http + + + io.quarkiverse.mcp + quarkus-mcp-server-hibernate-validator + + + + + dev.langchain4j + langchain4j-vertex-ai-anthropic + 1.9.1-beta17 + diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/chat/ChatService.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/chat/ChatService.java new file mode 100644 index 00000000..2a35f4c0 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/chat/ChatService.java @@ -0,0 +1,80 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.chat; + +import java.io.Serializable; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.context.SessionScoped; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.debezium.common.annotation.Incubating; + +import dev.langchain4j.model.chat.response.ChatResponse; +import dev.langchain4j.rag.content.Content; +import dev.langchain4j.service.TokenStream; +import dev.langchain4j.service.tool.ToolExecution; + +/** + * ChatService using fully declarative Quarkus LangChain4j approach. + * - Assistant is auto-generated via @RegisterAiService + * - MCP tools configured declaratively in application.properties + * - StreamingChatModel injected from ChatModelProducer (profile-based) + * + * @author Mario Fiore Vitale + */ +@Incubating +@SessionScoped +public class ChatService implements Serializable { + + private static final Logger LOG = LoggerFactory.getLogger(ChatService.class); + + private final Zium zium; + + public ChatService(Zium zium) { + this.zium = zium; + } + + /** + * Chat method for WebSocket - logs intermediate responses, returns only final answer + */ + public CompletableFuture chatWithLogging(String message) { + CompletableFuture futureResponse = new CompletableFuture<>(); + + LOG.info("Processing chat message: {}", message); + + TokenStream tokenStream = zium.chat(message); + + tokenStream + .onPartialResponse((String partialResponse) -> { + LOG.debug("Partial response: {}", partialResponse); + }) + .onRetrieved((List contents) -> { + LOG.info("Retrieved contents: {}", contents); + }) + .onIntermediateResponse((ChatResponse intermediateResponse) -> { + LOG.info("Intermediate response: {}", intermediateResponse); + }) + .onToolExecuted((ToolExecution toolExecution) -> { + LOG.info("Tool execution result: {}", toolExecution); + }) + .onCompleteResponse((ChatResponse response) -> { + LOG.info("Chat completed successfully"); + String finalAnswer = response.aiMessage().text(); + futureResponse.complete(finalAnswer); + }) + .onError((Throwable error) -> { + LOG.error("Error during chat processing", error); + futureResponse.completeExceptionally(error); + }) + .start(); + + return futureResponse; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/chat/ChatWebSocket.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/chat/ChatWebSocket.java new file mode 100644 index 00000000..3484b303 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/chat/ChatWebSocket.java @@ -0,0 +1,90 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.chat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.debezium.common.annotation.Incubating; +import io.quarkus.websockets.next.OnClose; +import io.quarkus.websockets.next.OnError; +import io.quarkus.websockets.next.OnOpen; +import io.quarkus.websockets.next.OnTextMessage; +import io.quarkus.websockets.next.WebSocket; +import io.quarkus.websockets.next.WebSocketConnection; + +/** + * WebSocket endpoint for chat using quarkus-websockets-next. + * This API properly supports @SessionScoped beans for chat memory. + * + * @author Mario Fiore Vitale + */ +@Incubating +@WebSocket(path = "/api/chat") +public class ChatWebSocket { + + private static final Logger LOG = LoggerFactory.getLogger(ChatWebSocket.class); + + private final ChatService chatService; + + public ChatWebSocket(ChatService chatService) { + this.chatService = chatService; + } + + @OnOpen + public void onOpen(WebSocketConnection connection) { + LOG.info("WebSocket opened: {}", connection.id()); + connection.sendTextAndAwait("{\"type\":\"connected\",\"message\":\"Connected to chat service\"}"); + } + + @OnClose + public void onClose(WebSocketConnection connection) { + LOG.info("WebSocket closed: {}", connection.id()); + } + + @OnError + public void onError(WebSocketConnection connection, Throwable throwable) { + LOG.error("WebSocket error on connection: {}", connection.id(), throwable); + connection.sendTextAndAwait("{\"type\":\"error\",\"message\":\"" + escapeJson(throwable.getMessage()) + "\"}"); + } + + @OnTextMessage + public void onMessage(String message, WebSocketConnection connection) { + LOG.info("Received message from {}: {}", connection.id(), message); + + try { + // Send processing indicator + connection.sendTextAndAwait("{\"type\":\"processing\",\"message\":\"Processing your request...\"}"); + + chatService.chatWithLogging(message).whenComplete((response, error) -> { + if (error != null) { + LOG.error("Error processing chat message", error); + connection.sendTextAndAwait("{\"type\":\"error\",\"message\":\"" + escapeJson(error.getMessage()) + "\"}"); + } + else { + // Send the complete final answer + String jsonResponse = "{\"type\":\"answer\",\"content\":\"" + escapeJson(response) + "\"}"; + connection.sendTextAndAwait(jsonResponse); + } + }); + } + catch (Exception e) { + LOG.error("Error handling message", e); + connection.sendTextAndAwait("{\"type\":\"error\",\"message\":\"" + escapeJson(e.getMessage()) + "\"}"); + } + } + + private String escapeJson(String input) { + if (input == null) { + return ""; + } + return input.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/chat/Zium.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/chat/Zium.java new file mode 100644 index 00000000..d004d923 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/chat/Zium.java @@ -0,0 +1,27 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.chat; + +import jakarta.enterprise.context.SessionScoped; + +import io.debezium.common.annotation.Incubating; +import io.quarkiverse.langchain4j.RegisterAiService; +import io.quarkiverse.langchain4j.mcp.runtime.McpToolBox; + +import dev.langchain4j.service.TokenStream; + +/** + * Debezium Platform AI Assistant using declarative Quarkus LangChain4j. + * Uses MCP protocol to access Platform tools. + */ +@Incubating +@RegisterAiService +@SessionScoped +public interface Zium { + + @McpToolBox({ "debezium-platform", "context7" }) + TokenStream chat(String userMessage); +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiDestination.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiDestination.java new file mode 100644 index 00000000..52eac69f --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiDestination.java @@ -0,0 +1,92 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.dto; + +import java.util.List; +import java.util.Map; + +import jakarta.validation.constraints.NotEmpty; + +/** + * AI-specific destination DTO for MCP tools. + * Simplified version optimized for LLM interaction with validation for schema generation. + */ +public class AiDestination { + private String description; + + @NotEmpty + private String type; + + @NotEmpty + private String schema; + + private List vaults; + private Map config; + + @NotEmpty + private String name; + + private Long id; + + public AiDestination() { + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public List getVaults() { + return vaults; + } + + public void setVaults(List vaults) { + this.vaults = vaults; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiDestinationReference.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiDestinationReference.java new file mode 100644 index 00000000..b21e17c3 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiDestinationReference.java @@ -0,0 +1,38 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.dto; + +import jakarta.validation.constraints.NotEmpty; + +/** + * AI-specific destination reference DTO for MCP tools. + * References an existing destination by name for pipeline creation. + */ +public class AiDestinationReference { + @NotEmpty + private String name; + + private Long id; + + public AiDestinationReference() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiPipeline.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiPipeline.java new file mode 100644 index 00000000..00cbe071 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiPipeline.java @@ -0,0 +1,104 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.dto; + +import java.util.List; +import java.util.Map; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +/** + * AI-specific pipeline DTO for MCP tools. + * Simplified version optimized for LLM interaction with validation for schema generation. + */ +public class AiPipeline { + @NotEmpty + private String name; + + private Long id; + private String description; + + @Valid + @NotNull + private AiSourceReference source; + + @Valid + @NotNull + private AiDestinationReference destination; + + private List transforms; + private String logLevel; + private Map logLevels; + + public AiPipeline() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public AiSourceReference getSource() { + return source; + } + + public void setSource(AiSourceReference source) { + this.source = source; + } + + public AiDestinationReference getDestination() { + return destination; + } + + public void setDestination(AiDestinationReference destination) { + this.destination = destination; + } + + public List getTransforms() { + return transforms; + } + + public void setTransforms(List transforms) { + this.transforms = transforms; + } + + public String getLogLevel() { + return logLevel; + } + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + public Map getLogLevels() { + return logLevels; + } + + public void setLogLevels(Map logLevels) { + this.logLevels = logLevels; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiSource.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiSource.java new file mode 100644 index 00000000..4de9de02 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiSource.java @@ -0,0 +1,92 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.dto; + +import java.util.List; +import java.util.Map; + +import jakarta.validation.constraints.NotEmpty; + +/** + * AI-specific source DTO for MCP tools. + * Simplified version optimized for LLM interaction with validation for schema generation. + */ +public class AiSource { + private String description; + + @NotEmpty + private String type; + + @NotEmpty + private String schema; + + private List vaults; + private Map config; + + @NotEmpty + private String name; + + private Long id; + + public AiSource() { + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public List getVaults() { + return vaults; + } + + public void setVaults(List vaults) { + this.vaults = vaults; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiSourceReference.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiSourceReference.java new file mode 100644 index 00000000..c6aec141 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiSourceReference.java @@ -0,0 +1,38 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.dto; + +import jakarta.validation.constraints.NotEmpty; + +/** + * AI-specific source reference DTO for MCP tools. + * References an existing source by name for pipeline creation. + */ +public class AiSourceReference { + @NotEmpty + private String name; + + private Long id; + + public AiSourceReference() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiTransformReference.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiTransformReference.java new file mode 100644 index 00000000..3caf3272 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiTransformReference.java @@ -0,0 +1,34 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.dto; + +/** + * AI-specific transform reference DTO for MCP tools. + * References a transform by name for pipeline creation. + */ +public class AiTransformReference { + private String name; + private Long id; + + public AiTransformReference() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiVaultReference.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiVaultReference.java new file mode 100644 index 00000000..b9d85848 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/dto/AiVaultReference.java @@ -0,0 +1,34 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.dto; + +/** + * AI-specific vault reference DTO for MCP tools. + * Simplified version for LLM interaction. + */ +public class AiVaultReference { + private String name; + private Long id; + + public AiVaultReference() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/mcp/Context7McpAuthProvider.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/mcp/Context7McpAuthProvider.java new file mode 100644 index 00000000..2d40ed21 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/mcp/Context7McpAuthProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.mcp; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkiverse.langchain4j.mcp.auth.McpClientAuthProvider; + +/** + * Authorization provider for Context7 MCP client. + * Injects the CONTEXT7_API_KEY as a Bearer token in the Authorization header. + */ +@ApplicationScoped +public class Context7McpAuthProvider implements McpClientAuthProvider { + + public static final String CONTEXT_7_DOMAIN = "context7.com"; + @ConfigProperty(name = "context7.api-key", defaultValue = "") + String apiKey; + + @Override + public String getAuthorization(Input input) { + // Only provide auth for Context7 MCP server + if (input.uri().toString().contains(CONTEXT_7_DOMAIN)) { + if (apiKey != null && !apiKey.isEmpty()) { + return "Bearer " + apiKey; + } + } + return null; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/mcp/PlatformMcpTools.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/mcp/PlatformMcpTools.java new file mode 100644 index 00000000..7e3c3fea --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/mcp/PlatformMcpTools.java @@ -0,0 +1,329 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.mcp; + +import java.util.HashSet; +import java.util.stream.Collectors; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; + +import com.blazebit.persistence.view.EntityViewManager; + +import io.debezium.common.annotation.Incubating; +import io.debezium.platform.ai.dto.AiDestination; +import io.debezium.platform.ai.dto.AiPipeline; +import io.debezium.platform.ai.dto.AiSource; +import io.debezium.platform.domain.DestinationService; +import io.debezium.platform.domain.PipelineService; +import io.debezium.platform.domain.SourceService; +import io.debezium.platform.domain.views.Destination; +import io.debezium.platform.domain.views.Pipeline; +import io.debezium.platform.domain.views.Source; +import io.debezium.platform.domain.views.refs.DestinationReference; +import io.debezium.platform.domain.views.refs.SourceReference; +import io.quarkiverse.mcp.server.Tool; +import io.quarkiverse.mcp.server.ToolArg; + +/** + * MCP tools for Platform operations using direct service injection. + * Exposes sources, destinations, and pipelines management to AI assistants. + * + * @author Mario Fiore Vitale + */ +@Incubating +@ApplicationScoped +public class PlatformMcpTools { + + @Inject + SourceService sourceService; + + @Inject + DestinationService destinationService; + + @Inject + PipelineService pipelineService; + + @Inject + EntityViewManager evm; + + @Inject + io.debezium.platform.domain.VaultService vaultService; + + @Tool(description = "Get defined pipelines on Debezium Platform.") + public String getPipelines() { + var pipelines = pipelineService.list(); + + if (pipelines == null || pipelines.isEmpty()) { + return "No pipelines are currently defined on the Debezium Platform."; + } + + return pipelines.stream().map(p -> """ + Name: %s + Description: %s + Source: %s + Destination: %s + """.formatted( + p.getName(), + p.getDescription() != null ? p.getDescription() : "(no description)", + p.getSource() != null ? p.getSource().getName() : "(not set)", + p.getDestination() != null ? p.getDestination().getName() : "(not set)")) + .collect(Collectors.joining("\n---\n")); + } + + @Tool(description = "Get defined sources on Debezium Platform.") + public String getSources() { + var sources = sourceService.list(); + + if (sources == null || sources.isEmpty()) { + return "No sources are currently defined on the Debezium Platform."; + } + + return sources.stream().map(s -> """ + Name: %s + Description: %s + Id: %s + Type: %s + Schema: %s + """.formatted( + s.getName(), + s.getDescription() != null ? s.getDescription() : "(no description)", + s.getId(), + s.getType(), + s.getSchema())) + .collect(Collectors.joining("\n---\n")); + } + + @Tool(description = "Get defined destinations on Debezium Platform.") + public String getDestinations() { + var destinations = destinationService.list(); + + if (destinations == null || destinations.isEmpty()) { + return "No destinations are currently defined on the Debezium Platform."; + } + + return destinations.stream().map(d -> """ + Name: %s + Description: %s + Id: %s + Type: %s + Schema: %s + """.formatted( + d.getName(), + d.getDescription() != null ? d.getDescription() : "(no description)", + d.getId(), + d.getType(), + d.getSchema())) + .collect(Collectors.joining("\n---\n")); + } + + @Tool(description = "Create a new data pipeline on Debezium Platform. " + + "A pipeline connects a source to a destination to enable change data capture. " + + "IMPORTANT: Always call first with userConfirmed=false to preview the configuration, " + + "then ask the user for approval before calling again with userConfirmed=true to create.") + @Transactional + public String createPipeline( + @ToolArg(description = "Set to false for preview, true to execute after user confirms") boolean userConfirmed, + @Valid @ToolArg(description = "Pipeline configuration") AiPipeline aiPipeline) { + + if (!userConfirmed) { + return String.format(""" + ⚠️ CONFIRMATION REQUIRED ⚠️ + + You are about to CREATE A PIPELINE with the following details: + - Name: %s + - Description: %s + - Source: %s + - Destination: %s + + This will create a new data pipeline on the Debezium Platform. + + Please ask the user: "Do you want to proceed with creating this pipeline?" + If the user confirms, call this tool again with userConfirmed=true. + """, + aiPipeline.getName(), + aiPipeline.getDescription() != null ? aiPipeline.getDescription() : "(no description)", + aiPipeline.getSource() != null ? aiPipeline.getSource().getName() : "(not specified)", + aiPipeline.getDestination() != null ? aiPipeline.getDestination().getName() : "(not specified)"); + } + + try { + Pipeline pipeline = evm.create(Pipeline.class); + pipeline.setName(aiPipeline.getName()); + pipeline.setDescription(aiPipeline.getDescription()); + + // Resolve source reference by finding source by name + if (aiPipeline.getSource() != null) { + String sourceName = aiPipeline.getSource().getName(); + Source source = sourceService.list().stream() + .filter(s -> s.getName().equals(sourceName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Source not found: " + sourceName)); + SourceReference sourceRef = evm.getReference(SourceReference.class, source.getId()); + pipeline.setSource(sourceRef); + } + + // Resolve destination reference by finding destination by name + if (aiPipeline.getDestination() != null) { + String destName = aiPipeline.getDestination().getName(); + Destination dest = destinationService.list().stream() + .filter(d -> d.getName().equals(destName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Destination not found: " + destName)); + DestinationReference destRef = evm.getReference(DestinationReference.class, dest.getId()); + pipeline.setDestination(destRef); + } + + if (aiPipeline.getLogLevel() != null) { + pipeline.setLogLevel(aiPipeline.getLogLevel()); + } + if (aiPipeline.getLogLevels() != null) { + pipeline.setLogLevels(aiPipeline.getLogLevels()); + } + + Pipeline created = pipelineService.create(pipeline); + return "Pipeline created successfully with ID: " + created.getId(); + + } + catch (Exception e) { + return "Error creating pipeline: " + e.getMessage(); + } + } + + @Tool(description = "Create a new data source connector on Debezium Platform. " + + "A source captures change events from a database or data system. " + + "Supported types include: postgresql, mysql, mongodb, sqlserver, oracle. " + + "IMPORTANT: Always call first with userConfirmed=false to preview the configuration, " + + "then ask the user for approval before calling again with userConfirmed=true to create.") + @Transactional + public String createSource( + @ToolArg(description = "Set to false for preview, true to execute after user confirms") boolean userConfirmed, + @Valid @ToolArg(description = "Source connector configuration") AiSource aiSource) { + + if (!userConfirmed) { + return String.format(""" + ⚠️ CONFIRMATION REQUIRED ⚠️ + + You are about to CREATE A SOURCE with the following details: + - Name: %s + - Type: %s + - Schema: %s + - Description: %s + - Config: %s + + This will create a new data source connector on the Debezium Platform. + + Please ask the user: "Do you want to proceed with creating this source?" + If the user confirms, call this tool again with userConfirmed=true. + """, + aiSource.getName(), + aiSource.getType(), + aiSource.getSchema(), + aiSource.getDescription() != null ? aiSource.getDescription() : "(no description)", + aiSource.getConfig() != null ? aiSource.getConfig().toString() : "(no additional config)"); + } + + try { + Source source = evm.create(Source.class); + source.setName(aiSource.getName()); + source.setType(aiSource.getType()); + source.setSchema(aiSource.getSchema()); + source.setDescription(aiSource.getDescription()); + + if (aiSource.getConfig() != null) { + source.setConfig(aiSource.getConfig()); + } + + if (aiSource.getVaults() != null && !aiSource.getVaults().isEmpty()) { + source.setVaults(new HashSet<>(aiSource.getVaults().stream() + .map(v -> { + String vaultName = v.getName(); + var vault = vaultService.list().stream() + .filter(vlt -> vlt.getName().equals(vaultName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Vault not found: " + vaultName)); + return evm.getReference(io.debezium.platform.domain.views.refs.VaultReference.class, vault.getId()); + }) + .toList())); + } + + Source created = sourceService.create(source); + return "Source created successfully with ID: " + created.getId(); + + } + catch (Exception e) { + return "Error creating source: " + e.getMessage(); + } + } + + @Tool(description = "Create a new destination connector on Debezium Platform. " + + "A destination receives change events from sources via pipelines. " + + "Supported types include: kafka, http, elasticsearch. " + + "IMPORTANT: Always call first with userConfirmed=false to preview the configuration, " + + "then ask the user for approval before calling again with userConfirmed=true to create.") + @Transactional + public String createDestination( + @ToolArg(description = "Set to false for preview, true to execute after user confirms") boolean userConfirmed, + @Valid @ToolArg(description = "Destination connector configuration") AiDestination aiDestination) { + + if (!userConfirmed) { + return String.format(""" + ⚠️ CONFIRMATION REQUIRED ⚠️ + + You are about to CREATE A DESTINATION with the following details: + - Name: %s + - Type: %s + - Schema: %s + - Description: %s + - Config: %s + + This will create a new destination connector on the Debezium Platform. + + Please ask the user: "Do you want to proceed with creating this destination?" + If the user confirms, call this tool again with userConfirmed=true. + """, + aiDestination.getName(), + aiDestination.getType(), + aiDestination.getSchema(), + aiDestination.getDescription() != null ? aiDestination.getDescription() : "(no description)", + aiDestination.getConfig() != null ? aiDestination.getConfig().toString() : "(no additional config)"); + } + + try { + Destination destination = evm.create(Destination.class); + destination.setName(aiDestination.getName()); + destination.setType(aiDestination.getType()); + destination.setSchema(aiDestination.getSchema()); + destination.setDescription(aiDestination.getDescription()); + + if (aiDestination.getConfig() != null) { + destination.setConfig(aiDestination.getConfig()); + } + + if (aiDestination.getVaults() != null && !aiDestination.getVaults().isEmpty()) { + destination.setVaults(new HashSet<>(aiDestination.getVaults().stream() + .map(v -> { + String vaultName = v.getName(); + var vault = vaultService.list().stream() + .filter(vlt -> vlt.getName().equals(vaultName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Vault not found: " + vaultName)); + return evm.getReference(io.debezium.platform.domain.views.refs.VaultReference.class, vault.getId()); + }) + .toList())); + } + + Destination created = destinationService.create(destination); + return "Destination created successfully with ID: " + created.getId(); + + } + catch (Exception e) { + return "Error creating destination: " + e.getMessage(); + } + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/model/ChatModelProducer.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/model/ChatModelProducer.java new file mode 100644 index 00000000..89734326 --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/model/ChatModelProducer.java @@ -0,0 +1,108 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.model; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.debezium.common.annotation.Incubating; +import io.quarkus.arc.DefaultBean; +import io.quarkus.arc.profile.IfBuildProfile; + +import dev.langchain4j.model.anthropic.AnthropicStreamingChatModel; +import dev.langchain4j.model.chat.StreamingChatModel; + +/** + * Produces profile-based StreamingChatModel beans. + * - Dev profile: CustomVertexAiAnthropicStreamingChatModel with SSL trust-all + * - Production: Standard AnthropicStreamingChatModel + */ +@Incubating +@ApplicationScoped +public class ChatModelProducer { + + @ConfigProperty(name = "quarkus.langchain4j.anthropic.api-key") + String apiKey; + + @ConfigProperty(name = "quarkus.langchain4j.anthropic.base-url") + String baseUrl; + + @ConfigProperty(name = "quarkus.langchain4j.anthropic.chat-model.model-name") + String modelName; + + @ConfigProperty(name = "quarkus.langchain4j.anthropic.chat-model.log-requests", defaultValue = "false") + boolean logRequests; + + @ConfigProperty(name = "quarkus.langchain4j.anthropic.chat-model.log-responses", defaultValue = "false") + boolean logResponses; + + /** + * Production chat model - standard Anthropic client + */ + @Produces + @DefaultBean + @ApplicationScoped + public StreamingChatModel productionChatModel() { + return AnthropicStreamingChatModel.builder() + .baseUrl(baseUrl) + .apiKey(apiKey) + .modelName(modelName) + .logRequests(logRequests) + .logResponses(logResponses) + .build(); + } + + /** + * Dev mode chat model - custom implementation with SSL trust-all + */ + @Produces + @IfBuildProfile("dev") + @ApplicationScoped + public StreamingChatModel devChatModel() { + return CustomVertexAiAnthropicStreamingChatModel.builder() + .baseUrl(baseUrl) + .apiKey(apiKey) + .modelName(modelName) + .systemPrompt(getSystemPrompt()) + .logRequests(logRequests) + .logResponses(logResponses) + .build(); + } + + private String getSystemPrompt() { + return """ + FORMATTING RULES - STRICTLY FOLLOW THESE: + - Use ONLY plain text - NO markdown syntax at all + - Do NOT use **bold**, _italic_, `code`, or any markdown formatting + - Do NOT use # headers or --- dividers + - Use UPPERCASE for emphasis on important names and terms + - Use simple indentation and line breaks for structure + - Use simple bullet points with - or • characters only + - Keep all formatting minimal and readable as plain text + + Example of CORRECT formatting: + CONFIGURATION PREVIEW: + - Name: postgres-source + - Type: PostgreSQL + + Example of WRONG formatting (DO NOT DO THIS): + **Configuration Preview:** + - **Name:** postgres-source + + CRITICAL: Do NOT show tool invocation syntax (like , , etc.) in your responses. + Tool calls happen automatically in the background - only show the results to the user. + Present information naturally as if you performed the action yourself. + + IMPORTANT SAFETY RULE: For any WRITE operations (createSource, createDestination, createPipeline), + you MUST ALWAYS call the tool first with userConfirmed=false to get the confirmation details, + then present those details to the user and ask for their explicit confirmation, + and ONLY after the user confirms should you call the tool again with userConfirmed=true. + NEVER execute write operations without user confirmation. + """; + } +} diff --git a/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/model/CustomVertexAiAnthropicStreamingChatModel.java b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/model/CustomVertexAiAnthropicStreamingChatModel.java new file mode 100644 index 00000000..399b64be --- /dev/null +++ b/debezium-platform-conductor/src/main/java/io/debezium/platform/ai/model/CustomVertexAiAnthropicStreamingChatModel.java @@ -0,0 +1,774 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package io.debezium.platform.ai.model; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.stream.Collectors; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import dev.langchain4j.agent.tool.ToolSpecification; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.data.message.ToolExecutionResultMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.model.ModelProvider; +import dev.langchain4j.model.chat.StreamingChatModel; +import dev.langchain4j.model.chat.listener.ChatModelListener; +import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.request.ChatRequestParameters; +import dev.langchain4j.model.chat.response.ChatResponse; +import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; +import dev.langchain4j.model.output.FinishReason; +import dev.langchain4j.model.output.TokenUsage; + +/** + * Custom streaming chat model for internal Vertex AI-compatible Anthropic API. + * This implementation works with APIs that use Vertex AI request/response format + * but have custom endpoints and bearer token authentication. + *

+ * + * The following code is mainly generated by Claude since this is just a custom adapter to internal model serving service + * + * @author Mario Fiore Vitale + */ +public class CustomVertexAiAnthropicStreamingChatModel implements StreamingChatModel { + + private static final Logger LOG = LoggerFactory.getLogger(CustomVertexAiAnthropicStreamingChatModel.class); + private static final String ANTHROPIC_VERSION = "vertex-2023-10-16"; + + // Static initializer to configure SSL to trust all certificates (for development only) + static { + try { + // Create a trust manager that trusts all certificates + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + } + }; + + // Install the all-trusting trust manager + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + + // Install the all-trusting host verifier + HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); + + LOG.info("SSL trust-all configured for CustomVertexAiAnthropicStreamingChatModel"); + } + catch (Exception e) { + LOG.error("Failed to configure SSL trust-all", e); + } + } + + private final String baseUrl; + private final String apiKey; + private final String modelName; + private final String modelType; // "sonnet" or "haiku" + private final Integer maxTokens; + private final Double temperature; + private final Boolean logRequests; + private final Boolean logResponses; + private final String systemPrompt; + private final ObjectMapper objectMapper; + + private CustomVertexAiAnthropicStreamingChatModel(Builder builder) { + this.baseUrl = builder.baseUrl; + this.apiKey = builder.apiKey; + this.modelName = builder.modelName; + this.modelType = determineModelType(builder.modelName); + this.maxTokens = builder.maxTokens != null ? builder.maxTokens : 4096; + this.temperature = builder.temperature; + this.logRequests = builder.logRequests != null ? builder.logRequests : false; + this.logResponses = builder.logResponses != null ? builder.logResponses : false; + this.systemPrompt = builder.systemPrompt; + + // Configure ObjectMapper to handle unknown types gracefully + this.objectMapper = new ObjectMapper(); + this.objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + } + + private String determineModelType(String modelName) { + if (modelName.toLowerCase().contains("haiku")) { + return "haiku"; + } + else if (modelName.toLowerCase().contains("sonnet")) { + return "sonnet"; + } + else { + // Default to sonnet + return "sonnet"; + } + } + + @Override + public void doChat(ChatRequest chatRequest, StreamingChatResponseHandler handler) { + try { + // Build the endpoint URL + String endpoint = String.format("%s/%s/models/%s:streamRawPredict", + baseUrl, modelType, modelName); + + if (logRequests) { + LOG.info("Calling endpoint: {}", endpoint); + } + + // Convert ChatRequest to Vertex AI format + Map requestBody = buildRequestBody(chatRequest); + + if (logRequests) { + try { + LOG.info("Request body: {}", objectMapper.writeValueAsString(requestBody)); + } + catch (Exception e) { + LOG.warn("Could not serialize request body for logging: {}", e.getMessage()); + LOG.info("Request endpoint: {}", endpoint); + } + } + + // Make HTTP request + makeStreamingRequest(endpoint, requestBody, handler); + + } + catch (Exception e) { + LOG.error("Error during chat", e); + try { + handler.onError(e); + } + catch (Exception userException) { + LOG.warn("User's onError handler threw an exception, ignoring", userException); + } + } + } + + private Map buildRequestBody(ChatRequest chatRequest) { + Map body = new HashMap<>(); + body.put("anthropic_version", ANTHROPIC_VERSION); + + // Add system message if configured + if (systemPrompt != null && !systemPrompt.isEmpty()) { + body.put("system", systemPrompt); + } + + // Convert messages + List> messages = convertMessages(chatRequest.messages()); + body.put("messages", messages); + + // Set max_tokens + ChatRequestParameters params = chatRequest.parameters(); + Integer tokens = params.maxOutputTokens() != null ? params.maxOutputTokens() : maxTokens; + body.put("max_tokens", tokens); + + // Optional parameters + if (temperature != null) { + body.put("temperature", temperature); + } + + // Add tools if present + if (params.toolSpecifications() != null && !params.toolSpecifications().isEmpty()) { + List> tools = convertTools(params.toolSpecifications()); + body.put("tools", tools); + } + + return body; + } + + private List> convertMessages(List messages) { + List> result = new ArrayList<>(); + + for (ChatMessage message : messages) { + Map msg = new HashMap<>(); + + // Determine role using instanceof checks + String role = switch (message) { + case UserMessage userMessage -> "user"; + case AiMessage aiMessage -> "assistant"; + case SystemMessage systemMessage -> "system"; + case ToolExecutionResultMessage toolExecutionResultMessage -> "user"; // Tool results come back as user messages in Anthropic format + + case null, default -> + // Default to user for unknown types + "user"; + }; + + if ("system".equals(role)) { + // Vertex AI handles system messages differently + // Skip for now, could be added to request root if needed + continue; + } + + msg.put("role", role); + + // Build content array + List> content = new ArrayList<>(); + + if (message instanceof ToolExecutionResultMessage toolResult) { + // Tool result - special format for Anthropic + Map toolResultContent = new HashMap<>(); + toolResultContent.put("type", "tool_result"); + toolResultContent.put("tool_use_id", toolResult.id()); + toolResultContent.put("content", toolResult.text() != null ? toolResult.text() : ""); + content.add(toolResultContent); + } + else if (message instanceof AiMessage aiMessage) { + // AI message - might have both text and tool calls + + // Add text content if present + if (aiMessage.text() != null && !aiMessage.text().isEmpty()) { + Map textContent = new HashMap<>(); + textContent.put("type", "text"); + textContent.put("text", aiMessage.text()); + content.add(textContent); + } + + // Add tool calls if present + if (aiMessage.hasToolExecutionRequests()) { + for (dev.langchain4j.agent.tool.ToolExecutionRequest toolRequest : aiMessage.toolExecutionRequests()) { + Map toolUseContent = new HashMap<>(); + toolUseContent.put("type", "tool_use"); + toolUseContent.put("id", toolRequest.id()); + toolUseContent.put("name", toolRequest.name()); + + // Parse arguments JSON back to object + try { + @SuppressWarnings("unchecked") + Map inputMap = objectMapper.readValue(toolRequest.arguments(), Map.class); + toolUseContent.put("input", inputMap); + } + catch (Exception e) { + LOG.warn("Failed to parse tool arguments: {}", e.getMessage()); + toolUseContent.put("input", new HashMap<>()); + } + + content.add(toolUseContent); + } + } + } + else { + // Regular user message + Map textContent = new HashMap<>(); + textContent.put("type", "text"); + + String text = ""; + if (message instanceof UserMessage) { + text = ((UserMessage) message).singleText(); + } + + textContent.put("text", text != null ? text : ""); + content.add(textContent); + } + + msg.put("content", content); + result.add(msg); + } + + return result; + } + + private List> convertTools(List toolSpecs) { + return toolSpecs.stream() + .map(tool -> { + Map toolMap = new HashMap<>(); + toolMap.put("name", tool.name()); + toolMap.put("description", tool.description()); + + // Build input_schema + Map inputSchema = new HashMap<>(); + inputSchema.put("type", "object"); + + if (tool.parameters() != null) { + try { + // Use reflection to call properties() method on JsonObjectSchema + java.lang.reflect.Method propertiesMethod = tool.parameters().getClass().getMethod("properties"); + @SuppressWarnings("unchecked") + Map schemaProperties = (Map) propertiesMethod.invoke(tool.parameters()); + + if (schemaProperties != null && !schemaProperties.isEmpty()) { + // Convert each schema element to a map + Map convertedProperties = new HashMap<>(); + for (Map.Entry entry : schemaProperties.entrySet()) { + Object schemaElement = entry.getValue(); + Map propertySchema = convertSchemaElement(schemaElement); + convertedProperties.put(entry.getKey(), propertySchema); + } + inputSchema.put("properties", convertedProperties); + } + else { + inputSchema.put("properties", new HashMap<>()); + } + + // Try to get required fields using reflection + try { + java.lang.reflect.Method requiredMethod = tool.parameters().getClass().getMethod("required"); + Object requiredFields = requiredMethod.invoke(tool.parameters()); + if (requiredFields != null) { + // Handle both List and Set (convert to List for JSON) + if (requiredFields instanceof List) { + @SuppressWarnings("unchecked") + List requiredList = (List) requiredFields; + if (!requiredList.isEmpty()) { + inputSchema.put("required", requiredList); + } + } + else if (requiredFields instanceof Set) { + @SuppressWarnings("unchecked") + Set requiredSet = (Set) requiredFields; + if (!requiredSet.isEmpty()) { + inputSchema.put("required", new ArrayList<>(requiredSet)); + } + } + } + } + catch (NoSuchMethodException e) { + // required() method not available, skip + } + + } + catch (Exception e) { + LOG.warn("Failed to convert tool parameters for {}: {}", tool.name(), e.getMessage()); + // Use basic empty properties + inputSchema.put("properties", new HashMap<>()); + } + } + else { + // No parameters - empty schema + inputSchema.put("properties", new HashMap<>()); + } + + toolMap.put("input_schema", inputSchema); + return toolMap; + }) + .collect(Collectors.toList()); + } + + private Map convertSchemaElement(Object schemaElement) { + Map schema = new HashMap<>(); + + if (schemaElement == null) { + return schema; + } + + String typeName = schemaElement.getClass().getSimpleName(); + + try { + // Try to get type using reflection + try { + java.lang.reflect.Method typeMethod = schemaElement.getClass().getMethod("type"); + Object typeObj = typeMethod.invoke(schemaElement); + if (typeObj != null) { + // Handle enum types (e.g., JsonSchemaType.STRING) + String typeStr = typeObj.toString(); + schema.put("type", typeStr.toLowerCase()); + } + } + catch (NoSuchMethodException e) { + // Some schema elements might not have a type() method + // Infer from class name + if (typeName.contains("String")) { + schema.put("type", "string"); + } + else if (typeName.contains("Integer")) { + schema.put("type", "integer"); + } + else if (typeName.contains("Number")) { + schema.put("type", "number"); + } + else if (typeName.contains("Boolean")) { + schema.put("type", "boolean"); + } + else if (typeName.contains("Array")) { + schema.put("type", "array"); + } + else if (typeName.contains("Object")) { + schema.put("type", "object"); + } + } + + // Try to get description + try { + java.lang.reflect.Method descMethod = schemaElement.getClass().getMethod("description"); + String description = (String) descMethod.invoke(schemaElement); + if (description != null && !description.isEmpty()) { + schema.put("description", description); + } + } + catch (NoSuchMethodException e) { + // description not available + } + + // For nested objects, recursively convert properties + if (typeName.equals("JsonObjectSchema")) { + try { + java.lang.reflect.Method propertiesMethod = schemaElement.getClass().getMethod("properties"); + @SuppressWarnings("unchecked") + Map nestedProperties = (Map) propertiesMethod.invoke(schemaElement); + + if (nestedProperties != null && !nestedProperties.isEmpty()) { + Map convertedNested = new HashMap<>(); + for (Map.Entry entry : nestedProperties.entrySet()) { + convertedNested.put(entry.getKey(), convertSchemaElement(entry.getValue())); + } + schema.put("properties", convertedNested); + } + + // Try to get required fields from nested object + try { + java.lang.reflect.Method requiredMethod = schemaElement.getClass().getMethod("required"); + Object requiredFields = requiredMethod.invoke(schemaElement); + if (requiredFields != null) { + if (requiredFields instanceof List) { + @SuppressWarnings("unchecked") + List requiredList = (List) requiredFields; + if (!requiredList.isEmpty()) { + schema.put("required", requiredList); + } + } + else if (requiredFields instanceof Set) { + @SuppressWarnings("unchecked") + Set requiredSet = (Set) requiredFields; + if (!requiredSet.isEmpty()) { + schema.put("required", new ArrayList<>(requiredSet)); + } + } + } + } + catch (NoSuchMethodException e) { + // required() method not available, skip + } + + } + catch (Exception e) { + LOG.debug("Could not extract nested properties from {}: {}", typeName, e.getMessage()); + } + } + + // For arrays, get items schema + if (typeName.equals("JsonArraySchema")) { + try { + java.lang.reflect.Method itemsMethod = schemaElement.getClass().getMethod("items"); + Object items = itemsMethod.invoke(schemaElement); + if (items != null) { + schema.put("items", convertSchemaElement(items)); + } + } + catch (Exception e) { + LOG.debug("Could not extract array items from {}: {}", typeName, e.getMessage()); + } + } + + // For enums, get enum values + if (typeName.equals("JsonEnumSchema")) { + try { + java.lang.reflect.Method enumValuesMethod = schemaElement.getClass().getMethod("enumValues"); + @SuppressWarnings("unchecked") + List enumValues = (List) enumValuesMethod.invoke(schemaElement); + if (enumValues != null && !enumValues.isEmpty()) { + schema.put("enum", enumValues); + } + } + catch (Exception e) { + LOG.debug("Could not extract enum values from {}: {}", typeName, e.getMessage()); + } + } + + } + catch (Exception e) { + LOG.debug("Error converting schema element {}: {}", typeName, e.getMessage()); + } + + return schema; + } + + private void makeStreamingRequest(String endpoint, Map requestBody, + StreamingChatResponseHandler handler) + throws IOException { + + URL url = URI.create(endpoint).toURL(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + try { + // Configure connection + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Authorization", "Bearer " + apiKey); + connection.setDoOutput(true); + connection.setConnectTimeout(30000); + connection.setReadTimeout(300000); // 5 minutes for streaming + + // Write request body + String jsonBody = objectMapper.writeValueAsString(requestBody); + try (OutputStream os = connection.getOutputStream()) { + byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + + // Check response code + int responseCode = connection.getResponseCode(); + if (responseCode != 200) { + String errorResponse = readErrorStream(connection); + throw new IOException("HTTP " + responseCode + ": " + errorResponse); + } + + // Read streaming response + handleStreamingResponse(connection, handler); + + } + finally { + connection.disconnect(); + } + } + + private String readErrorStream(HttpURLConnection connection) { + try (BufferedReader br = new BufferedReader( + new InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8))) { + return br.lines().collect(Collectors.joining("\n")); + } + catch (Exception e) { + return "Unable to read error stream"; + } + } + + private void handleStreamingResponse(HttpURLConnection connection, StreamingChatResponseHandler handler) + throws IOException { + StringBuilder fullText = new StringBuilder(); + List toolExecutionRequests = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + + // The response might be a single JSON object (non-streaming) or streamed + // Read the entire response first + String responseText = reader.lines().collect(Collectors.joining("\n")); + + if (logResponses) { + LOG.info("Response: {}", responseText); + } + + // Parse response + JsonNode responseNode = objectMapper.readTree(responseText); + + // Extract content + if (responseNode.has("content")) { + JsonNode contentArray = responseNode.get("content"); + for (JsonNode contentItem : contentArray) { + String contentType = contentItem.has("type") ? contentItem.get("type").asText() : ""; + + if ("text".equals(contentType)) { + String text = contentItem.get("text").asText(); + fullText.append(text); + + // Send as partial response (simulate streaming by sending chunks) + sendInChunks(text, handler); + } + else if ("tool_use".equals(contentType)) { + // Extract tool call + String toolId = contentItem.has("id") ? contentItem.get("id").asText() : ""; + String toolName = contentItem.has("name") ? contentItem.get("name").asText() : ""; + JsonNode inputNode = contentItem.has("input") ? contentItem.get("input") : null; + + // Convert input to JSON string + String arguments = inputNode != null ? objectMapper.writeValueAsString(inputNode) : "{}"; + + dev.langchain4j.agent.tool.ToolExecutionRequest toolRequest = dev.langchain4j.agent.tool.ToolExecutionRequest.builder() + .id(toolId) + .name(toolName) + .arguments(arguments) + .build(); + + toolExecutionRequests.add(toolRequest); + LOG.info("Tool execution requested: {} ({})", toolName, toolId); + } + } + } + + // Send tool execution requests if present + if (!toolExecutionRequests.isEmpty()) { + for (int i = 0; i < toolExecutionRequests.size(); i++) { + dev.langchain4j.agent.tool.ToolExecutionRequest toolRequest = toolExecutionRequests.get(i); + dev.langchain4j.model.chat.response.CompleteToolCall completeToolCall = new dev.langchain4j.model.chat.response.CompleteToolCall(i, toolRequest); + handler.onCompleteToolCall(completeToolCall); + } + } + + // Build complete response + TokenUsage tokenUsage = extractTokenUsage(responseNode); + + // Build AI message with text and tool execution requests + AiMessage.Builder aiMessageBuilder = AiMessage.builder(); + if (!fullText.isEmpty()) { + aiMessageBuilder.text(fullText.toString()); + } + if (!toolExecutionRequests.isEmpty()) { + aiMessageBuilder.toolExecutionRequests(toolExecutionRequests); + } + + ChatResponse chatResponse = ChatResponse.builder() + .aiMessage(aiMessageBuilder.build()) + .tokenUsage(tokenUsage) + .finishReason(extractFinishReason(responseNode)) + .build(); + + handler.onCompleteResponse(chatResponse); + + } + catch (Exception e) { + LOG.error("Error processing streaming response", e); + handler.onError(e); + } + } + + private void sendInChunks(String text, StreamingChatResponseHandler handler) { + // Split text into small chunks to simulate streaming + int chunkSize = 5; + for (int i = 0; i < text.length(); i += chunkSize) { + int end = Math.min(i + chunkSize, text.length()); + String chunk = text.substring(i, end); + try { + handler.onPartialResponse(chunk); + Thread.sleep(10); // Small delay to simulate streaming + } + catch (Exception e) { + LOG.warn("Error sending chunk", e); + } + } + } + + private TokenUsage extractTokenUsage(JsonNode responseNode) { + if (responseNode.has("usage")) { + JsonNode usage = responseNode.get("usage"); + int inputTokens = usage.has("input_tokens") ? usage.get("input_tokens").asInt() : 0; + int outputTokens = usage.has("output_tokens") ? usage.get("output_tokens").asInt() : 0; + return new TokenUsage(inputTokens, outputTokens); + } + return null; + } + + private FinishReason extractFinishReason(JsonNode responseNode) { + if (responseNode.has("stop_reason")) { + String stopReason = responseNode.get("stop_reason").asText(); + return switch (stopReason) { + case "end_turn" -> FinishReason.STOP; + case "max_tokens" -> FinishReason.LENGTH; + case "stop_sequence" -> FinishReason.STOP; + case "tool_use" -> FinishReason.TOOL_EXECUTION; + case null, default -> FinishReason.OTHER; + }; + } + return FinishReason.STOP; + } + + @Override + public List listeners() { + return Collections.emptyList(); + } + + @Override + public ModelProvider provider() { + return ModelProvider.GOOGLE_VERTEX_AI_ANTHROPIC; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String baseUrl; + private String apiKey; + private String modelName; + private Integer maxTokens; + private Double temperature; + private Boolean logRequests; + private Boolean logResponses; + private String systemPrompt; + + public Builder baseUrl(String baseUrl) { + this.baseUrl = baseUrl; + return this; + } + + public Builder apiKey(String apiKey) { + this.apiKey = apiKey; + return this; + } + + public Builder modelName(String modelName) { + this.modelName = modelName; + return this; + } + + public Builder maxTokens(Integer maxTokens) { + this.maxTokens = maxTokens; + return this; + } + + public Builder temperature(Double temperature) { + this.temperature = temperature; + return this; + } + + public Builder logRequests(Boolean logRequests) { + this.logRequests = logRequests; + return this; + } + + public Builder logResponses(Boolean logResponses) { + this.logResponses = logResponses; + return this; + } + + /** + * Set a system prompt to control Claude's behavior and output format. + * Common use cases: + * - HTML formatting: "Format responses using HTML tags (strong, em, ul, li, p). No markdown." + * - Plain text: "Reply in plain text without any formatting or markdown." + * - Custom instructions: "You are a helpful assistant specialized in..." + */ + public Builder systemPrompt(String systemPrompt) { + this.systemPrompt = systemPrompt; + return this; + } + + public CustomVertexAiAnthropicStreamingChatModel build() { + if (baseUrl == null || baseUrl.trim().isEmpty()) { + throw new IllegalArgumentException("baseUrl is required"); + } + if (apiKey == null || apiKey.trim().isEmpty()) { + throw new IllegalArgumentException("apiKey is required"); + } + if (modelName == null || modelName.trim().isEmpty()) { + throw new IllegalArgumentException("modelName is required"); + } + return new CustomVertexAiAnthropicStreamingChatModel(this); + } + } +} diff --git a/debezium-platform-conductor/src/main/resources/application.yml b/debezium-platform-conductor/src/main/resources/application.yml index 33c2746f..e73f8ba1 100644 --- a/debezium-platform-conductor/src/main/resources/application.yml +++ b/debezium-platform-conductor/src/main/resources/application.yml @@ -45,6 +45,10 @@ destinations: connection: timeout: 60 +# Context7 MCP integration +context7: + api-key: ${CONTEXT7_API_KEY:fake} + quarkus: hibernate-orm: mapping: @@ -73,6 +77,36 @@ quarkus: migrate-at-start: true swagger-ui: always-include: true + # LangChain4j Anthropic configuration + langchain4j: + anthropic: + api-key: ${CHAT_OPENAI_API_KEY:fake} + base-url: ${CHAT_OPENAI_BASE_URL:fake} + chat-model: + model-name: ${CHAT_OPENAI_MODEL_NAME:fake} + log-requests: ${CHAT_LOG_REQUESTS:false} + log-responses: ${CHAT_LOG_RESPONSES:false} + # MCP client configurations + mcp: + debezium-platform: + transport-type: streamable-http + url: http://localhost:8081/mcp + log-requests: ${MCP_LOG_REQUESTS:false} + log-responses: ${MCP_LOG_RESPONSES:false} + context7: + transport-type: streamable-http + url: https://mcp.context7.com/mcp + log-requests: ${MCP_LOG_REQUESTS:false} + log-responses: ${MCP_LOG_RESPONSES:false} + # MCP server schema generator configuration + mcp: + server: + schema-generator: + jakarta-validation: + enabled: true + not-nullable-field-is-required: true + not-nullable-method-is-required: true + include-pattern-expressions: true log: min-level: TRACE level: INFO diff --git a/debezium-platform-stage/package.json b/debezium-platform-stage/package.json index 91e28e07..34b462f7 100644 --- a/debezium-platform-stage/package.json +++ b/debezium-platform-stage/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@hookform/resolvers": "^5.2.2", + "@patternfly/chatbot": "^6.4.1", "@patternfly/patternfly": "^6.3.0", "@patternfly/react-charts": "^8.4.0", "@patternfly/react-code-editor": "^6.4.0", @@ -84,13 +85,13 @@ "chromatic": "^13.3.3", "cypress": "^15.7.1", "eslint": "^8.57.0", - "start-server-and-test": "^2.1.3", "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-storybook": "^9.0.7", "jsdom": "^24.1.0", "msw": "^2.11.6", + "start-server-and-test": "^2.1.3", "storybook": "^8.0.0", "typescript": "^5.2.2", "vite": "^5.4.21", diff --git a/debezium-platform-stage/src/App.tsx b/debezium-platform-stage/src/App.tsx index f66bae29..08462485 100644 --- a/debezium-platform-stage/src/App.tsx +++ b/debezium-platform-stage/src/App.tsx @@ -5,16 +5,19 @@ import { AppLayout } from "./appLayout/AppLayout"; import { AppRoutes } from "./AppRoutes"; import { AppContextProvider } from "./appLayout/AppContext"; import { NotificationProvider } from "./appLayout/AppNotificationContext"; +import { ChatbotProvider } from "./components/AIChatbot"; function App() { return ( - - - - + + + + + + ); diff --git a/debezium-platform-stage/src/appLayout/AppLayout.tsx b/debezium-platform-stage/src/appLayout/AppLayout.tsx index ddb2c0c0..0a3313fa 100644 --- a/debezium-platform-stage/src/appLayout/AppLayout.tsx +++ b/debezium-platform-stage/src/appLayout/AppLayout.tsx @@ -19,6 +19,7 @@ import { selectedDestinationAtom, selectedTransformAtom, } from "../pages/Pipeline/PipelineDesigner"; +import { AIChatbot } from "../components/AIChatbot"; interface IAppLayout { children: React.ReactNode; @@ -159,6 +160,7 @@ const AppLayout: React.FunctionComponent = ({ children }) => { > {alerts.slice(0, 3)} + ); }; diff --git a/debezium-platform-stage/src/components/AIChatbot/AIChatbot.css b/debezium-platform-stage/src/components/AIChatbot/AIChatbot.css new file mode 100644 index 00000000..ed3a0e97 --- /dev/null +++ b/debezium-platform-stage/src/components/AIChatbot/AIChatbot.css @@ -0,0 +1,328 @@ +/* Chatbot Toggle Button */ +.chatbot-toggle-button { + position: fixed; + bottom: 24px; + right: 24px; + z-index: 9999; +} + +.chatbot-fab { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + border-radius: 24px; + padding: 12px 20px; + font-size: 14px; + font-weight: 600; +} + +.chatbot-fab:hover { + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); + transform: translateY(-2px); + transition: all 0.2s ease; +} + +/* Chatbot Container */ +.chatbot-container { + position: fixed; + bottom: 24px; + right: 24px; + z-index: 9999; + width: 380px; + max-height: 600px; + animation: slideUp 0.3s ease-out; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.chatbot-card { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); + height: 600px; + display: flex; + flex-direction: column; +} + +.chatbot-card-body { + padding: 0; + height: 100%; + display: flex; + flex-direction: column; +} + +/* Header */ +.chatbot-header { + padding: 16px; + border-bottom: 1px solid var(--pf-v6-global--BorderColor--100); + background: linear-gradient(135deg, #0066cc 0%, #004080 100%); + color: black; + border-radius: 8px 8px 0 0; +} + +.chatbot-header .pf-v6-c-title { + color: black; + margin: 0; +} + +.chatbot-subtitle-container { + display: flex; + align-items: center; + gap: 12px; +} + +.chatbot-subtitle { + color: rgba(255, 255, 255, 0.8); + font-size: 12px; +} + +.chatbot-status { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 11px; + padding: 2px 8px; + border-radius: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.chatbot-status::before { + content: ''; + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; +} + +.chatbot-status-connected { + background-color: rgba(146, 212, 0, 0.2); + color: #92d400; +} + +.chatbot-status-connected::before { + background-color: #92d400; + animation: pulse 2s ease-in-out infinite; +} + +.chatbot-status-connecting { + background-color: rgba(240, 171, 0, 0.2); + color: #f0ab00; +} + +.chatbot-status-connecting::before { + background-color: #f0ab00; + animation: blink 1s ease-in-out infinite; +} + +.chatbot-status-error { + background-color: rgba(201, 25, 11, 0.2); + color: #c9190b; +} + +.chatbot-status-error::before { + background-color: #c9190b; +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +@keyframes blink { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.3; + } +} + +.chatbot-icon { + color: white; +} + +.chatbot-header .pf-v6-c-button { + color: black; +} + +.chatbot-header .pf-v6-c-button:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +/* Messages Container */ +.chatbot-messages { + padding: 16px; + overflow-y: auto; + flex: 1; + background-color: var(--pf-v6-global--BackgroundColor--200); + min-height: 0; +} + +.chatbot-messages::-webkit-scrollbar { + width: 6px; +} + +.chatbot-messages::-webkit-scrollbar-track { + background: transparent; +} + +.chatbot-messages::-webkit-scrollbar-thumb { + background: var(--pf-v6-global--BorderColor--200); + border-radius: 3px; +} + +.chatbot-messages::-webkit-scrollbar-thumb:hover { + background: var(--pf-v6-global--BorderColor--300); +} + +/* Message Bubbles */ +.chatbot-message { + padding: 12px; + border-radius: 12px; + max-width: 85%; + animation: fadeIn 0.3s ease-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.chatbot-message-bot { + background-color: white; + margin-right: auto; + border: 1px solid var(--pf-v6-global--BorderColor--100); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.chatbot-message-user { + background-color: var(--pf-v6-global--primary-color--100); + color: black; + margin-left: auto; + box-shadow: 0 1px 3px rgba(0, 102, 204, 0.3); +} + +.chatbot-message-header { + display: flex; + justify-content: space-between; + margin-bottom: 6px; + font-size: 11px; + opacity: 0.8; +} + +.chatbot-message-role { + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.chatbot-message-user .chatbot-message-role, +.chatbot-message-user .chatbot-message-time { + color: rgba(255, 255, 255, 0.9); +} + +.chatbot-message-content { + line-height: 1.5; + white-space: pre-wrap; + word-wrap: break-word; + font-size: 14px; +} + +/* Typing Indicator */ +.chatbot-typing-indicator { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 0; +} + +/* Input Container */ +.chatbot-input-container { + padding: 16px; + border-top: 1px solid var(--pf-v6-global--BorderColor--100); + background-color: white; + border-radius: 0 0 8px 8px; +} + +.chatbot-input-container .pf-v6-c-text-input-group { + width: 100%; +} + +.chatbot-input-container .pf-v6-c-form-control, +.chatbot-input-container .pf-v6-c-text-input-group__text-input, +.chatbot-input-container input[type="text"] { + color: var(--pf-v6-global--Color--100) !important; + background-color: white !important; + border: 1px solid var(--pf-v6-global--BorderColor--100); + font-size: 14px; + padding: 8px 12px; +} + +.chatbot-input-container .pf-v6-c-form-control:focus, +.chatbot-input-container .pf-v6-c-text-input-group__text-input:focus, +.chatbot-input-container input[type="text"]:focus { + color: var(--pf-v6-global--Color--100) !important; + border-color: var(--pf-v6-global--primary-color--100); + outline: none; + box-shadow: 0 0 0 1px var(--pf-v6-global--primary-color--100); +} + +.chatbot-input-container .pf-v6-c-form-control::placeholder, +.chatbot-input-container input[type="text"]::placeholder { + color: var(--pf-v6-global--Color--200); + opacity: 1; +} + +.chatbot-input-container .pf-v6-c-button { + height: 40px; + min-width: 40px; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .chatbot-container { + width: calc(100vw - 32px); + right: 16px; + bottom: 16px; + max-height: calc(100vh - 100px); + } + + .chatbot-card { + height: calc(100vh - 100px); + } + + .chatbot-toggle-button { + right: 16px; + bottom: 16px; + } +} + +/* Accessibility */ +.chatbot-message:focus { + outline: 2px solid var(--pf-v6-global--primary-color--100); + outline-offset: 2px; +} + +/* Smooth Transitions */ +.chatbot-card, +.chatbot-message, +.chatbot-fab { + transition: all 0.2s ease; +} diff --git a/debezium-platform-stage/src/components/AIChatbot/AIChatbot.tsx b/debezium-platform-stage/src/components/AIChatbot/AIChatbot.tsx new file mode 100644 index 00000000..f9cd19e7 --- /dev/null +++ b/debezium-platform-stage/src/components/AIChatbot/AIChatbot.tsx @@ -0,0 +1,214 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { + Button, + TextInput, + Stack, + StackItem, + Card, + CardBody, + Title, + Split, + SplitItem, + Icon, + Spinner, +} from '@patternfly/react-core'; +import { + CommentIcon, + TimesIcon, + PaperPlaneIcon, + RobotIcon, + TrashIcon, +} from '@patternfly/react-icons'; +import { useChatbot } from './AIChatbotContext'; +import './AIChatbot.css'; + +const AIChatbot: React.FC = () => { + const { isOpen, messages, isLoading, connectionStatus, toggleChatbot, sendMessage, clearMessages } = useChatbot(); + const [inputValue, setInputValue] = useState(''); + const messagesEndRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const handleSendMessage = async () => { + if (inputValue.trim() && connectionStatus === 'connected') { + await sendMessage(inputValue); + setInputValue(''); + } + }; + + const getConnectionStatusText = () => { + switch (connectionStatus) { + case 'connecting': + return 'Connecting...'; + case 'connected': + return 'Connected'; + case 'disconnected': + return 'Disconnected'; + case 'error': + return 'Connection Error'; + default: + return ''; + } + }; + + const getConnectionStatusClass = () => { + switch (connectionStatus) { + case 'connecting': + return 'chatbot-status-connecting'; + case 'connected': + return 'chatbot-status-connected'; + case 'disconnected': + case 'error': + return 'chatbot-status-error'; + default: + return ''; + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + if (!isOpen) { + return ( +

+ +
+ ); + } + + return ( +
+ + + + {/* Header */} + + + + + + + + + + AI Assistant + +
+ + Powered by Debezium Platform + + + {getConnectionStatusText()} + +
+
+ +
+ ); +}; + +export default AIChatbot; diff --git a/debezium-platform-stage/src/components/AIChatbot/AIChatbotContext.tsx b/debezium-platform-stage/src/components/AIChatbot/AIChatbotContext.tsx new file mode 100644 index 00000000..13600e8e --- /dev/null +++ b/debezium-platform-stage/src/components/AIChatbot/AIChatbotContext.tsx @@ -0,0 +1,256 @@ +import React, { createContext, useContext, useState, ReactNode, useEffect, useRef, useCallback } from 'react'; +import { getBackendUrl } from '../../config'; + +export interface Message { + id: string; + role: 'user' | 'bot'; + content: string; + timestamp: Date; +} + +export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error'; + +interface ChatbotContextType { + isOpen: boolean; + messages: Message[]; + isLoading: boolean; + connectionStatus: ConnectionStatus; + toggleChatbot: () => void; + sendMessage: (message: string) => Promise; + clearMessages: () => void; +} + +const ChatbotContext = createContext(undefined); + +// Convert HTTP URL to WebSocket URL +const getWebSocketUrl = (): string => { + const backendUrl = getBackendUrl(); + const wsUrl = backendUrl.replace(/^http/, 'ws'); + return `${wsUrl}/api/chat`; +}; + +export const ChatbotProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [isOpen, setIsOpen] = useState(false); + const [messages, setMessages] = useState([ + { + id: '1', + role: 'bot', + content: 'Hello! I\'m your Debezium Platform assistant. How can I help you today?', + timestamp: new Date(), + }, + ]); + const [isLoading, setIsLoading] = useState(false); + const [connectionStatus, setConnectionStatus] = useState('disconnected'); + + const wsRef = useRef(null); + const reconnectTimeoutRef = useRef(null); + const reconnectAttemptsRef = useRef(0); + const maxReconnectAttempts = 5; + + // Connect to WebSocket + const connectWebSocket = useCallback(() => { + if (wsRef.current?.readyState === WebSocket.OPEN || + wsRef.current?.readyState === WebSocket.CONNECTING) { + return; // Already connected or connecting + } + + try { + setConnectionStatus('connecting'); + const wsUrl = getWebSocketUrl(); + console.log('Connecting to WebSocket:', wsUrl); + + const ws = new WebSocket(wsUrl); + wsRef.current = ws; + + ws.onopen = () => { + console.log('WebSocket connected'); + setConnectionStatus('connected'); + reconnectAttemptsRef.current = 0; // Reset reconnect attempts on successful connection + }; + + ws.onmessage = (event) => { + console.log('Received message:', event.data); + try { + const data = JSON.parse(event.data); + + // Handle bot response + const botMessage: Message = { + id: Date.now().toString(), + role: 'bot', + content: data.message || data.content || data.response || event.data, + timestamp: new Date(), + }; + + setMessages((prev) => [...prev, botMessage]); + setIsLoading(false); + } catch (error) { + // If not JSON, treat as plain text + const botMessage: Message = { + id: Date.now().toString(), + role: 'bot', + content: event.data, + timestamp: new Date(), + }; + + setMessages((prev) => [...prev, botMessage]); + setIsLoading(false); + } + }; + + ws.onerror = (error) => { + console.error('WebSocket error:', error); + setConnectionStatus('error'); + setIsLoading(false); + + // Add error message to chat + const errorMessage: Message = { + id: Date.now().toString(), + role: 'bot', + content: 'Connection error occurred. Attempting to reconnect...', + timestamp: new Date(), + }; + setMessages((prev) => [...prev, errorMessage]); + }; + + ws.onclose = (event) => { + console.log('WebSocket closed:', event.code, event.reason); + setConnectionStatus('disconnected'); + setIsLoading(false); + + // Attempt to reconnect with exponential backoff + if (reconnectAttemptsRef.current < maxReconnectAttempts) { + const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 30000); + console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttemptsRef.current + 1}/${maxReconnectAttempts})`); + + reconnectTimeoutRef.current = setTimeout(() => { + reconnectAttemptsRef.current += 1; + connectWebSocket(); + }, delay); + } else { + const errorMessage: Message = { + id: Date.now().toString(), + role: 'bot', + content: 'Unable to connect to the AI assistant. Please check your connection and try again later.', + timestamp: new Date(), + }; + setMessages((prev) => [...prev, errorMessage]); + } + }; + } catch (error) { + console.error('Error creating WebSocket:', error); + setConnectionStatus('error'); + } + }, []); + + // Disconnect WebSocket + const disconnectWebSocket = useCallback(() => { + if (reconnectTimeoutRef.current) { + clearTimeout(reconnectTimeoutRef.current); + reconnectTimeoutRef.current = null; + } + + if (wsRef.current) { + wsRef.current.close(); + wsRef.current = null; + } + + setConnectionStatus('disconnected'); + }, []); + + // Connect when chatbot opens, disconnect when it closes + useEffect(() => { + if (isOpen) { + connectWebSocket(); + } else { + disconnectWebSocket(); + } + + return () => { + disconnectWebSocket(); + }; + }, [isOpen, connectWebSocket, disconnectWebSocket]); + + const toggleChatbot = () => { + setIsOpen(!isOpen); + }; + + const sendMessage = async (messageContent: string) => { + const userMessage: Message = { + id: Date.now().toString(), + role: 'user', + content: messageContent, + timestamp: new Date(), + }; + + setMessages((prev) => [...prev, userMessage]); + setIsLoading(true); + + try { + // Check WebSocket connection + if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) { + throw new Error('WebSocket is not connected'); + } + + // Send message through WebSocket + const payload = JSON.stringify({ + message: messageContent, + timestamp: new Date().toISOString(), + }); + + wsRef.current.send(payload); + console.log('Message sent:', payload); + + } catch (error) { + console.error('Error sending message:', error); + setIsLoading(false); + + const errorMessage: Message = { + id: (Date.now() + 1).toString(), + role: 'bot', + content: 'Sorry, I couldn\'t send your message. Please check the connection and try again.', + timestamp: new Date(), + }; + setMessages((prev) => [...prev, errorMessage]); + + // Attempt to reconnect + if (connectionStatus !== 'connecting') { + connectWebSocket(); + } + } + }; + + const clearMessages = () => { + setMessages([ + { + id: '1', + role: 'bot', + content: 'Hello! I\'m your Debezium Platform assistant. How can I help you today?', + timestamp: new Date(), + }, + ]); + }; + + return ( + + {children} + + ); +}; + +export const useChatbot = () => { + const context = useContext(ChatbotContext); + if (context === undefined) { + throw new Error('useChatbot must be used within a ChatbotProvider'); + } + return context; +}; diff --git a/debezium-platform-stage/src/components/AIChatbot/index.ts b/debezium-platform-stage/src/components/AIChatbot/index.ts new file mode 100644 index 00000000..4937b351 --- /dev/null +++ b/debezium-platform-stage/src/components/AIChatbot/index.ts @@ -0,0 +1,3 @@ +export { default as AIChatbot } from './AIChatbot'; +export { ChatbotProvider, useChatbot } from './AIChatbotContext'; +export type { Message, ConnectionStatus } from './AIChatbotContext'; diff --git a/debezium-platform-stage/yarn.lock b/debezium-platform-stage/yarn.lock index 67b48b42..fd1851bd 100644 --- a/debezium-platform-stage/yarn.lock +++ b/debezium-platform-stage/yarn.lock @@ -742,6 +742,18 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@lukeed/csprng@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" + integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== + +"@lukeed/uuid@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@lukeed/uuid/-/uuid-2.0.1.tgz#4f6c34259ee0982a455e1797d56ac27bb040fd74" + integrity sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w== + dependencies: + "@lukeed/csprng" "^1.1.0" + "@mdx-js/react@^3.0.0": version "3.1.1" resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.1.1.tgz#24bda7fffceb2fe256f954482123cda1be5f5fef" @@ -814,6 +826,26 @@ resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda" integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== +"@patternfly/chatbot@^6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@patternfly/chatbot/-/chatbot-6.4.1.tgz#41861bd2752e27f315d233fcb8d65e415bddf634" + integrity sha512-59Ct4uzuPjf07FW7cTU6sJP8Qpr9Sg5VM0ITEw3RjONK+b1v0gvo3F/g8ZtetGM4Rvhv1C/6V4aGH8peuQswhQ== + dependencies: + "@patternfly/react-code-editor" "^6.1.0" + "@patternfly/react-core" "^6.1.0" + "@patternfly/react-icons" "^6.1.0" + "@patternfly/react-table" "^6.1.0" + "@segment/analytics-next" "^1.76.0" + clsx "^2.1.0" + path-browserify "^1.0.1" + posthog-js "^1.194.4" + react-markdown "^9.0.1" + rehype-external-links "^3.0.0" + rehype-sanitize "^6.0.0" + rehype-unwrap-images "^1.0.0" + remark-gfm "^4.0.0" + unist-util-visit "^5.0.0" + "@patternfly/patternfly@^6.3.0": version "6.4.0" resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-6.4.0.tgz#8ccc792573a525440f828ca1fb11eade864afb4d" @@ -830,7 +862,7 @@ lodash "^4.17.21" tslib "^2.8.1" -"@patternfly/react-code-editor@^6.4.0": +"@patternfly/react-code-editor@^6.1.0", "@patternfly/react-code-editor@^6.4.0": version "6.4.0" resolved "https://registry.yarnpkg.com/@patternfly/react-code-editor/-/react-code-editor-6.4.0.tgz#a1d8b4302d29107391bc5b3286445b1762c5bc0e" integrity sha512-jZK4qzytcriZalug3KvwTD+h8lNjUfm79opAsLJwGfuq2DD5pzrDwF4GysZrR4hUMdN0jUJqh5yvAqwZtJI/UA== @@ -853,7 +885,7 @@ "@patternfly/react-table" "^6.0.0" react-jss "^10.10.0" -"@patternfly/react-core@^6.0.0", "@patternfly/react-core@^6.3.0", "@patternfly/react-core@^6.4.0": +"@patternfly/react-core@^6.0.0", "@patternfly/react-core@^6.1.0", "@patternfly/react-core@^6.3.0", "@patternfly/react-core@^6.4.0": version "6.4.0" resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-6.4.0.tgz#a2c0b60c1a4f34f708d079e92e86b007b0c88c3c" integrity sha512-zMgJmcFohp2FqgAoZHg7EXZS7gnaFESquk0qIavemYI0FsqspVlzV2/PUru7w+86+jXfqebRhgubPRsv1eJwEg== @@ -878,7 +910,7 @@ "@patternfly/react-styles" "^6.4.0" resize-observer-polyfill "^1.5.1" -"@patternfly/react-icons@^6.0.0", "@patternfly/react-icons@^6.3.0", "@patternfly/react-icons@^6.4.0": +"@patternfly/react-icons@^6.0.0", "@patternfly/react-icons@^6.1.0", "@patternfly/react-icons@^6.3.0", "@patternfly/react-icons@^6.4.0": version "6.4.0" resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-6.4.0.tgz#563d5a51e76084dea8c752b587fc479dfe10fc31" integrity sha512-SPjzatm73NUYv/BL6A/cjRA5sFQ15NkiyPAcT8gmklI7HY+ptd6/eg49uBDFmxTQcSwbb5ISW/R6wwCQBY2M+Q== @@ -898,7 +930,7 @@ resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-6.4.0.tgz#2e29d59a4e3df737e742de58dc1525a27287415d" integrity sha512-EXmHA67s5sy+Wy/0uxWoUQ52jr9lsH2wV3QcgtvVc5zxpyBX89gShpqv4jfVqaowznHGDoL6fVBBrSe9BYOliQ== -"@patternfly/react-table@^6.0.0", "@patternfly/react-table@^6.4.0": +"@patternfly/react-table@^6.0.0", "@patternfly/react-table@^6.1.0", "@patternfly/react-table@^6.4.0": version "6.4.0" resolved "https://registry.yarnpkg.com/@patternfly/react-table/-/react-table-6.4.0.tgz#5bfa35f97000920749e38bbdd0a4108679860877" integrity sha512-yv0sFOLGts8a2q9C1xUegjp50ayYyVRe0wKjMf+aMSNIK8sVYu8qu0yfBsCDybsUCldue7+qsYKRLFZosTllWQ== @@ -939,6 +971,18 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@posthog/core@1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@posthog/core/-/core-1.9.1.tgz#bcc5392359b8413c2de19181c081fcc0475ab784" + integrity sha512-kRb1ch2dhQjsAapZmu6V66551IF2LnCbc1rnrQqnR7ArooVyJN9KOPXre16AJ3ObJz2eTfuP7x25BMyS2Y5Exw== + dependencies: + cross-spawn "^7.0.6" + +"@posthog/types@1.318.2": + version "1.318.2" + resolved "https://registry.yarnpkg.com/@posthog/types/-/types-1.318.2.tgz#a6b3dd0abd51789666d8348b7e9e7209bf055d3a" + integrity sha512-/4m1DStpyaZStTJJ4b7RyftnFoSM/IWmVBTiWvdm2e2I8A8W3D4+YY7YkTpPtTinRZEhDQEZFjhlHdDgNCiNuA== + "@reactflow/background@11.3.14": version "11.3.14" resolved "https://registry.yarnpkg.com/@reactflow/background/-/background-11.3.14.tgz#778ca30174f3de77fc321459ab3789e66e71a699" @@ -1134,6 +1178,76 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz#38ae84f4c04226c1d56a3b17296ef1e0460ecdfe" integrity sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ== +"@segment/analytics-core@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@segment/analytics-core/-/analytics-core-1.8.2.tgz#f8aea312af3655e6483d0bdc160f0e05484529ab" + integrity sha512-5FDy6l8chpzUfJcNlIcyqYQq4+JTUynlVoCeCUuVz+l+6W0PXg+ljKp34R4yLVCcY5VVZohuW+HH0VLWdwYVAg== + dependencies: + "@lukeed/uuid" "^2.0.0" + "@segment/analytics-generic-utils" "1.2.0" + dset "^3.1.4" + tslib "^2.4.1" + +"@segment/analytics-generic-utils@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@segment/analytics-generic-utils/-/analytics-generic-utils-1.2.0.tgz#9232162d6dbcd18501813fdff18035ce48fd24bf" + integrity sha512-DfnW6mW3YQOLlDQQdR89k4EqfHb0g/3XvBXkovH1FstUN93eL1kfW9CsDcVQyH3bAC5ZsFyjA/o/1Q2j0QeoWw== + dependencies: + tslib "^2.4.1" + +"@segment/analytics-next@^1.76.0": + version "1.81.1" + resolved "https://registry.yarnpkg.com/@segment/analytics-next/-/analytics-next-1.81.1.tgz#1e98c44090c95c131c249aaf8ad48aa4afb964e1" + integrity sha512-nVHNhriViptjkp4004qBbvmnW9/3brjhBv2P5Cvcg7iWW41fBzUY8FbLi+8wkmSbycT9fH1pTM3TUxB6+h0Fcg== + dependencies: + "@lukeed/uuid" "^2.0.0" + "@segment/analytics-core" "1.8.2" + "@segment/analytics-generic-utils" "1.2.0" + "@segment/analytics-page-tools" "1.0.0" + "@segment/analytics.js-video-plugins" "^0.2.1" + "@segment/facade" "^3.4.9" + dset "^3.1.4" + js-cookie "3.0.1" + node-fetch "^2.6.7" + tslib "^2.4.1" + unfetch "^4.1.0" + +"@segment/analytics-page-tools@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@segment/analytics-page-tools/-/analytics-page-tools-1.0.0.tgz#f5fb9b8f0c1e80e821b0a77ca13ac53daf08a28a" + integrity sha512-o9OVB91qLB9qb0Bw1HfjmWm5AnrMNULRjx++4lBqLt8InKRX1urrRBparVlpj+yJA0sckN5ZcsfazRLuPgBYDQ== + dependencies: + tslib "^2.4.1" + +"@segment/analytics.js-video-plugins@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@segment/analytics.js-video-plugins/-/analytics.js-video-plugins-0.2.1.tgz#3596fa3887dcd9df5978dc566edf4a0aea2a9b1e" + integrity sha512-lZwCyEXT4aaHBLNK433okEKdxGAuyrVmop4BpQqQSJuRz0DglPZgd9B/XjiiWs1UyOankg2aNYMN3VcS8t4eSQ== + dependencies: + unfetch "^3.1.1" + +"@segment/facade@^3.4.9": + version "3.4.10" + resolved "https://registry.yarnpkg.com/@segment/facade/-/facade-3.4.10.tgz#118fab29cf2250d3128f9b2a16d6ec76f86e3710" + integrity sha512-xVQBbB/lNvk/u8+ey0kC/+g8pT3l0gCT8O2y9Z+StMMn3KAFAQ9w8xfgef67tJybktOKKU7pQGRPolRM1i1pdA== + dependencies: + "@segment/isodate-traverse" "^1.1.1" + inherits "^2.0.4" + new-date "^1.0.3" + obj-case "0.2.1" + +"@segment/isodate-traverse@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@segment/isodate-traverse/-/isodate-traverse-1.1.1.tgz#37e1a68b5e48a841260145f1be86d342995dfc64" + integrity sha512-+G6e1SgAUkcq0EDMi+SRLfT48TNlLPF3QnSgFGVs0V9F3o3fq/woQ2rHFlW20W0yy5NnCUH0QGU3Am2rZy/E3w== + dependencies: + "@segment/isodate" "^1.0.3" + +"@segment/isodate@1.0.3", "@segment/isodate@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@segment/isodate/-/isodate-1.0.3.tgz#f44e8202d5edd277ce822785239474b2c9411d4a" + integrity sha512-BtanDuvJqnACFkeeYje7pWULVv8RgZaqKHWwGFnL/g/TH/CcZjkIVTfGDp/MAxmilYHUkrX70SqwnYSTNEaN7A== + "@standard-schema/spec@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c" @@ -1804,6 +1918,13 @@ "@types/d3-transition" "*" "@types/d3-zoom" "*" +"@types/debug@^4.0.0": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + "@types/deep-eql@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" @@ -1814,7 +1935,14 @@ resolved "https://registry.yarnpkg.com/@types/doctrine/-/doctrine-0.0.9.tgz#d86a5f452a15e3e3113b99e39616a9baa0f9863f" integrity sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA== -"@types/estree@1.0.8", "@types/estree@^1.0.0": +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== @@ -1824,16 +1952,35 @@ resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a" integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + "@types/lodash@^4.17.20": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.21.tgz#b806831543d696b14f8112db600ea9d3a1df6ea4" integrity sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ== +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + "@types/mdx@^2.0.0": version "2.0.13" resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.13.tgz#68f6877043d377092890ff5b298152b0a21671bd" integrity sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw== +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + "@types/node@*": version "24.10.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.1.tgz#91e92182c93db8bd6224fca031e2370cef9a8f01" @@ -1878,6 +2025,21 @@ resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.6.tgz#d785ee90c52d7cc020e249c948c36f7b32d1e217" integrity sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA== +"@types/trusted-types@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + "@types/uuid@^9.0.1": version "9.0.8" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" @@ -2047,7 +2209,7 @@ "@typescript-eslint/types" "8.49.0" eslint-visitor-keys "^4.2.1" -"@ungap/structured-clone@^1.2.0": +"@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== @@ -2463,6 +2625,11 @@ axios@^1.12.2, axios@^1.13.2: form-data "^4.0.4" proxy-from-env "^1.1.0" +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -2611,6 +2778,11 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + chai@^5.1.1: version "5.3.3" resolved "https://registry.yarnpkg.com/chai/-/chai-5.3.3.tgz#dd3da955e270916a4bd3f625f4b919996ada7e06" @@ -2643,6 +2815,26 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + check-error@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" @@ -2716,6 +2908,11 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clsx@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -2745,6 +2942,11 @@ combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + commander@7: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" @@ -2775,6 +2977,11 @@ cookie@^1.0.1, cookie@^1.0.2: resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== +core-js@^3.38.1: + version "3.47.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.47.0.tgz#436ef07650e191afeb84c24481b298bd60eb4a17" + integrity sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -3210,7 +3417,7 @@ dayjs@^1.10.4: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.19.tgz#15dc98e854bb43917f12021806af897c58ae2938" integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw== -debug@4, debug@4.4.3, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@4.4.3, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -3229,6 +3436,13 @@ decimal.js@^10.4.3: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== +decode-named-character-reference@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz#25c32ae6dd5e21889549d40f676030e9514cc0ed" + integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== + dependencies: + character-entities "^2.0.0" + deep-eql@^5.0.1: version "5.0.2" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" @@ -3286,7 +3500,7 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -dequal@^2.0.2, dequal@^2.0.3: +dequal@^2.0.0, dequal@^2.0.2, dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== @@ -3296,6 +3510,13 @@ detect-node@^2.0.4, detect-node@^2.1.0: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -3320,6 +3541,18 @@ dom-accessibility-api@^0.6.3: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== +dompurify@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.3.1.tgz#c7e1ddebfe3301eacd6c0c12a4af284936dbbb86" + integrity sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q== + optionalDependencies: + "@types/trusted-types" "^2.0.7" + +dset@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.4.tgz#f8eaf5f023f068a036d08cd07dc9ffb7d0065248" + integrity sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA== + dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -3585,6 +3818,11 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + eslint-plugin-react-hooks@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3" @@ -3723,6 +3961,11 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" @@ -3800,7 +4043,7 @@ expect-type@^1.2.2: resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.2.tgz#c030a329fb61184126c8447585bc75a7ec6fbff3" integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA== -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -3865,6 +4108,11 @@ fdir@^6.5.0: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== +fflate@^0.4.8: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -4199,6 +4447,65 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hast-util-has-property@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz#4e595e3cddb8ce530ea92f6fc4111a818d8e7f93" + integrity sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-interactive@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-interactive/-/hast-util-interactive-3.0.0.tgz#f5d42eb52320bc42214c3210b30fe8d21eb17b04" + integrity sha512-9VFa3kP6AT40BNYcPmn3jpsG+1KPDF0rUFCrFVQDUsuUXZ3YLODm8UGV0tmYzFpcOIQXTAOi2ccS3ywlj2dQTA== + dependencies: + "@types/hast" "^3.0.0" + hast-util-has-property "^3.0.0" + +hast-util-is-element@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz#6e31a6532c217e5b533848c7e52c9d9369ca0932" + integrity sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-sanitize@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz#edb260d94e5bba2030eb9375790a8753e5bf391f" + integrity sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg== + dependencies: + "@types/hast" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + unist-util-position "^5.0.0" + +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.6" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + headers-polyfill@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.3.tgz#922a0155de30ecc1f785bcf04be77844ca95ad07" @@ -4230,6 +4537,11 @@ html-parse-stringify@^3.0.1: dependencies: void-elements "3.1.0" +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== + http-proxy-agent@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" @@ -4339,7 +4651,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4349,6 +4661,11 @@ ini@2.0.0: resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== +inline-style-parser@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909" + integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== + internal-slot@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" @@ -4363,6 +4680,24 @@ internal-slot@^1.1.0: resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== +is-absolute-url@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-4.0.1.tgz#16e4d487d4fded05cfe0685e53ec86804a5e94dc" + integrity sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A== + +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + is-arguments@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b" @@ -4435,6 +4770,11 @@ is-date-object@^1.0.5, is-date-object@^1.1.0: call-bound "^1.0.2" has-tostringtag "^1.0.2" +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -4475,6 +4815,11 @@ is-glob@^4.0.0, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + is-in-browser@^1.0.2, is-in-browser@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" @@ -4516,6 +4861,11 @@ is-path-inside@^3.0.2, is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" @@ -4694,6 +5044,11 @@ jotai@^2.15.1: resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.15.2.tgz#c66438eb7781879ae1d5957561bf09dbc5c6a763" integrity sha512-El86CCfXNMEOytp20NPfppqGGmcp6H6kIA+tJHdmASEUURJCYW4fh8nTHEnB8rUXEFAY1pm8PdHPwnrcPGwdEg== +js-cookie@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414" + integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw== + js-sha3@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" @@ -5030,6 +5385,11 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -5099,6 +5459,11 @@ map-stream@~0.1.0: resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" integrity sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g== +markdown-table@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a" + integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw== + match-sorter@^6.0.2: version "6.4.0" resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.4.0.tgz#ae9c166cb3c9efd337690b3160c0e28cb8377c13" @@ -5112,6 +5477,186 @@ math-intrinsics@^1.1.0: resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== +mdast-util-find-and-replace@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz#70a3174c894e14df722abf43bc250cbae44b11df" + integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== + dependencies: + "@types/mdast" "^4.0.0" + escape-string-regexp "^5.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +mdast-util-from-markdown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-gfm-autolink-literal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz#abd557630337bd30a6d5a4bd8252e1c2dc0875d5" + integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ== + dependencies: + "@types/mdast" "^4.0.0" + ccount "^2.0.0" + devlop "^1.0.0" + mdast-util-find-and-replace "^3.0.0" + micromark-util-character "^2.0.0" + +mdast-util-gfm-footnote@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz#7778e9d9ca3df7238cc2bd3fa2b1bf6a65b19403" + integrity sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + +mdast-util-gfm-strikethrough@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16" + integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38" + integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + markdown-table "^3.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-task-list-item@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936" + integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz#2cdf63b92c2a331406b0fb0db4c077c1b0331751" + integrity sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-gfm-autolink-literal "^2.0.0" + mdast-util-gfm-footnote "^2.0.0" + mdast-util-gfm-strikethrough "^2.0.0" + mdast-util-gfm-table "^2.0.0" + mdast-util-gfm-task-list-item "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.2.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" + integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + memoize-one@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" @@ -5129,6 +5674,279 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +micromark-core-commonmark@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" + integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-autolink-literal@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935" + integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-footnote@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750" + integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== + dependencies: + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-strikethrough@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz#86106df8b3a692b5f6a92280d3879be6be46d923" + integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-table@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz#fac70bcbf51fe65f5f44033118d39be8a9b5940b" + integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-tagfilter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57" + integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-extension-gfm-task-list-item@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz#bcc34d805639829990ec175c3eea12bb5b781f2c" + integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b" + integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w== + dependencies: + micromark-extension-gfm-autolink-literal "^2.0.0" + micromark-extension-gfm-footnote "^2.0.0" + micromark-extension-gfm-strikethrough "^2.0.0" + micromark-extension-gfm-table "^2.0.0" + micromark-extension-gfm-tagfilter "^2.0.0" + micromark-extension-gfm-task-list-item "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== + +micromark@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" + integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + microseconds@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" @@ -5248,7 +6066,14 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-fetch@^2.6.12: +new-date@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/new-date/-/new-date-1.0.3.tgz#a5956086d3f5ed43d0b210d87a10219ccb7a2326" + integrity sha512-0fsVvQPbo2I18DT2zVHpezmeeNYV2JaJSrseiHLc17GNOxJzUdx5mvSigPu8LtIfZSij5i1wXnXFspEs2CD6hA== + dependencies: + "@segment/isodate" "1.0.3" + +node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -5272,6 +6097,11 @@ nwsapi@^2.2.12: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.22.tgz#109f9530cda6c156d6a713cdf5939e9f0de98b9d" integrity sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ== +obj-case@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/obj-case/-/obj-case-0.2.1.tgz#13a554d04e5ca32dfd9d566451fd2b0e11007f1a" + integrity sha512-PquYBBTy+Y6Ob/O2574XHhDtHJlV1cJHMCgW+rDRc9J5hhmRelJB3k5dTK/3cVmFVtzvAKuENeuLpoyTzMzkOg== + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -5426,6 +6256,19 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== + dependencies: + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + parse5@^7.1.2: version "7.3.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" @@ -5433,6 +6276,11 @@ parse5@^7.1.2: dependencies: entities "^6.0.0" +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -5539,6 +6387,25 @@ postcss@^8.4.43, postcss@^8.5.6: picocolors "^1.1.1" source-map-js "^1.2.1" +posthog-js@^1.194.4: + version "1.318.2" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.318.2.tgz#dd18312b738aaaf6169ac3f9d40e09b2ffc3b019" + integrity sha512-M5C/3HYlyNaqmCIwgGkmJLcGt5MREwsllwGLAOO3nMYhrWSjEuJnEzenplHxa/4UgwvhR4121ErzRHgK67qq1A== + dependencies: + "@posthog/core" "1.9.1" + "@posthog/types" "1.318.2" + core-js "^3.38.1" + dompurify "^3.3.1" + fflate "^0.4.8" + preact "^10.19.3" + query-selector-shadow-dom "^1.0.1" + web-vitals "^4.2.4" + +preact@^10.19.3: + version "10.28.2" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.28.2.tgz#4b668383afa4b4a2546bbe4bd1747e02e2360138" + integrity sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -5577,6 +6444,11 @@ property-expr@^2.0.5: resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== +property-information@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d" + integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== + proxy-from-env@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" @@ -5621,6 +6493,11 @@ qs@6.14.0: dependencies: side-channel "^1.1.0" +query-selector-shadow-dom@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz#1c7b0058eff4881ac44f45d8f84ede32e9a2f349" + integrity sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw== + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -5740,6 +6617,23 @@ react-jss@^10.10.0: theming "^3.3.0" tiny-warning "^1.0.2" +react-markdown@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-9.1.0.tgz#606bd74c6af131ba382a7c1282ff506708ed2e26" + integrity sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + react-query@^3.39.3: version "3.39.3" resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35" @@ -5831,6 +6725,78 @@ regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: gopd "^1.2.0" set-function-name "^2.0.2" +rehype-external-links@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/rehype-external-links/-/rehype-external-links-3.0.0.tgz#2b28b5cda1932f83f045b6f80a3e1b15f168c6f6" + integrity sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw== + dependencies: + "@types/hast" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + hast-util-is-element "^3.0.0" + is-absolute-url "^4.0.0" + space-separated-tokens "^2.0.0" + unist-util-visit "^5.0.0" + +rehype-sanitize@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz#16e95f4a67a69cbf0f79e113c8e0df48203db73c" + integrity sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg== + dependencies: + "@types/hast" "^3.0.0" + hast-util-sanitize "^5.0.0" + +rehype-unwrap-images@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rehype-unwrap-images/-/rehype-unwrap-images-1.0.0.tgz#7e6ae5bddffdf7ad439cc2e4ca1820e85a031a79" + integrity sha512-wzW5Mk9IlVF2UwXC5NtIZsx1aHYbV8+bLWjJnlZaaamz5QU52RppWtq1uEZJqGo8d9Y4RuDqidB6r9RFpKugIg== + dependencies: + "@types/hast" "^3.0.0" + hast-util-interactive "^3.0.0" + hast-util-whitespace "^3.0.0" + unist-util-visit "^5.0.0" + +remark-gfm@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b" + integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-gfm "^3.0.0" + micromark-extension-gfm "^3.0.0" + remark-parse "^11.0.0" + remark-stringify "^11.0.0" + unified "^11.0.0" + +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^11.0.0: + version "11.1.2" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37" + integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +remark-stringify@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3" + integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-to-markdown "^2.0.0" + unified "^11.0.0" + remove-accents@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687" @@ -6177,6 +7143,11 @@ source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + split@0.3: version "0.3.3" resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" @@ -6328,6 +7299,14 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -6369,6 +7348,20 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +style-to-js@^1.0.0: + version "1.1.21" + resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.21.tgz#2908941187f857e79e28e9cd78008b9a0b3e0e8d" + integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ== + dependencies: + style-to-object "1.0.14" + +style-to-object@1.0.14: + version "1.0.14" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.14.tgz#1d22f0e7266bb8c6d8cae5caf4ec4f005e08f611" + integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw== + dependencies: + inline-style-parser "0.2.7" + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -6561,6 +7554,16 @@ tree-kill@1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== + ts-api-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" @@ -6585,7 +7588,7 @@ tsconfig-paths@^4.2.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.7.0, tslib@^2.8.1: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.1, tslib@^2.7.0, tslib@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -6706,6 +7709,67 @@ undici-types@~7.16.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== +unfetch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-3.1.2.tgz#dc271ef77a2800768f7b459673c5604b5101ef77" + integrity sha512-L0qrK7ZeAudGiKYw6nzFjnJ2D5WHblUBwmHIqtPS6oKUd+Hcpk7/hKsSmcHsTlpd1TbTNsiRBUKRq3bHLNIqIw== + +unfetch@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + +unified@^11.0.0: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + +unist-util-is@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9" + integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-parents@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02" + integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + universalify@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" @@ -6805,6 +7869,22 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vfile-message@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + victory-area@37.3.6: version "37.3.6" resolved "https://registry.yarnpkg.com/victory-area/-/victory-area-37.3.6.tgz#968f1ae2e402c5afa426ec743b4045068b53be75" @@ -7188,6 +8268,11 @@ wait-on@9.0.3: minimist "^1.2.8" rxjs "^7.8.2" +web-vitals@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7" + integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw== + webcola@3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/webcola/-/webcola-3.4.0.tgz#490d26ae98e5b5109478b94a846a62ff6831a99d" @@ -7414,3 +8499,8 @@ zustand@^4.4.1: integrity sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw== dependencies: use-sync-external-store "^1.2.2" + +zwitch@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==