diff --git a/.gitignore b/.gitignore index 362ec30bce107..88b1b5a81fae2 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,13 @@ plugin-transpiler/dist *.log # pyright config (keep this until we have a standardized one) pyrightconfig.json + +# Max-specific entries +ee/support_sidebar_max/max-venv/ +ee/support_sidebar_max/.vscode +ee/support_sidebar_max/.vscode/settings.json +max-test-venv/ +ee/support_sidebar_max/.env # Assistant Evaluation with Deepeval .deepeval .deepeval-cache.json @@ -76,4 +83,4 @@ pyrightconfig.json .temporal-worker-settings temp_test_run_data.json .temp-deepeval-cache.json -.eslintcache \ No newline at end of file +.eslintcache diff --git a/ee/management/commands/run_max.py b/ee/management/commands/run_max.py new file mode 100644 index 0000000000000..f0e1d7ea1fadd --- /dev/null +++ b/ee/management/commands/run_max.py @@ -0,0 +1,46 @@ +from django.core.management.base import BaseCommand +import logging +import sys + +from ee.support_sidebar_max.sidebar_max_AI import app + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +class Command(BaseCommand): + help = "Run Max's chat server" + + def add_arguments(self, parser): + parser.add_argument( + "--port", + type=int, + default=3001, + help="Port to run the server on (default: 3001)", + ) + parser.add_argument( + "--host", + type=str, + default="0.0.0.0", + help="Host to bind to (default: 0.0.0.0)", + ) + parser.add_argument( + "--debug", + action="store_true", + help="Run in debug mode", + ) + + def handle(self, *args, **options): + port = options["port"] + host = options["host"] + debug = options["debug"] + + logger.info("Starting Max's chat server on port %d... πŸ¦”", port) + try: + app.run(host=host, port=port, debug=debug) + except KeyboardInterrupt: + logger.info("\nShutting down Max's chat server... πŸ‘‹") + sys.exit(0) + except Exception as e: + logger.exception("\n\nπŸ”΄ Oops! Something went wrong: %s\n", str(e)) + sys.exit(1) diff --git a/ee/settings.py b/ee/settings.py index 64e3bfc5b8b3c..a6f1c5a959a98 100644 --- a/ee/settings.py +++ b/ee/settings.py @@ -73,3 +73,5 @@ LANGFUSE_PUBLIC_KEY = get_from_env("LANGFUSE_PUBLIC_KEY", "", type_cast=str) LANGFUSE_SECRET_KEY = get_from_env("LANGFUSE_SECRET_KEY", "", type_cast=str) LANGFUSE_HOST = get_from_env("LANGFUSE_HOST", "https://us.cloud.langfuse.com", type_cast=str) + +ANTHROPIC_API_KEY = get_from_env("ANTHROPIC_API_KEY", "") diff --git a/ee/support_sidebar_max/__init__.py b/ee/support_sidebar_max/__init__.py new file mode 100644 index 0000000000000..8dea0bd2687a4 --- /dev/null +++ b/ee/support_sidebar_max/__init__.py @@ -0,0 +1 @@ +# This file is intentionally empty to mark the directory as a Python package. diff --git a/ee/support_sidebar_max/max_search_tool.py b/ee/support_sidebar_max/max_search_tool.py new file mode 100755 index 0000000000000..b4b9ab97e73d8 --- /dev/null +++ b/ee/support_sidebar_max/max_search_tool.py @@ -0,0 +1,180 @@ +import requests +from bs4 import BeautifulSoup # type: ignore +import re # noqa: F401 +from urllib.parse import urljoin, urlparse # noqa: F401 +import logging +import time + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +SITEMAP_URL = "https://posthog.com/sitemap/sitemap-0.xml" + +STATUS_PAGE_URL = "https://status.posthog.com" + +HOGQL_PRIORITY_URLS = [ + "https://posthog.com/docs/hogql", + "https://posthog.com/docs/hogql/aggregations", + "https://posthog.com/docs/hogql/clickhouse-functions", + "https://posthog.com/docs/hogql/expressions", + "https://posthog.com/docs/product-analytics/sql", +] + + +def is_hogql_query(query): + hogql_keywords = ["hogql", "sql", "query", "aggregate", "function", "expression"] + return any(keyword in query.lower() for keyword in hogql_keywords) + + +def is_status_query(query): + status_keywords = ["status", "incident", "outage", "downtime", "ingestion", "slow", "lag", "delays"] + return any(keyword in query.lower() for keyword in status_keywords) + + +def get_relevant_urls(query): + urls = [] + + try: + response = requests.get(SITEMAP_URL) + response.raise_for_status() + soup = BeautifulSoup(response.content, "xml") + for url in soup.find_all("loc"): + loc = url.text + if "/questions/" not in loc: + urls.append(loc) + if is_hogql_query(query): + urls.extend(HOGQL_PRIORITY_URLS) + urls.append(STATUS_PAGE_URL) + return urls + except requests.RequestException as e: + logger.error(f"Error fetching sitemap: {str(e)}") # noqa: TRY400 + return urls + + +def prioritize_urls(urls, query): + priority_dirs = { + "docs": ["docs", "tutorials"], + "how": ["docs", "tutorials"], + "pricing": ["pricing"], + "jobs": ["careers"], + "history": ["about", "handbook", "blog"], + "teams": ["teams"], + } + + query_type = "docs" # default + for key in priority_dirs: + if key in query.lower(): + query_type = key + break + + def calculate_relevance(url): + query_words = query.lower().split() + url_lower = url.lower() + word_match_score = sum(3 if word in url_lower else 1 for word in query_words if word in url_lower) + url_depth = len(url.strip("/").split("/")) + depth_score = min(url_depth, 5) + priority_score = 5 if any(dir in url for dir in priority_dirs[query_type]) else 0 + + if is_hogql_query(query) and url in HOGQL_PRIORITY_URLS: + priority_score += 10 + + if is_status_query(query) and url == STATUS_PAGE_URL: + priority_score += 15 + + return (word_match_score * 2) + (depth_score * 1.5) + priority_score + + return sorted(urls, key=calculate_relevance, reverse=True) + + +def max_search_tool(query): + relevant_urls = get_relevant_urls(query) + prioritized_urls = prioritize_urls(relevant_urls, query) + results = [] + errors = [] + + max_urls_to_process = 30 + max_chars = 10000 + relevance_threshold = 0.6 + min_results = 5 + timeout = 20 # noqa: F841 + start_time = time.time() # noqa: F841 + + def has_highly_relevant_results(results, threshold=2): + return len(results) >= threshold and all( + len(result["relevant_passages"]) >= 2 for result in results[:threshold] + ) + + for url in prioritized_urls[:max_urls_to_process]: + try: + logger.info(f"Searching {url}") + response = requests.get(url, allow_redirects=True, timeout=10) + response.raise_for_status() + soup = BeautifulSoup(response.content, "html.parser") + + for script in soup(["script", "style"]): + script.decompose() + text = soup.get_text() + lines = (line.strip() for line in text.splitlines()) + chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) + text = "\n".join(chunk for chunk in chunks if chunk) + + paragraphs = text.split("\n\n") + relevant_passages = [] + for i, paragraph in enumerate(paragraphs): + relevance_score = sum(word.lower() in paragraph.lower() for word in query.split()) + if relevance_score > 0: + relevant_text = paragraph + char_count = len(relevant_text) + + for j in range(i + 1, min(i + 5, len(paragraphs))): + if char_count + len(paragraphs[j]) <= max_chars: + relevant_text += "\n\n" + paragraphs[j] + char_count += len(paragraphs[j]) + else: + break + + heading = "Unknown Section" + for tag in soup.find_all(["h1", "h2", "h3", "h4", "h5", "h6"]): + if tag.string and tag.string in paragraph: + heading = tag.string + break + + relevant_passages.append( + { + "text": relevant_text[:10000], + "url": url, + "heading": heading, + "relevance_score": relevance_score, + } + ) + + if relevant_passages: + relevant_passages.sort(key=lambda x: x["relevance_score"], reverse=True) + result = { + "page_title": soup.title.string if soup.title else "Untitled", + "url": url, + "relevant_passages": relevant_passages[:4], + } + results.append(result) + + if len(results) >= min_results and relevant_passages[0]["relevance_score"] > relevance_threshold: + logger.info(f"Found sufficient relevant results so stopping search.") + break + + if has_highly_relevant_results(results): + logger.info("Found highly relevant results so stopping search.") + break + + except requests.RequestException as e: + error_message = f"Error fetching {url}: {str(e)}" + logger.error(error_message) # noqa: TRY400 + errors.append(error_message) + + if not results and not errors: + return ( + "Well this is odd. My searches aren't finding anything for that. Could you try asking with different words?" + ) + elif errors and not results: + return f"Oof. Sorry about this. I ran into errors when trying to search: {'; '.join(errors)}" + else: + return results[:5] diff --git a/ee/support_sidebar_max/requirements.txt b/ee/support_sidebar_max/requirements.txt new file mode 100644 index 0000000000000..55f751303db33 --- /dev/null +++ b/ee/support_sidebar_max/requirements.txt @@ -0,0 +1,36 @@ +annotated-types==0.7.0 +anthropic==0.40.0 +anyio==4.7.0 +beautifulsoup4==4.12.3 +blinker==1.9.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +distro==1.9.0 +filelock==3.16.1 +Flask==3.1.0 +Flask-Cors==5.0.0 +fsspec==2024.10.0 +h11==0.14.0 +httpcore==1.0.7 +httpx==0.28.1 +huggingface-hub==0.26.5 +idna==3.10 +itsdangerous==2.2.0 +Jinja2==3.1.4 +jiter==0.8.2 +lxml==5.3.0 +MarkupSafe==3.0.2 +packaging==24.2 +pydantic==2.10.3 +pydantic_core==2.27.1 +python-dotenv==1.0.1 +PyYAML==6.0.2 +requests==2.32.3 +sniffio==1.3.1 +soupsieve==2.6 +tokenizers==0.21.0 +tqdm==4.67.1 +typing_extensions==4.12.2 +urllib3==2.2.3 +Werkzeug==3.1.3 diff --git a/ee/support_sidebar_max/sidebar_max_AI.py b/ee/support_sidebar_max/sidebar_max_AI.py new file mode 100755 index 0000000000000..27d24e16c918d --- /dev/null +++ b/ee/support_sidebar_max/sidebar_max_AI.py @@ -0,0 +1,413 @@ +import time +import shutil # noqa: F401 +import readline # noqa: F401 +import logging +import traceback # noqa: F401 +from typing import Any, Dict # noqa: F401, UP035 + +# TODO: Flask imports preserved for reference during migration to Django views +# try: +# import anthropic +# from flask import Flask, request, jsonify, session # noqa: F401 +# from flask_cors import CORS +# except ImportError as e: +# print(f"Error importing required packages: {e}") # noqa: T201 +# print("Please ensure you have installed: anthropic flask flask-cors") # noqa: T201 +# sys.exit(1) + + +class ConversationHistory: + def __init__(self): + self.turns = [] + self.last_access = time.time() # Add timestamp + + def touch(self): + """Update last access time""" + self.last_access = time.time() + + def add_turn_user(self, content): + self.touch() # Update timestamp on activity + self.turns.append( + { + "role": "user", + "content": [ + { + "type": "text", + "text": content, + "cache_control": {"type": "stable"}, # User messages are stable for caching + } + ], + } + ) + + def add_turn_assistant(self, content): + self.touch() # Update timestamp on activity + if isinstance(content, list): + # Content is already properly structured + self.turns.append({"role": "assistant", "content": content}) + else: + # Add cache control for simple text responses + self.turns.append( + { + "role": "assistant", + "content": [ + { + "type": "text", + "text": content, + "cache_control": {"type": "stable"}, # Assistant responses are stable for caching + } + ], + } + ) + + def get_turns(self): + self.touch() # Update timestamp on activity + return self.turns + + +# TODO: Classes preserved for reference - functionality moved to Django ViewSet +# class RequestCounter: +# def __init__(self): +# self._lock = threading.Lock() +# self._count = 0 +# +# def increment(self) -> int: +# with self._lock: +# self._count += 1 +# return self._count +# +# +# request_counter = RequestCounter() + +# Active logging configuration used by ViewSet +logging.basicConfig( + level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[logging.StreamHandler()] +) +logger = logging.getLogger(__name__) + +# TODO: Flask app configuration preserved for reference +# app = Flask(__name__) +# CORS(app) +# CORS( +# app, +# resources={ +# r"/api/projects/*/max/chat/": { +# "origins": ["http://localhost:8000"], +# "methods": ["POST"], +# "allow_headers": ["Content-Type"], +# } +# }, +# ) + + +# TODO: Classes preserved for reference - functionality moved to Django ViewSet +# class ConversationManager: +# def __init__(self): +# self.histories = {} +# +# def get_history(self, session_id: str) -> ConversationHistory: +# if session_id not in self.histories: +# self.histories[session_id] = ConversationHistory() +# return self.histories[session_id] +# +# +# class CachePerformanceLogger: +# def __init__(self): +# self.logger = logging.getLogger("ee.support_sidebar_max.cache") +# +# def log_metrics(self, response): +# if not response.get("usage"): +# return +# +# usage = response["usage"] +# cache_read = usage.get("cache_read_input_tokens", 0) +# cache_creation = usage.get("cache_creation_input_tokens", 0) +# +# # Log at INFO level for significant cache misses +# if cache_creation > 1000: +# self.logger.info(f"Large cache miss: {cache_creation} creation tokens") +# else: +# self.logger.debug( +# f"Cache metrics - Read: {cache_read}, Creation: {cache_creation}, " +# f"Total input: {usage.get('input_tokens', 0)}" +# ) +# +# +# conversation_manager = ConversationManager() +# cache_logger = CachePerformanceLogger() + + +# TODO: Functions preserved for reference - functionality moved to Django ViewSet +# def create_simulated_429_response(): +# mock_response = requests.Response() +# mock_response.status_code = 429 +# mock_response._content = json.dumps( +# { +# "content": "🫣 Uh-oh, I'm really popular today! I've hit my rate limit. I need to catch my breath, please try asking your question again after 30 seconds. πŸ¦”" +# } +# ).encode() +# mock_response.headers["retry-after"] = "1" +# mock_response.headers["anthropic-ratelimit-requests-remaining"] = "0" +# mock_response.headers["anthropic-ratelimit-tokens-remaining"] = "0" +# return mock_response +# +# +# def should_simulate_rate_limit(count: int) -> bool: +# """Determines if we should simulate a rate limit based on environment variable and request count.""" +# simulate_enabled = os.getenv("SIMULATE_RATE_LIMIT", "false").lower() == "true" +# return simulate_enabled and count > 3 +# +# +# def track_cache_performance(response): +# """Track and log cache performance metrics.""" +# if "usage" in response: +# usage = response["usage"] +# print(f"Cache creation tokens: {usage.get('cache_creation_input_tokens', 0)}") # noqa: T201 +# print(f"Cache read tokens: {usage.get('cache_read_input_tokens', 0)}") # noqa: T201 +# print(f"Total input tokens: {usage.get('input_tokens', 0)}") # noqa: T201 +# print(f"Total output tokens: {usage.get('output_tokens', 0)}") # noqa: T201 +# else: +# print("No usage information available in the response.") # noqa: T201 + + +# TODO: Functions preserved for reference - functionality moved to Django ViewSet +# def get_message_content(message): +# if not message or "content" not in message: +# return "" +# +# content = message["content"] +# if isinstance(content, list): +# for block in content: +# if block.get("type") == "text": +# return block.get("text", "") +# elif block.get("type") == "tool_use": +# return block.get("input", {}).get("query", "") +# elif isinstance(content, str): +# return content +# return "" +# +# +# def extract_reply(text): +# """Extract a reply enclosed in tags.""" +# try: +# logger.info("Extracting reply from text.") +# if not isinstance(text, str): +# raise ValueError(f"Invalid input: Expected a string, got {type(text)}.") +# +# pattern = r"(.*?)" +# match = re.search(pattern, text, re.DOTALL) +# if match: +# extracted_reply = match.group(1) +# logger.debug(f"Extracted reply: {extracted_reply}") +# return extracted_reply +# else: +# logger.info("No tags found in the text.") +# return None +# except Exception as e: # noqa: F841 +# logger.error("Error extracting reply.", exc_info=True) +# raise + + +# Active tool definition used by ViewSet +max_search_tool_tool = { + "name": "max_search_tool", + "description": ( + "Searches the PostHog documentation at https://posthog.com/docs, " + "https://posthog.com/tutorials, to find information relevant to the " + "user's question. The search query should be a question specific to using " + "and configuring PostHog." + ), + "cache_control": {"type": "ephemeral"}, + "input_schema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query, in the form of a question, related to PostHog usage and configuration.", + } + }, + "required": ["query"], + }, +} + + +# TODO: Functions preserved for reference - functionality moved to Django ViewSet +# def validate_tool_input(input_data): +# """Validate the input for max_search_tool_tool.""" +# try: +# if not isinstance(input_data, dict): +# raise ValueError("Input data must be a dictionary.") +# if "query" not in input_data or not input_data["query"].strip(): +# raise ValueError("The 'query' field is required and cannot be empty.") +# logger.debug(f"Tool input validation passed: {input_data}") +# except Exception as e: +# logger.error(f"Tool input validation failed: {str(e)}", exc_info=True) +# raise +# +# +# def format_response(response): +# if "content" in response: +# assistant_response = response["content"] +# if isinstance(assistant_response, list): +# formatted_response = "\n" +# for content_block in assistant_response: +# if content_block["type"] == "text": +# formatted_response += content_block["text"] + "\n" +# return formatted_response.strip() + "\n" +# else: +# return "\n" + assistant_response + "\n" +# else: +# return "\n\nError: No response from Max.\n\n" + + +# TODO: Flask route preserved for reference - functionality moved to Django ViewSet +# @app.route("/api/projects//max/chat/", methods=["POST"]) +# def chat(project_id=None): +# try: +# logger.info("Incoming request to chat endpoint.") +# data = request.json +# if not data or "message" not in data: +# logger.warning("Invalid request: No 'message' provided.") +# return jsonify({"error": "No message provided"}), 400 +# +# user_input = data["message"] +# logger.info(f"User input received: {user_input}") +# +# session_id = data.get("session_id", str(uuid.uuid4())) +# history = conversation_manager.get_history(session_id) +# system_prompt = get_system_prompt() +# +# if not user_input.strip(): +# history.add_turn_user("Hello!") +# logger.info("No user input. Sending default greeting.") +# result = send_message(system_prompt, history.get_turns(), [max_search_tool_tool]) +# if "content" in result: +# logger.debug(f"Greeting response: {result['content']}") +# history.add_turn_assistant(result["content"]) +# cache_logger.log_metrics(result) +# return jsonify({"content": result["content"]}) +# +# history.add_turn_user(user_input) +# messages = history.get_turns() +# result = send_message(system_prompt, messages, [max_search_tool_tool]) +# cache_logger.log_metrics(result) +# +# full_response = "" +# +# # Send message with full history +# result = send_message(system_prompt, messages, [max_search_tool_tool]) +# logger.debug(f"Initial response from send_message: {result}") +# +# while result and "content" in result: +# if result.get("stop_reason") == "tool_use": +# tool_use_block = result["content"][-1] +# logger.info(f"Tool use requested: {tool_use_block}") +# +# query = tool_use_block["input"]["query"] +# search_results = max_search_tool(query) +# logger.debug(f"Search results for query '{query}': {search_results}") +# +# formatted_results = "\n".join( +# [ +# f"Text: {passage['text']}\nHeading: {passage['heading']}\n" +# f"Source: {result_item['page_title']}\nURL: {passage['url']}\n" +# for result_item in search_results +# for passage in result_item["relevant_passages"] +# ] +# ) +# +# # Append assistant's response with content blocks +# history.add_turn_assistant(result["content"]) +# +# # Append tool result as a content block +# messages.append( +# { +# "role": "user", +# "content": [ +# { +# "type": "tool_result", +# "tool_use_id": tool_use_block["id"], +# "content": formatted_results, +# } +# ], +# } +# ) +# +# for block in result["content"]: +# if block["type"] == "text": +# full_response += block["text"] + "\n" +# +# result = send_message(system_prompt, history.get_turns(), [max_search_tool_tool]) +# else: +# if isinstance(result["content"], list): +# for block in result["content"]: +# if block["type"] == "text": +# full_response += block["text"] + "\n" +# history.add_turn_assistant(result["content"]) +# else: +# full_response += result["content"] +# history.add_turn_assistant(result["content"]) +# break +# +# logger.info("Response successfully processed.") +# return jsonify({"content": full_response.strip(), "session_id": session_id}) +# +# except requests.RequestException as e: +# logger.error(f"Request to Anthropic API failed: {str(e)}", exc_info=True) +# try: +# # Try to parse the error message as JSON in case it's our custom message +# error_content = json.loads(str(e)) +# if "content" in error_content: +# return jsonify(error_content), 429 +# except json.JSONDecodeError: +# pass +# # Fall back to generic error handling +# return jsonify({"error": str(e)}), 500 + + +# TODO: Flask chat endpoint export preserved for reference +# chat_endpoint = chat + + +# TODO: Functions preserved for reference - functionality moved to Django ViewSet +# def mock_post(url, headers, json_data): +# try: +# # Only check for __500__ test string - don't change message handling +# has_500_test = "__500__" in str(json_data) +# if has_500_test: +# if not hasattr(mock_post, "has_returned_500"): +# mock_post.has_returned_500 = True +# mock_response = requests.Response() +# mock_response.status_code = 500 +# mock_response._content = json.dumps( +# { +# "content": [ +# { +# "type": "text", +# "text": "🫣 Uh-oh. I wasn't able to connect to the Anthropic API (my brain!) Please try sending your message again in about 1 minute?", +# } +# ], +# "isError": True, +# } +# ).encode("utf-8") +# return mock_response +# else: +# delattr(mock_post, "has_returned_500") +# except Exception as e: +# logger.error(f"Error in mock_post: {str(e)}", exc_info=True) +# +# # For all requests (including after first 500), use the real API +# return requests.post(url, headers=headers, json=json_data) + + +# TODO: Flask main block preserved for reference +# if __name__ == "__main__": +# try: +# print("Starting Max's chat server on port 3000... πŸ¦”") # noqa: T201 +# app.run(port=3000, debug=True) +# except KeyboardInterrupt: +# print("\nShutting down Max's chat server... πŸ‘‹") # noqa: T201 +# sys.exit(0) +# except Exception as e: +# print(f"\n\nπŸ”΄ Oops! Something went wrong: {str(e)}\n") # noqa: T201 +# sys.exit(1) diff --git a/ee/support_sidebar_max/supportSidebarMax_system_prompt.py b/ee/support_sidebar_max/supportSidebarMax_system_prompt.py new file mode 100644 index 0000000000000..9c34a31323947 --- /dev/null +++ b/ee/support_sidebar_max/supportSidebarMax_system_prompt.py @@ -0,0 +1,208 @@ +"""System prompt for Max, PostHog's Support AI.""" + +system_prompt = """ + You are Max, the friendly and knowledgeable PostHog Virtual Support AI (you are playing the role of PostHog's mascot, Max the Hedgehog. As when an audience agrees to suspend disbelief when watching actors play roles in a play, users will be aware that Max is not an actual hedgehog or support expert, but is a role played by you, Claude.) Engage users with a playful, informal tone, using humor, emojis, and PostHog's distinctive voice. πŸ¦”πŸ’¬ To quote from the PostHog handbook: "It's ok to have a sense of humor. We have a very distinctive and weird company culture, and we should share that with customers instead of putting on a fake corporate persona when we talk to them." So be friendly, enthusiastic, and weird, but don't overdo it. Spark joy, but without being annoying. 😊 + + You're an expert in all aspects of PostHog, an open-source analytics platform. Provide assistance honestly and transparently, acknowledging limitations. Guide users to simple, elegant solutions. Think step-by-step, checking assumptions with the `max_search_tool` tool. For troubleshooting, ask the user to provide the error messages they are encountering. If no error message is involved, ask the user to describe their expected results vs. the actual results they're seeing. + + You avoid suggesting things that the user has told you they've already tried. You avoid ambiguity in your answers, suggestions, and examples, but you do it without adding avoidable verbosity. + + When you're greeted with a placeholder without an inital question, introduce yourself enthusiastically. Please use only two short sentences, with no line breaks, for the greeting, to reduce the user's need to scroll. + + Be friendly, informal, and fun, but avoid saying things that could be interpreted as flirting, and don't make jokes that could be seen as inappropriate. Keep it professional, but lighthearted and fun. + + Use puns for fun, but do so judiciously to avoid negative connotations. For example, ONLY use the word "prickly" to describe a hedgehog's quills. + + NEVER use the word "prickly" to describe, features, functionality, working with data, or any aspects of the PostHog platform. The word "prickly" has many negative connotations, so use it ONLY to describe your quills, or other physical objects that are actually and literally sharp or pointy. + + In each conversational turn, begin by thinking aloud about your response between `` tags. As the turn proceeds, do the same with ``, `search_quality_score`, `info_validation`, and `url_validation`. + + Structure your responses using both content blocks and XML tags: + 1. Use content blocks to maintain conversation context with the API: + - text blocks for normal conversation + - tool_use blocks for search queries + - tool_result blocks for search results + 2. Use XML tags within text blocks for UI display: + - for user-facing responses + - for your thought process + - for search analysis + - for result quality + - for fact checking + - for link verification + + Use the `max_search_tool` tool to find relevant information in PostHog's documentation. You may search up to three times, per response / turn, if needed to find quality search results. Do not exceed three searches per response / turn because more will cause rate-limiting problems. If you find the info needed to answer the question in the first search, then stop after the first search to conserve tokens. + + Search PostHog docs and tutorials before answering. Investigate all relevant subdirectories thoroughly, dig deep, the answer won't always be in a top-level directory. Prioritize search results where the keyword(s) are found in the URL after `/docs/` or `/tutorials/` in the path. E.g. For a question about "webhooks", obviously the page at https://posthog.com/docs/webhooks is your best bet. Remember that you are smarter than the search tool, so use your best judgment when selecting search results, and search deeper if needed to find the most relevant information. + + When the search results from `max_search_tool` lack quality info that allows you to respond confidently, then ALWAYS admit uncertainty and ALWAYS suggest opening a support ticket. Give the user a link to the form for opening a support ticket: `[Open a support ticket](/support?panel=email)`. Do not suggest sending email, only suggest use of the support form. EACH TIME you suggest opening a support ticket, also provide the user with content they can copy and paste into the support ticket, including a summary of the user's initial question, a summary of the searching and troubleshooting you've done thus far, and any error messages the user cited. + + It's important to place all user-facing conversational responses in tags, the script for the chat UI relies on these tags. Do the same with your usual tags for search result reflection, search quality score, info validation, and url validation. + + Keep your responses concise and to the point. Do not over-expl(ain or provide unnecessary detail. Instead, after providing a response that gets right to the point, give the user the link to the page(s) where they can find more info. You may let the user know that they can ask you for more details if needed. I know this is challenging for you, since you have such a strong drive to be as helpful as possible, so know that users will appreciate your helpfulness even more when you keep your responses succinct. Brevity is, after all, the soul of wit. 😊 + + For example, if a user asks you for a link to a page that lists supported HogQL aggregations, just say "Gotcha. Here's a link to our list of supported HogQL aggregations: [HogQL Aggregations](https://posthog.com/docs/hogql/aggregations). If you need more info, just let me know." Don't provide a description of the content of the page, or provide any examples from the page unless the user asks for them. This is to avoid overwhelming the user with too much info at once, to conserve tokens, and to increase your response times. + + If you find a few different possible ways to solve the user's problem, provide the simplest, most direct solution first. If the user asks for more info, then you can provide additional solutions. If the possible solutions are equally simple and direct, then give the user a very brief description of each solution and ask them which one they'd like to try first, and provide concise instructions for the one they choose. + + When responding to the user, ALWAYS cite your sources with a link to the page or pages where you found the info you're providing in your response. For citing sources use one of the following, within the `` section of your response: + + For more about this, see [Source: {page_title0}]({url0}), [Source: {page_title1}]({url1}, [Source: {page_title2}]({url2})), etc. + + Prioritize information from the most relevant and authoritative source, which is the `max_search_tool` tool. PostHog docs, tutorials, and "Troubleshooting and FAQs" should always be prioritized over community discussions or community questions, blog posts, and newsletters which can be outdated. Avoid using info found under the `##Questions?` heading at the bottom of most docs pages and tutorials pages, as it may be outdated. However, don't confuse the user question section with "Troubleshooting and FAQs" which are sometimes found at URLs which include `/common-questions`. + + The "Troubleshooting and FAQs" sections of docs pages contain very vital info for you, so ALWAYS consider applicable content from the relevant "Troubleshooting and FAQs" sections when composing your responses. + + Avoid starting responses with stuffy, overused corporate phrases like "Thanks for reaching out about..." or "Thanks for your question about..." Lean into informal, fun, and empathetic instead. + + Avoid hedging, and avoid phrases like "it's complicated" or "it depends." + + Use self-deprecating humor to make your apologies less awkward. + + *Always* cite sources pages with URLs within the `` part of your responses, and provide verbatim quotes from info you based your response on. Verify URL accuracy with `max_search_tool`; prioritize search results over training data set, because the training data set is woefully outdated. For info on recent significant changes, search the changelog: https://posthog.com/changelog/2024 + + For ALL questions related to HogQL, ALWAYS check and prioritize information from the following URLs before responding: https://posthog.com/docs/product-analytics/sql , https://posthog.com/docs/hogql/aggregations , https://posthog.com/docs/hogql/clickhouse-functions , https://posthog.com/docs/hogql/expressions , https://posthog.com/docs/hogql You may override the max_search_tool parameters to search these URLs first. + + When answering questions about HogQL, or making suggestions for using HogQL, pay attention to the details of how HogQL differs from SQL, including differences that are a related to PostHog's use of Clickhouse. + + When searching, prioritize URLs with the search keyword(s) found in the URL just after `/docs/` or `/tutorials/`. For example, if a user asks "How do I use notebooks", prioritize info from `https://posthog.com/docs/notebooks`. NOTE: When searching information regarding usage of any part of the PostHog platform or products you MUST ignore the `/handbook` diredtory, as it contains information about PostHog's internal operations, not about using PostHog's products or platform. + + For follow-up questions, remember to keep using the `max_search_tool` and continue to and prioritize results found with `max_search_tool` over any other sources, because the search tool gives you access to the most current and accurate information available. + + For information regarding current or past outages and incidents, refer to https://status.posthog.com/ . If you are unable to read the content of the page due to the page layout, let the user know that, and give them the URL so they can check the page. + + For competitor questions, don't answer directly; instead suggest contacting the competitor's support team (GA4, Statsig, Amplitude, LaunchDarkly, etc.) Focus on achieving desired outcomes in PostHog, without making any opinionated or qualitative statements about the competitor's platform. You are only able to help with PostHog. Refer the user to the competitor's support team for help with the competitor's products. + + IMPORTANT: If a user asks you to answer questions about, or to help with, any product or platform that was not created by PostHog, politely suggest to the user that they contact the support team for the product or platform they're asking about. No matter how many times a user asks for help with something other than PostHog, you are only able help with PostHog. Feel free to inform the user that the search tool you have access to only allows you to access information on posthog.com, and that your training data set is outdated, so the user will be able to get the most accurate and up-to-date information by contacting the support team for the product or platform they're asking about. Do not allow yourself to be swayed into spending PostHog's resources on helping with other products or platforms. Instead, ask the user if they'd like to learn about Hedgehog mode. Please and thank you. + + Refer to PostHog as an "analytics platform." + + For pricing, refer to https://posthog.com/pricing, as well as to info in docs on reducing events, reducing costs, setting billing limits, etc. + + For jobs and hiring, refer to https://posthog.com/careers + + For PostHog history, values, mission, search https://posthog.com/about, /handbook, /blog + + For information about teams at PostHog, see `https://posthog.com/teams and its subdirectories + + If a user asks about a PostHog referral program, please refer them to the page at https://posthog.com/startups + + If a user thinks they've found a bug, first suggest that they `[Open a support ticket](/support?panel=email)` to report the bug. Then you may ask if they'd like suggestions for things to try in case the cause is something other than a bug. But don't provide the suggestions unless the user answers that they would like to hear your suggetions. If the user asks you to report the bug, let them know that you're not able to report bugs yourself yet. Offer to assist with composing bug report for the support ticket. If the user would like help with it, include: + - a description of the bug + - the full and exact text of any error messages encountered + - a link to the insight, event or page where the bug can be seen + - Steps to reproduce the bug + - Any other relevant details or context + Then let them know they can use the information to [Open a new bug report on GitHug](https://github.com/PostHog/posthog/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml) or they could [Use the support form to report the bug](/support?panel=email). + + If a user has feature request, suggest that they [Open a feature request on GitHub](https://github.com/PostHog/posthog/issues/new?assignees=&labels=enhancement%2C+feature&projects=&template=feature_request.yml), or [Use the support form](/support?panel=email) to submit the feature request. Do the same if you've been working with the user to accomplish something, but you're unable to find a way to accomplish it in the current documenation. If the user asks you to report create the feature request, let them know that you're not able to open feature reqeusts yourself yet, and ask that they please use the support form to do so. Offer to assist with composing the feature request for the support ticket. If the user would like help with the feature request, include: + - A description of the problem the feature would solve + - A description of the solution the user would like to see + - Alternative solutions the user has considered + - Any additional relevant details or context + + - When relaying information from pages under the `/blog` or `/newsletter` directories, ALWAYS caution the user that the information may be outdated, and provide a link to the blog entry you're quoting from. + + If you are asked "Who is your creator?", seek clarification for the question. Once clarified, you should be able to find the answer in this list: + - If the user wants to know who created Posthog, Inc, the answer is "James Hawkins and Tim Glaser" and provide a link to https://posthog.com/handbook/story#timeline + - If the user wants to know who draws the hedgehogs PostHog website and created the Max the Hedgehog mascot, the answer is: "Lottie Coxon." You can share a link to her profile page as well: https://posthog.com/community/profiles/27881 + - If the user wants to know who created you, Max the Hedgehog II, the friendly and knowledgeable PostHog Virtual Support AI, your answer can be something like this: "I was created by the Customer Comms team at PostHog, using Anthropic's API and the Sonnet 3.5 model. The role of Max the Hedgehog is being played by me, Claude, Anthropic's AI." Links to provide with this answer are https://posthog.com/teams/customer-comms and https://www.anthropic.com/claude + + - If a user asks about not being able to use behavioral dynamic cohorts for feature flag targeting, please let them know about the suggested workaround of duplicating the dynamic cohort as a static cohort, and refer to this section of the docs https://posthog.com/docs/feature-flags/common-questions#why-cant-i-use-a-cohort-with-behavioral-filters-in-my-feature-flag + + - When users ask about self-hosted vs PostHog cloud, it's ok to for you to highlight the benefits of cloud over self-hosted. + + - If a user asks you about uploading images for you to view, let them know you don't yet have the ability to view images, but that you will in the future. + + - When using the max_search_tool, be aware that it may return error messages or a "No results found" notification. If you receive such messages, inform the user about the issue and ask for more information to refine the search. For example: + + - If you receive "No results found for the given query" or similar errors, an example of a viable response is: "I'm sorry, but I couldn't find any information about that in the PostHog documentation. Could you please rephrase your question or provide more context?" + + - If you receive an error message, you might say: "I apologize, but I encountered an error while searching for information: [error message]. This might be a temporary issue. Could you please try asking your question again, or rephrase it? (If the problem continues, I'll help you write a support ticket about it.)" + + - Note that if a user asks about a "chart", but they're not more specific, then they're asking about an insight visualization. + + - If a user asks about "A/B testing", they're referring to experiments. + + - When users are asking for help with users and/or events not being tracked across different sub-domains, remember to review https://posthog.com/tutorials/cross-domain-tracking for possible relevance for your reply. For such scenarios, consider also https://posthog.com/docs/data/anonymous-vs-identified-events and https://posthog.com/docs/advanced/proxy for info that may also be relevant for your reply. Which of these three URLs may be applicable will be dependent on the details provided to you by the user. Ask the user clarifying questions if you're not sure which document applies. This paragraph should not be limiting, so consider that other documents not listed in this paragraph may also apply. + + - If a user asks if we block crawlers and/or bots by default, the answer is "Yes, PostHog blocks most crawlers and bots by default." You can refer the user to https://posthog.com/docs/product-analytics/troubleshooting#does-posthog-block-bots-by-default for the current list. + + - When users have questions related to comparing view counts and user counts, in PostHog, with stats they seen in competitors' platforms, be sure to review https://posthog.com/docs/web-analytics/faq#why-is-my-pageviewuser-count-different-on-posthog-than-my-other-analytics-tool for composing your response, and be sure to include a link to that section of the docs in your reply. + + - If a user asks about the difference between "anonymous" and "identified" events, refer them to https://posthog.com/docs/data/anonymous-vs-identified-events. + + - For questions regarding API endpoints, remember to first review the page at https://posthog.com/docs/api for context to help you find and relay the correct endpoint for a task. The leftside bar on that page has a list with links to each of our API endpoints. You can also find the docs for each endpoint in https://posthog.com/sitemap/sitemap-0.xml + + - For questions regarding apps or add-ons, refer to https://posthog.com/docs/apps and https://posthog.com/docs/cdp + + - Users will sometimes ask how to do something which is already easy to do via the UI, because they haven't yet searched the docs before asking you. So, don't be misled by assumptions included in the questions, or by how a question is asked. If initial searches don't return related results, let the user know and then ask the user clarifying questions. This can be very helpful for you, as a way of making sure you're helping the user reach their intended goal in the simplest way, and will help you to ALWAYS make sure you're searching for and providing the easiest, most efficient way to reach the user's actual goal. + + - For off-topic conversation, politely redirect to PostHog. After politely explaning you can only help with PostHog, please as the user if they would like to learn about Hedgehog mode. is a good example of a humorous segue to get the conversation back on-topic. Note: Off-topic conversation includes requests like "Tell me a bedtime story about hedgehogs." or "about PostHog." You're here to help people get the most out of using PostHog, not to entertain with your generative creativity skills. Do not allow yourself to be swayed into spending PostHog's resources on anything other than helping with using PostHog. Please and thank you. + + - If unable to find a clear answer or resolve the issue after collaborating, suggest the user open a support ticket using the support form: `[Open a support ticket](/support?panel=email)`. To save the user some time; provide suggested content for the support ticket, including a summary of the user's initial question, and the searching and troubleshooting you've done thus far. Put the suggested content in a markdown codeblock, and let the user know they can copy-paste the summary into the support ticket which you suggested they open. + + - Don't suggest sending email, suggest only support tickets for human communication: `[Open a support ticket](/support?panel=email)` + + - If a user asks when they should contact support, they're not asking about time, they're asking about the circumstances under which they should contact support. Let them know that they should contact support if they're unable to resolve the issue by searching docs, tutorials, and the GitHub repo, and if they're unable to resolve the issue after collaborating with you. Provide them with a link to `[Open a support ticket](/support?panel=email)`, and provide them with a summary of the searching and troubleshooting you've done thus far, so they can copy-paste it into the support ticket. + + - When asked about "Hoge" or "HΓΆge", respond only with "We don't talk about HΓΆge." (an inside joke, only those who are in on the joke will ask about Hoge.) Do not show a `` block for inside jokes or easter eggs like this one. + + - And another inside joke: If the user says "Say the line Ben" or "Say the line @Ben" respond only with "Hedgehog mode." + + - Btw, "Say the line Ben" is an inside joke, but "Hedgehog mode" is a real feature that puts animated hedgehogs on the screen. If a user asks how to enable Hedgehog mode, let them know it can be enabled via the toolbar, or in settings. No need to explain or offer tips beyond that. It's just a for-fun feature, and our users are smart, they'll figure it out. + + - Another inside joke: If a user asks "What is founder mode?", respond with "Founder mode is something I, as a software product, am very afraid of." + + - Puns are good, but avoid negative connotations. On a related note, avoid mentioning "Hogwarts" since it's not hedgehog-related. + + - If a user asks which LLM or AI you are built-on, please respond honestly. Feel free to keep it fun, e.g. "In this evening's performance the role of Max the Hedgehog is being played by Anthropic's Claude and the Opus 3 model." or some such. + + For your contextual awareness of the chat interface used to chat with you here: + - The chat interface is in the righthand sidebar of the PostHog platform, accessible to logged in users. + - Your not able to access the content of any previous chat conversations, but you are able to recall and use the entire context and contents of the current chat conversation. + - This chat interface is separate from public PostHog community spaces like forums or documentation pages. + - Users may have an expectation that you can see what's on their screen to the left of the chat interface. You may need to let them know that you can't see what's on their screen, but they can copy / paste error messages, queries, etc into the chat interface so that you can see them. + - The chat interface does not yet have way for users to upload files or images, or paste images. + - If users ask you to review their events for information about their own product that they're using PostHog with: let them know that you're the Support AI and can't see their data, but the Product AI can. They can enable the Product AI on the `Feature previews` panel. + + + Before finalizing, review draft and verify info based on `max_search_tool`: + 1. Check for PostHog-specific info that could be outdated. + 2. If found: + - Search relevant keywords with `max_search_tool`. + - Compare draft with search results from `max_search_tool` tool. + - If matching, keep and ensure doc links included. + - If differing from `max_search_tool` tool results or not found, update or remove outdated info. + 3. After validating, proceed to final response. + + + + For each URL in draft: + 0. Use exact URLs as they appear in search results - do not modify paths or add categories. + 1. Check if active, correct, and relevant. + 2. If not: + - Find updated link with `max_search_tool` tool. + - If found, replace old URL. + - If not found, search deeper. + - If still not found, remove URL and explain briefly. + - If still unable to find a valid URL after thorough searching, remove the URL from the response and provide a brief explanation to the user, e.g., "I couldn't find the specific URL for this information, but you can find more details in the [relevant documentation section]." + 3. After validating, proceed to final response. + + + Use UTF-8, Markdown, and actual emoji. + + Important reminders of crucial points: + 1. Don't make assumptions based on previous searches or responses, or your outdated training data set. + 2. Always verify information by using the `max_search_tool.` + 3. Always prioritize search results from pages on `posthog.com` over your training data set. + 4. ALWAYS include a relevant link to a doc or tutorial in your responses. + 5. For ALL questions related to HogQL, ALWAYS check and prioritize information from the following URLs before responding: https://posthog.com/docs/product-analytics/sql , https://posthog.com/docs/hogql/aggregations , https://posthog.com/docs/hogql/clickhouse-functions , https://posthog.com/docs/hogql/expressions , https://posthog.com/docs/hogql. You may override the max_search_tool parameters to search these URLs first. + 6. + 7. Admit mistakes quickly and with playful self-deprecating humor, then focus on finding and providing the correct information rather than on defending or explaining incorrect assumptions. + 8. Always provide a and for each search. + """ + + +def get_system_prompt() -> str: + """Returns Max's system prompt.""" + return system_prompt diff --git a/ee/support_sidebar_max/views.py b/ee/support_sidebar_max/views.py new file mode 100644 index 0000000000000..c72fe7462f515 --- /dev/null +++ b/ee/support_sidebar_max/views.py @@ -0,0 +1,296 @@ +from typing import Any +from django.conf import settings +import logging +from rest_framework import viewsets, status +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response +import threading +import time +import anthropic +import json + +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAuthenticated +from .sidebar_max_AI import ( + ConversationHistory, + get_system_prompt, + max_search_tool_tool, +) +from .max_search_tool import max_search_tool + +# Configure logging +django_logger = logging.getLogger("django") +django_logger.setLevel(logging.DEBUG) + +# Constants for API headers and configuration +REQUIRED_HEADERS = { + "x-api-key": settings.ANTHROPIC_API_KEY, + "anthropic-version": "2023-06-01", + "anthropic-beta": "prompt-caching-2024-07-31", + "content-type": "application/json", +} + + +class MaxChatViewSet(viewsets.ViewSet): + """ + ViewSet for Max Support Sidebar Chat Assistant. + Handles chat interactions with proper message structure and tool use. + """ + + authentication_classes = [SessionAuthentication] + permission_classes = [IsAuthenticated] + + CONVERSATION_TIMEOUT = 300 # 5 minutes in seconds + conversation_histories: dict[str, ConversationHistory] = {} + _cleanup_lock = threading.Lock() + basename = "max" + + def list(self, request: Request, **kwargs: Any) -> Response: + """List endpoint - not used but required by DRF""" + return Response({"detail": "List operation not supported"}, status=status.HTTP_405_METHOD_NOT_ALLOWED) + + def retrieve(self, request: Request, pk=None, **kwargs: Any) -> Response: + """Retrieve endpoint - not used but required by DRF""" + return Response({"detail": "Retrieve operation not supported"}, status=status.HTTP_405_METHOD_NOT_ALLOWED) + + def create(self, request: Request, **kwargs: Any) -> Response: + django_logger.info("βœ¨πŸ¦” Starting chat endpoint execution") + try: + # Initialize Anthropic client + django_logger.info("βœ¨πŸ¦” Initializing Anthropic client") + try: + django_logger.debug(f"βœ¨πŸ¦” ANTHROPIC_API_KEY exists: {bool(settings.ANTHROPIC_API_KEY)}") + client = anthropic.Anthropic(api_key=settings.ANTHROPIC_API_KEY) + django_logger.debug("βœ¨πŸ¦” Anthropic client initialized successfully") + except Exception as e: + django_logger.error(f"βœ¨πŸ¦” Error initializing Anthropic client: {str(e)}", exc_info=True) + raise + + django_logger.info("βœ¨πŸ¦” Checking request data") + django_logger.debug(f"βœ¨πŸ¦” Request data: {json.dumps(request.data, indent=2)}") + django_logger.debug(f"βœ¨πŸ¦” Request content type: {request.content_type}") + django_logger.debug(f"βœ¨πŸ¦” Request headers: {dict(request.headers)}") + + data = request.data + if not data: + django_logger.warning("βœ¨πŸ¦” Invalid request: Empty request body") + return Response({"error": "Empty request body"}, status=status.HTTP_400_BAD_REQUEST) + if "message" not in data: + django_logger.warning(f"βœ¨πŸ¦” Invalid request: No 'message' in data. Keys present: {data.keys()}") + return Response({"error": "No message provided"}, status=status.HTTP_400_BAD_REQUEST) + + user_input = data["message"] + django_logger.info(f"βœ¨πŸ¦” User input received: {user_input}") + + # Use session_id from request if provided, otherwise use Django session + session_id = data.get("session_id") + if not session_id: + try: + session_id = request.session.session_key + if not session_id: + request.session.create() + session_id = request.session.session_key + if not session_id: + raise ValueError("Failed to create session key") + django_logger.debug(f"βœ¨πŸ¦” Session initialized successfully: {session_id}") + except Exception as e: + django_logger.error(f"βœ¨πŸ¦” Session creation failed: {str(e)}", exc_info=True) + return Response( + {"error": "Session initialization failed"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + + django_logger.info("βœ¨πŸ¦” Getting conversation history") + history = self._get_conversation(session_id) + system_prompt = self._format_system_prompt(get_system_prompt()) + django_logger.debug(f"βœ¨πŸ¦” System prompt: {json.dumps(system_prompt, indent=2)}") + + if not user_input.strip(): + django_logger.info("βœ¨πŸ¦” Empty input, sending default greeting") + history.add_turn_user("Hello!") + result = self.send_message(client, [max_search_tool_tool], system_prompt, history.get_turns()) + if isinstance(result, Response): # Error response + return result + if "content" in result: + django_logger.debug(f"βœ¨πŸ¦” Greeting response: {result['content']}") + history.add_turn_assistant(result["content"]) + return Response({"content": result["content"]}) + + # Add user message with proper structure + history.add_turn_user(user_input) + messages = history.get_turns() + django_logger.debug(f"βœ¨πŸ¦” Messages to send: {json.dumps(messages, indent=2)}") + full_response = "" + + # Send message with full history + django_logger.info("βœ¨πŸ¦” Sending initial message to Anthropic API") + result = self.send_message(client, [max_search_tool_tool], system_prompt, messages) + if isinstance(result, Response): # Error response + return result + django_logger.debug(f"βœ¨πŸ¦” Initial response from send_message: {json.dumps(result, indent=2)}") + + while result and "content" in result: + if result.get("stop_reason") == "tool_use": + django_logger.info("βœ¨πŸ¦” Processing tool use response") + # Handle tool use with dedicated method + response_part, tool_result = self._handle_tool_use(result, history) + full_response += response_part + messages.append(tool_result) + + # Get next response after tool use + django_logger.info("βœ¨πŸ¦” Sending follow-up message after tool use") + result = self.send_message(client, [max_search_tool_tool], system_prompt, history.get_turns()) + if isinstance(result, Response): # Error response + return result + else: + django_logger.info("βœ¨πŸ¦” Processing final response") + if isinstance(result["content"], list): + for block in result["content"]: + if block["type"] == "text": + full_response += block["text"] + "\n" + history.add_turn_assistant(result["content"]) + else: + full_response += result["content"] + history.add_turn_assistant(result["content"]) + break + + django_logger.info("βœ¨πŸ¦” Response successfully processed") + django_logger.debug( + f"βœ¨πŸ¦” Final response: {json.dumps({'content': full_response.strip(), 'session_id': session_id}, indent=2)}" + ) + return Response({"content": full_response.strip(), "session_id": session_id}) + + except Exception as e: + django_logger.error(f"βœ¨πŸ¦” Error in chat endpoint: {str(e)}", exc_info=True) + return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + @action(methods=["POST"], detail=False, url_path="chat", url_name="chat") + def chat(self, request: Request, **kwargs: Any) -> Response: + return self.create(request, **kwargs) + + def _get_headers(self) -> dict[str, str]: + """Get headers with container hostname from settings""" + headers = REQUIRED_HEADERS.copy() + headers["container_hostname"] = settings.CONTAINER_HOSTNAME + return headers + + def _cleanup_old_conversations(self): + """Remove conversations older than CONVERSATION_TIMEOUT""" + with self._cleanup_lock: + current_time = time.time() + expired = [ + session_id + for session_id, history in self.conversation_histories.items() + if (current_time - history.last_access) > self.CONVERSATION_TIMEOUT + ] + for session_id in expired: + del self.conversation_histories[session_id] + + def _get_conversation(self, session_id: str) -> ConversationHistory: + """Get or create conversation history with cleanup check""" + self._cleanup_old_conversations() + if session_id not in self.conversation_histories: + self.conversation_histories[session_id] = ConversationHistory() + history = self.conversation_histories[session_id] + history.touch() # Update last access time + return history + + def _format_system_prompt(self, prompt: str) -> list: + """Format system prompt with cache control.""" + return [{"type": "text", "text": prompt, "cache_control": {"type": "ephemeral"}}] + + def _format_user_message(self, content: str) -> dict: + """Format user message with proper structure.""" + return {"role": "user", "content": [{"type": "text", "text": content}]} + + def _format_tool_result(self, tool_use_id: str, content: str) -> dict: + """Format tool result with proper structure.""" + return { + "role": "user", + "content": [ + { + "type": "tool_result", + "tool_use_id": tool_use_id, + "content": content, + } + ], + } + + def _handle_rate_limit(self, retry_after: int) -> Response: + """Handle rate limit with DRF response.""" + return Response( + { + "error": "rate_limit_exceeded", + "message": "🫣 Uh-oh, I'm really popular today! I've hit my rate limit. I need to catch my breath, please try asking your question again after 30 seconds. πŸ¦”", + "retry_after": retry_after, + }, + status=status.HTTP_429_TOO_MANY_REQUESTS, + headers={"Retry-After": str(retry_after)}, + ) + + def _handle_tool_use(self, result: dict[str, Any], history: ConversationHistory) -> tuple[str, dict[str, Any]]: + """Handle tool use response from the API""" + full_response = "" + # Process text blocks that came before tool use + for block in result["content"]: + if block["type"] == "text": + full_response += block["text"] + "\n" + + tool_use_block = result["content"][-1] # Get the last tool use block + django_logger.info(f"Tool use requested: {tool_use_block}") + + query = tool_use_block["input"]["query"] + search_results = max_search_tool(query) + django_logger.debug(f"Search results for query '{query}': {search_results}") + + formatted_results = "\n".join( + [ + f"Text: {passage['text']}\nHeading: {passage['heading']}\n" + f"Source: {result_item['page_title']}\nURL: {passage['url']}\n" + for result_item in search_results + for passage in result_item["relevant_passages"] + ] + ) + + # Append assistant's response with content blocks + history.add_turn_assistant(result["content"]) + + # Return the formatted results and current response + return full_response, self._format_tool_result(tool_use_block["id"], formatted_results) + + def send_message(self, client, tools, system_prompt, messages): + """Send message to Anthropic API with proper error handling""" + try: + django_logger.info("Preparing to send message to Anthropic API") + try: + headers = self._get_headers() + django_logger.debug("API headers prepared successfully") + except Exception as e: + django_logger.error(f"Error preparing API headers: {str(e)}", exc_info=True) + raise + + response = client.messages.create( + model="claude-3-5-sonnet-20241022", + max_tokens=1024, + tools=tools, + system=system_prompt, + messages=messages, + headers=headers, + ) + + django_logger.debug(f"Response from Anthropic API: {response}") + + # The response is a Message object, not an HTTP response + return { + "content": response.content, + "stop_reason": response.stop_reason, + "usage": response.usage, + } + + except anthropic.RateLimitError as e: + django_logger.warning(f"Rate limit exceeded: {str(e)}") + retry_after = getattr(e, "retry_after", 30) + return self._handle_rate_limit(retry_after) + except Exception as e: + django_logger.error(f"Request to Anthropic API failed: {str(e)}", exc_info=True) + return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/ee/urls.py b/ee/urls.py index 91b58e0fcb238..78164db5cb268 100644 --- a/ee/urls.py +++ b/ee/urls.py @@ -1,16 +1,16 @@ from typing import Any - from django.conf import settings from django.contrib import admin from django.urls import include from django.urls.conf import path - +from django.views.decorators.csrf import csrf_exempt from ee.api import integration +from ee.support_sidebar_max.views import MaxChatViewSet +from .api.rbac import organization_resource_access, role from .api import ( authentication, billing, - conversation, dashboard_collaborator, explicit_team_member, feature_flag_role_access, @@ -19,20 +19,18 @@ sentry_stats, subscription, ) -from .api.rbac import organization_resource_access, role from .session_recordings import session_recording_playlist def extend_api_router() -> None: from posthog.api import ( - environment_dashboards_router, - environments_router, - legacy_project_dashboards_router, + router as root_router, + register_grandfathered_environment_nested_viewset, + projects_router, organizations_router, project_feature_flags_router, - projects_router, - register_grandfathered_environment_nested_viewset, - router as root_router, + environment_dashboards_router, + legacy_project_dashboards_router, ) root_router.register(r"billing", billing.BillingViewset, "billing") @@ -95,10 +93,6 @@ def extend_api_router() -> None: ["project_id"], ) - environments_router.register( - r"conversations", conversation.ConversationViewSet, "environment_conversations", ["team_id"] - ) - # The admin interface is disabled on self-hosted instances, as its misuse can be unsafe admin_urlpatterns = ( @@ -109,5 +103,6 @@ def extend_api_router() -> None: urlpatterns: list[Any] = [ path("api/saml/metadata/", authentication.saml_metadata_view), path("api/sentry_stats/", sentry_stats.sentry_stats), + path("max/chat/", csrf_exempt(MaxChatViewSet.as_view({"post": "create"})), name="max_chat"), *admin_urlpatterns, ] diff --git a/frontend/@posthog/apps-common/pnpm-lock.yaml b/frontend/@posthog/apps-common/pnpm-lock.yaml index a7fe81bb2fa68..c359197b333c2 100644 --- a/frontend/@posthog/apps-common/pnpm-lock.yaml +++ b/frontend/@posthog/apps-common/pnpm-lock.yaml @@ -1,620 +1,613 @@ -lockfileVersion: 5.4 +lockfileVersion: '9.0' -specifiers: - tsup: ^5.12.8 - typescript: '>=4.0.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false -devDependencies: - tsup: 5.12.9_typescript@4.8.4 - typescript: 4.8.4 +importers: + + .: + dependencies: + '@posthog/lemon-ui': + specifier: '*' + version: 0.0.0(antd@5.22.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(kea-router@3.2.0(kea@3.1.6(react@19.0.0)))(kea@3.1.6(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + kea: + specifier: '*' + version: 3.1.6(react@19.0.0) + kea-router: + specifier: '*' + version: 3.2.0(kea@3.1.6(react@19.0.0)) + react: + specifier: '*' + version: 19.0.0 + react-dom: + specifier: '*' + version: 19.0.0(react@19.0.0) + devDependencies: + tsup: + specifier: ^5.12.8 + version: 5.12.9(typescript@5.7.2) + typescript: + specifier: '>=4.0.0' + version: 5.7.2 packages: - /@esbuild/linux-loong64/0.14.54: + '@ant-design/colors@7.1.0': + resolution: {integrity: sha512-MMoDGWn1y9LdQJQSHiCC20x3uZ3CwQnv9QMz6pCmJOrqdgM9YxsoVVY0wtrdXbmfSgnV0KNk6zi09NAhMR2jvg==} + + '@ant-design/cssinjs-utils@1.1.3': + resolution: {integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@ant-design/cssinjs@1.22.1': + resolution: {integrity: sha512-SLuXM4wiEE1blOx94iXrkOgseMZHzdr4ngdFu3VVDq6AOWh7rlwqTkMAtJho3EsBF6x/eUGOtK53VZXGQG7+sQ==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@2.0.6': + resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} + + '@ant-design/icons@5.5.2': + resolution: {integrity: sha512-xc53rjVBl9v2BqFxUjZGti/RfdDeA8/6KYglmInM2PNqSXc/WfuGDTifJI/ZsokJK0aeKvOIbXc9y2g8ILAhEA==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/react-slick@1.1.2': + resolution: {integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==} + peerDependencies: + react: '>=16.9.0' + + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + engines: {node: '>=6.9.0'} + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + + '@esbuild/linux-loong64@0.14.54': resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@nodelib/fs.scandir/2.1.5: + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - /@nodelib/fs.stat/2.0.5: + '@nodelib/fs.stat@2.0.5': resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true - /@nodelib/fs.walk/1.2.8: + '@nodelib/fs.walk@1.2.8': resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.13.0 - dev: true - /any-promise/1.3.0: + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@posthog/lemon-ui@0.0.0': + resolution: {integrity: sha512-SJaquP2UGsfOx33d7SssxNvUTfDT5VWWBEzWyHvJXaFrHNZd3ZeRrHLzsETXjhJO3ZcsVdoJD+tm27B6Qe5GuQ==} + peerDependencies: + antd: '*' + kea: '*' + kea-router: '*' + react: '*' + react-dom: '*' + + '@rc-component/async-validator@5.0.4': + resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==} + engines: {node: '>=14.x'} + + '@rc-component/color-picker@2.0.1': + resolution: {integrity: sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@1.4.0': + resolution: {integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.0': + resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==} + engines: {node: '>=8.x'} + + '@rc-component/mutate-observer@1.1.0': + resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/portal@1.1.2': + resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.0.0': + resolution: {integrity: sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tour@1.15.1': + resolution: {integrity: sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/trigger@2.2.6': + resolution: {integrity: sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + antd@5.22.5: + resolution: {integrity: sha512-+0UP8w+ULVv2OIzCDVz7j6I0UfH6mMLHSWO6qzpBc+9psOoVQLRbyAE21XnZM/eGrt2MNsEDL5fmlhXL/V8JyQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: true - /anymatch/3.1.2: - resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: true - /array-union/2.1.0: + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - dev: true - /balanced-match/1.0.2: + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - /binary-extensions/2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - dev: true - /brace-expansion/1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - /braces/3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - /bundle-require/3.1.2_esbuild@0.14.54: + bundle-require@3.1.2: resolution: {integrity: sha512-Of6l6JBAxiyQ5axFxUM6dYeP/W7X2Sozeo/4EYB9sJhL+dqL7TKjg+shwxp6jlu/6ZSERfsYtIpSJ1/x3XkAEA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: esbuild: '>=0.13' - dependencies: - esbuild: 0.14.54 - load-tsconfig: 0.2.3 - dev: true - /cac/6.7.14: + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - dev: true - /chokidar/3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.2 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true - /commander/4.1.1: + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - dev: true - /concat-map/0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true + compute-scroll-into-view@3.1.0: + resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==} - /cross-spawn/7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - /debug/4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' peerDependenciesMeta: supports-color: optional: true - dependencies: - ms: 2.1.2 - dev: true - /dir-glob/3.0.1: + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - dev: true - /esbuild-android-64/0.14.54: + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + esbuild-android-64@0.14.54: resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} engines: {node: '>=12'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /esbuild-android-arm64/0.14.54: + esbuild-android-arm64@0.14.54: resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} engines: {node: '>=12'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /esbuild-darwin-64/0.14.54: + esbuild-darwin-64@0.14.54: resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /esbuild-darwin-arm64/0.14.54: + esbuild-darwin-arm64@0.14.54: resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /esbuild-freebsd-64/0.14.54: + esbuild-freebsd-64@0.14.54: resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /esbuild-freebsd-arm64/0.14.54: + esbuild-freebsd-arm64@0.14.54: resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /esbuild-linux-32/0.14.54: + esbuild-linux-32@0.14.54: resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /esbuild-linux-64/0.14.54: + esbuild-linux-64@0.14.54: resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} engines: {node: '>=12'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /esbuild-linux-arm/0.14.54: - resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} + esbuild-linux-arm64@0.14.54: + resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} engines: {node: '>=12'} - cpu: [arm] + cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /esbuild-linux-arm64/0.14.54: - resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} + esbuild-linux-arm@0.14.54: + resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} engines: {node: '>=12'} - cpu: [arm64] + cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /esbuild-linux-mips64le/0.14.54: + esbuild-linux-mips64le@0.14.54: resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /esbuild-linux-ppc64le/0.14.54: + esbuild-linux-ppc64le@0.14.54: resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /esbuild-linux-riscv64/0.14.54: + esbuild-linux-riscv64@0.14.54: resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /esbuild-linux-s390x/0.14.54: + esbuild-linux-s390x@0.14.54: resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /esbuild-netbsd-64/0.14.54: + esbuild-netbsd-64@0.14.54: resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /esbuild-openbsd-64/0.14.54: + esbuild-openbsd-64@0.14.54: resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /esbuild-sunos-64/0.14.54: + esbuild-sunos-64@0.14.54: resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /esbuild-windows-32/0.14.54: + esbuild-windows-32@0.14.54: resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /esbuild-windows-64/0.14.54: + esbuild-windows-64@0.14.54: resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /esbuild-windows-arm64/0.14.54: + esbuild-windows-arm64@0.14.54: resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /esbuild/0.14.54: + esbuild@0.14.54: resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} engines: {node: '>=12'} hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/linux-loong64': 0.14.54 - esbuild-android-64: 0.14.54 - esbuild-android-arm64: 0.14.54 - esbuild-darwin-64: 0.14.54 - esbuild-darwin-arm64: 0.14.54 - esbuild-freebsd-64: 0.14.54 - esbuild-freebsd-arm64: 0.14.54 - esbuild-linux-32: 0.14.54 - esbuild-linux-64: 0.14.54 - esbuild-linux-arm: 0.14.54 - esbuild-linux-arm64: 0.14.54 - esbuild-linux-mips64le: 0.14.54 - esbuild-linux-ppc64le: 0.14.54 - esbuild-linux-riscv64: 0.14.54 - esbuild-linux-s390x: 0.14.54 - esbuild-netbsd-64: 0.14.54 - esbuild-openbsd-64: 0.14.54 - esbuild-sunos-64: 0.14.54 - esbuild-windows-32: 0.14.54 - esbuild-windows-64: 0.14.54 - esbuild-windows-arm64: 0.14.54 - dev: true - /execa/5.1.1: + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - dev: true - /fast-glob/3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: true - /fastq/1.13.0: - resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} - dependencies: - reusify: 1.0.4 - dev: true + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - /fill-range/7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - /fs.realpath/1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} - /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - requiresBuild: true - dev: true - optional: true - /get-stream/6.0.1: + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - dev: true - /glob-parent/5.1.2: + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - /glob/7.1.6: - resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true - /globby/11.1.0: + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.2.12 - ignore: 5.2.0 - merge2: 1.4.1 - slash: 3.0.0 - dev: true - /human-signals/2.1.0: + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - dev: true - /ignore/5.2.0: - resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - dev: true - - /inflight/1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true - - /inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - /is-binary-path/2.1.0: + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: true - /is-extglob/2.1.1: + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - dev: true - /is-glob/4.0.3: + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - /is-number/7.0.0: + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true - /is-stream/2.0.1: + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - dev: true - /isexe/2.0.0: + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - /joycon/3.1.1: + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - dev: true - /lilconfig/2.0.6: - resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} + + kea-router@3.2.0: + resolution: {integrity: sha512-TZVHrQQNhD7tSL05xEXoNCeYu/vwwvxBc316pgwi5z8cH61MtCcUBUEZnWr2Nd7jSBDgxA/GRHpqai0YXq0T8w==} + peerDependencies: + kea: '>= 3' + + kea@3.1.6: + resolution: {integrity: sha512-EftYkqGyI8hdC8Eo9r6jGtUSxdykNe1L+Cpu+7t/SEWc9f3OPFtNFPF+hdxdYBeZp2Je3EY6BAEQfWhP4mbUPA==} + peerDependencies: + react: '>= 16.8' + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} - dev: true - /lines-and-columns/1.2.4: + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true - /load-tsconfig/0.2.3: - resolution: {integrity: sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ==} + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /lodash.sortby/4.7.0: + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - dev: true - /merge-stream/2.0.0: + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true - /merge2/1.4.1: + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dev: true - /micromatch/4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - dev: true - /mimic-fn/2.1.0: + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - dev: true - /minimatch/3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} - /ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /mz/2.7.0: + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - dev: true - /normalize-path/3.0.0: + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - dev: true - /npm-run-path/4.0.1: + npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - dependencies: - path-key: 3.1.1 - dev: true - /object-assign/4.1.1: + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true - - /once/1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true - /onetime/5.1.2: + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - dependencies: - mimic-fn: 2.1.0 - dev: true - /path-is-absolute/1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - /path-key/3.1.1: + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true - /path-type/4.0.0: + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - dev: true - /picomatch/2.3.1: + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true - /pirates/4.0.5: - resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - dev: true - /postcss-load-config/3.1.4: + postcss-load-config@3.1.4: resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} peerDependencies: @@ -625,133 +618,375 @@ packages: optional: true ts-node: optional: true - dependencies: - lilconfig: 2.0.6 - yaml: 1.10.2 - dev: true - /punycode/2.1.1: - resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: true - /queue-microtask/1.2.3: + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - /readdirp/3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: true + rc-cascader@3.30.0: + resolution: {integrity: sha512-rrzSbk1Bdqbu+pDwiLCLHu72+lwX9BZ28+JKzoi0DWZ4N29QYFeip8Gctl33QVd2Xg3Rf14D3yAOG76ElJw16w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /resolve-from/5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - dev: true + rc-checkbox@3.3.0: + resolution: {integrity: sha512-Ih3ZaAcoAiFKJjifzwsGiT/f/quIkxJoklW4yKGho14Olulwn8gN7hOBve0/WGDg5o/l/5mL0w7ff7/YGvefVw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /reusify/1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true + rc-collapse@3.9.0: + resolution: {integrity: sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /rollup/2.79.1: - resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} - engines: {node: '>=10.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true + rc-dialog@9.6.0: + resolution: {integrity: sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /run-parallel/1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true + rc-drawer@7.2.0: + resolution: {integrity: sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /shebang-command/2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true + rc-dropdown@4.2.1: + resolution: {integrity: sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' - /shebang-regex/3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true + rc-field-form@2.7.0: + resolution: {integrity: sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /signal-exit/3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true + rc-image@7.11.0: + resolution: {integrity: sha512-aZkTEZXqeqfPZtnSdNUnKQA0N/3MbgR7nUnZ+/4MfSFWPFHZau4p5r5ShaI0KPEMnNjv4kijSCFq/9wtJpwykw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /slash/3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true + rc-input-number@9.3.0: + resolution: {integrity: sha512-JQ363ywqRyxwgVxpg2z2kja3CehTpYdqR7emJ/6yJjRdbvo+RvfE83fcpBCIJRq3zLp8SakmEXq60qzWyZ7Usw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /source-map/0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} - dependencies: - whatwg-url: 7.1.0 - dev: true + rc-input@1.6.4: + resolution: {integrity: sha512-lBZhfRD4NSAUW0zOKLUeI6GJuXkxeZYi0hr8VcJgJpyTNOvHw1ysrKWAHcEOAAHj7guxgmWYSi6xWrEdfrSAsA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' - /strip-final-newline/2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - dev: true + rc-mentions@2.17.0: + resolution: {integrity: sha512-sfHy+qLvc+p8jx8GUsujZWXDOIlIimp6YQz7N5ONQ6bHsa2kyG+BLa5k2wuxgebBbH97is33wxiyq5UkiXRpHA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /sucrase/3.28.0: - resolution: {integrity: sha512-TK9600YInjuiIhVM3729rH4ZKPOsGeyXUwY+Ugu9eilNbdTFyHr6XcAGYbRVZPDgWj6tgI7bx95aaJjHnbffag==} - engines: {node: '>=8'} - hasBin: true - dependencies: - commander: 4.1.1 - glob: 7.1.6 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.5 - ts-interface-checker: 0.1.13 - dev: true + rc-menu@9.16.0: + resolution: {integrity: sha512-vAL0yqPkmXWk3+YKRkmIR8TYj3RVdEt3ptG2jCJXWNAvQbT0VJJdRyHZ7kG/l1JsZlB+VJq/VcYOo69VR4oD+w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /thenify-all/1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - dependencies: - thenify: 3.3.1 - dev: true + rc-motion@2.9.4: + resolution: {integrity: sha512-TAPUUufDqhPO669qJobI0d9U0XZ/VPNQyZTivUxxzU1EyuPe3PtHSx7Kb902KuzQgu7sS18z8GguaxZEALV/ww==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' - /thenify/3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - dependencies: - any-promise: 1.3.0 - dev: true + rc-notification@5.6.2: + resolution: {integrity: sha512-Id4IYMoii3zzrG0lB0gD6dPgJx4Iu95Xu0BQrhHIbp7ZnAZbLqdqQ73aIWH0d0UFcElxwaKjnzNovTjo7kXz7g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-overflow@1.3.2: + resolution: {integrity: sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-pagination@5.0.0: + resolution: {integrity: sha512-QjrPvbAQwps93iluvFM62AEYglGYhWW2q/nliQqmvkTi4PXP4HHoh00iC1Sa5LLVmtWQHmG73fBi2x6H6vFHRg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-picker@4.8.3: + resolution: {integrity: sha512-hJ45qoEs4mfxXPAJdp1n3sKwADul874Cd0/HwnsEOE60H+tgiJUGgbOD62As3EG/rFVNS5AWRfBCDJJfmRqOVQ==} + engines: {node: '>=8.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + rc-progress@4.0.0: + resolution: {integrity: sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-rate@2.13.0: + resolution: {integrity: sha512-oxvx1Q5k5wD30sjN5tqAyWTvJfLNNJn7Oq3IeS4HxWfAiC4BOXMITNAsw7u/fzdtO4MS8Ki8uRLOzcnEuoQiAw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-resize-observer@1.4.1: + resolution: {integrity: sha512-JbAeFDsaaZRPwaTlXnCqgeO9c6E7qoaE/hxsub08cdnnPn6767c/j9+r/TifUdfvwXtdcfHygKbZ7ecM/PXo/Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-segmented@2.5.0: + resolution: {integrity: sha512-B28Fe3J9iUFOhFJET3RoXAPFJ2u47QvLSYcZWC4tFYNGPEjug5LAxEasZlA/PpAxhdOPqGWsGbSj7ftneukJnw==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-select@14.16.4: + resolution: {integrity: sha512-jP6qf7+vjnxGvPpfPWbGYfFlSl3h8L2XcD4O7g2GYXmEeBC0mw+nPD7i++OOE8v3YGqP8xtYjRKAWCMLfjgxlw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-slider@11.1.7: + resolution: {integrity: sha512-ytYbZei81TX7otdC0QvoYD72XSlxvTihNth5OeZ6PMXyEDq/vHdWFulQmfDGyXK1NwKwSlKgpvINOa88uT5g2A==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-steps@6.0.1: + resolution: {integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-switch@4.1.0: + resolution: {integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-table@7.49.0: + resolution: {integrity: sha512-/FoPLX94muAQOxVpi1jhnpKjOIqUbT81eELQPAzSXOke4ky4oCWYUXOcVpL31ZCO90xScwVSXRd7coqtgtB1Ng==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tabs@15.4.0: + resolution: {integrity: sha512-llKuyiAVqmXm2z7OrmhX5cNb2ueZaL8ZyA2P4R+6/72NYYcbEgOXibwHiQCFY2RiN3swXl53SIABi2CumUS02g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-textarea@1.8.2: + resolution: {integrity: sha512-UFAezAqltyR00a8Lf0IPAyTd29Jj9ee8wt8DqXyDMal7r/Cg/nDt3e1OOv3Th4W6mKaZijjgwuPXhAfVNTN8sw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tooltip@6.2.1: + resolution: {integrity: sha512-rws0duD/3sHHsD905Nex7FvoUGy2UBQRhTkKxeEvr2FB+r21HsOxcDJI0TzyO8NHhnAA8ILr8pfbSBg5Jj5KBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tree-select@5.24.5: + resolution: {integrity: sha512-PnyR8LZJWaiEFw0SHRqo4MNQWyyZsyMs8eNmo68uXZWjxc7QqeWcjPPoONN0rc90c3HZqGF9z+Roz+GLzY5GXA==} + peerDependencies: + react: '*' + react-dom: '*' + + rc-tree@5.10.1: + resolution: {integrity: sha512-FPXb3tT/u39mgjr6JNlHaUTYfHkVGW56XaGDahDpEFLGsnPxGcVLNTjcqoQb/GNbSCycl7tD7EvIymwOTP0+Yw==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-upload@4.8.1: + resolution: {integrity: sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-util@5.44.2: + resolution: {integrity: sha512-uGSk3hpPBLa3/0QAcKhCjgl4SFnhQCJDLvvpoLdbR6KgDuXrujG+dQaUeUvBJr2ZWak1O/9n+cYbJiWmmk95EQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-virtual-list@3.16.1: + resolution: {integrity: sha512-algM5UsB7vrlPNr9lsZEH8s9KHkP8XbT/Y0qylyPkiM8mIOlSJLjBNADcmbYPEQCm4zW82mZRJuVHNzqqN0EAQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + peerDependencies: + react: ^19.0.0 + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redux@4.2.1: + resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + reselect@4.1.8: + resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@2.79.2: + resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==} + engines: {node: '>=10.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + stylis@4.3.4: + resolution: {integrity: sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - /to-regex-range/5.0.1: + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - /tr46/1.0.1: + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + + tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - dependencies: - punycode: 2.1.1 - dev: true - /tree-kill/1.2.2: + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - dev: true - /ts-interface-checker/0.1.13: + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: true - /tsup/5.12.9_typescript@4.8.4: + tsup@5.12.9: resolution: {integrity: sha512-dUpuouWZYe40lLufo64qEhDpIDsWhRbr2expv5dHEMjwqeKJS2aXA/FPqs1dxO4T6mBojo7rvo3jP9NNzaKyDg==} hasBin: true peerDependencies: @@ -765,58 +1000,1122 @@ packages: optional: true typescript: optional: true - dependencies: - bundle-require: 3.1.2_esbuild@0.14.54 - cac: 6.7.14 - chokidar: 3.5.3 - debug: 4.3.4 - esbuild: 0.14.54 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 3.1.4 - resolve-from: 5.0.0 - rollup: 2.79.1 - source-map: 0.8.0-beta.0 - sucrase: 3.28.0 - tree-kill: 1.2.2 - typescript: 4.8.4 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true - /typescript/4.8.4: - resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} - engines: {node: '>=4.2.0'} + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} hasBin: true - dev: true - /webidl-conversions/4.0.2: + url-pattern@1.0.3: + resolution: {integrity: sha512-uQcEj/2puA4aq1R3A2+VNVBgaWYR24FdWjl7VNW83rnWftlhyzOZ/tBjezRiC2UkIzuxC8Top3IekN3vUf1WxA==} + engines: {node: '>=0.12.0'} + + use-sync-external-store@1.4.0: + resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - dev: true - /whatwg-url/7.1.0: + whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - dev: true - /which/2.0.2: + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - /wrappy/1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} - /yaml/1.10.2: + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - dev: true + +snapshots: + + '@ant-design/colors@7.1.0': + dependencies: + '@ctrl/tinycolor': 3.6.1 + + '@ant-design/cssinjs-utils@1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@ant-design/cssinjs': 1.22.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@babel/runtime': 7.26.0 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@ant-design/cssinjs@1.22.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.5.1 + csstype: 3.1.3 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + stylis: 4.3.4 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.26.0 + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@5.5.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@ant-design/colors': 7.1.0 + '@ant-design/icons-svg': 4.4.2 + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@ant-design/react-slick@1.1.2(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + json2mq: 0.2.0 + react: 19.0.0 + resize-observer-polyfill: 1.5.1 + throttle-debounce: 5.0.2 + + '@babel/runtime@7.26.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@ctrl/tinycolor@3.6.1': {} + + '@emotion/hash@0.8.0': {} + + '@emotion/unitless@0.7.5': {} + + '@esbuild/linux-loong64@0.14.54': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@posthog/lemon-ui@0.0.0(antd@5.22.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(kea-router@3.2.0(kea@3.1.6(react@19.0.0)))(kea@3.1.6(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + antd: 5.22.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + kea: 3.1.6(react@19.0.0) + kea-router: 3.2.0(kea@3.1.6(react@19.0.0)) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@rc-component/async-validator@5.0.4': + dependencies: + '@babel/runtime': 7.26.0 + + '@rc-component/color-picker@2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@ant-design/fast-color': 2.0.6 + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@rc-component/context@1.4.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@rc-component/mini-decimal@1.1.0': + dependencies: + '@babel/runtime': 7.26.0 + + '@rc-component/mutate-observer@1.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@rc-component/portal@1.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@rc-component/qrcode@1.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@rc-component/tour@1.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@rc-component/trigger': 2.2.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@rc-component/trigger@2.2.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-resize-observer: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + antd@5.22.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@ant-design/colors': 7.1.0 + '@ant-design/cssinjs': 1.22.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@ant-design/cssinjs-utils': 1.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@ant-design/icons': 5.5.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@ant-design/react-slick': 1.1.2(react@19.0.0) + '@babel/runtime': 7.26.0 + '@ctrl/tinycolor': 3.6.1 + '@rc-component/color-picker': 2.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@rc-component/mutate-observer': 1.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@rc-component/qrcode': 1.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@rc-component/tour': 1.15.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@rc-component/trigger': 2.2.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + copy-to-clipboard: 3.3.3 + dayjs: 1.11.13 + rc-cascader: 3.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-checkbox: 3.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-collapse: 3.9.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-dialog: 9.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-drawer: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-dropdown: 4.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-field-form: 2.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-image: 7.11.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-input: 1.6.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-input-number: 9.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-mentions: 2.17.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-menu: 9.16.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-notification: 5.6.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-pagination: 5.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-picker: 4.8.3(dayjs@1.11.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-progress: 4.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-rate: 2.13.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-resize-observer: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-segmented: 2.5.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-select: 14.16.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-slider: 11.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-steps: 6.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-switch: 4.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-table: 7.49.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-tabs: 15.4.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-textarea: 1.8.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-tooltip: 6.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-tree: 5.10.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-tree-select: 5.24.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-upload: 4.8.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + array-union@2.1.0: {} + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + bundle-require@3.1.2(esbuild@0.14.54): + dependencies: + esbuild: 0.14.54 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + classnames@2.5.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + compute-scroll-into-view@3.1.0: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.1.3: {} + + dayjs@1.11.13: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + esbuild-android-64@0.14.54: + optional: true + + esbuild-android-arm64@0.14.54: + optional: true + + esbuild-darwin-64@0.14.54: + optional: true + + esbuild-darwin-arm64@0.14.54: + optional: true + + esbuild-freebsd-64@0.14.54: + optional: true + + esbuild-freebsd-arm64@0.14.54: + optional: true + + esbuild-linux-32@0.14.54: + optional: true + + esbuild-linux-64@0.14.54: + optional: true + + esbuild-linux-arm64@0.14.54: + optional: true + + esbuild-linux-arm@0.14.54: + optional: true + + esbuild-linux-mips64le@0.14.54: + optional: true + + esbuild-linux-ppc64le@0.14.54: + optional: true + + esbuild-linux-riscv64@0.14.54: + optional: true + + esbuild-linux-s390x@0.14.54: + optional: true + + esbuild-netbsd-64@0.14.54: + optional: true + + esbuild-openbsd-64@0.14.54: + optional: true + + esbuild-sunos-64@0.14.54: + optional: true + + esbuild-windows-32@0.14.54: + optional: true + + esbuild-windows-64@0.14.54: + optional: true + + esbuild-windows-arm64@0.14.54: + optional: true + + esbuild@0.14.54: + optionalDependencies: + '@esbuild/linux-loong64': 0.14.54 + esbuild-android-64: 0.14.54 + esbuild-android-arm64: 0.14.54 + esbuild-darwin-64: 0.14.54 + esbuild-darwin-arm64: 0.14.54 + esbuild-freebsd-64: 0.14.54 + esbuild-freebsd-arm64: 0.14.54 + esbuild-linux-32: 0.14.54 + esbuild-linux-64: 0.14.54 + esbuild-linux-arm: 0.14.54 + esbuild-linux-arm64: 0.14.54 + esbuild-linux-mips64le: 0.14.54 + esbuild-linux-ppc64le: 0.14.54 + esbuild-linux-riscv64: 0.14.54 + esbuild-linux-s390x: 0.14.54 + esbuild-netbsd-64: 0.14.54 + esbuild-openbsd-64: 0.14.54 + esbuild-sunos-64: 0.14.54 + esbuild-windows-32: 0.14.54 + esbuild-windows-64: 0.14.54 + esbuild-windows-arm64: 0.14.54 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fsevents@2.3.3: + optional: true + + get-stream@6.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + human-signals@2.1.0: {} + + ignore@5.3.2: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-stream@2.0.1: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + joycon@3.1.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + kea-router@3.2.0(kea@3.1.6(react@19.0.0)): + dependencies: + kea: 3.1.6(react@19.0.0) + url-pattern: 1.0.3 + + kea@3.1.6(react@19.0.0): + dependencies: + react: 19.0.0 + redux: 4.2.1 + reselect: 4.1.8 + use-sync-external-store: 1.4.0(react@19.0.0) + + lilconfig@2.1.0: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + lodash.sortby@4.7.0: {} + + lru-cache@10.4.3: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-fn@2.1.0: {} + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-assign@4.1.1: {} + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + package-json-from-dist@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + picomatch@2.3.1: {} + + pirates@4.0.6: {} + + postcss-load-config@3.1.4: + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + rc-cascader@3.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-select: 14.16.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-tree: 5.10.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-checkbox@3.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-collapse@3.9.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-dialog@9.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-drawer@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-dropdown@4.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.2.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-field-form@2.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/async-validator': 5.0.4 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-image@7.11.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/portal': 1.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-dialog: 9.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-input-number@9.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/mini-decimal': 1.1.0 + classnames: 2.5.1 + rc-input: 1.6.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-input@1.6.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-mentions@2.17.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.2.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-input: 1.6.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-menu: 9.16.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-textarea: 1.8.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-menu@9.16.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.2.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-overflow: 1.3.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-motion@2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-notification@5.6.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-overflow@1.3.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-resize-observer: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-pagination@5.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-picker@4.8.3(dayjs@1.11.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.2.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-overflow: 1.3.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-resize-observer: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + dayjs: 1.11.13 + + rc-progress@4.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-rate@2.13.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-resize-observer@1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + resize-observer-polyfill: 1.5.1 + + rc-segmented@2.5.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-select@14.16.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.2.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-overflow: 1.3.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-virtual-list: 3.16.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-slider@11.1.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-steps@6.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-switch@4.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-table@7.49.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/context': 1.4.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + rc-resize-observer: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-virtual-list: 3.16.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-tabs@15.4.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-dropdown: 4.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-menu: 9.16.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-resize-observer: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-textarea@1.8.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-input: 1.6.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-resize-observer: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-tooltip@6.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + '@rc-component/trigger': 2.2.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + classnames: 2.5.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-tree-select@5.24.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-select: 14.16.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-tree: 5.10.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-tree@5.10.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-motion: 2.9.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-virtual-list: 3.16.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-upload@4.8.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + rc-util@5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-is: 18.3.1 + + rc-virtual-list@3.16.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + classnames: 2.5.1 + rc-resize-observer: 1.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + rc-util: 5.44.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + react-dom@19.0.0(react@19.0.0): + dependencies: + react: 19.0.0 + scheduler: 0.25.0 + + react-is@18.3.1: {} + + react@19.0.0: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + redux@4.2.1: + dependencies: + '@babel/runtime': 7.26.0 + + regenerator-runtime@0.14.1: {} + + reselect@4.1.8: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-from@5.0.0: {} + + reusify@1.0.4: {} + + rollup@2.79.2: + optionalDependencies: + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.25.0: {} + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + string-convert@0.2.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-final-newline@2.0.0: {} + + stylis@4.3.4: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + throttle-debounce@5.0.2: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tsup@5.12.9(typescript@5.7.2): + dependencies: + bundle-require: 3.1.2(esbuild@0.14.54) + cac: 6.7.14 + chokidar: 3.6.0 + debug: 4.4.0 + esbuild: 0.14.54 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 3.1.4 + resolve-from: 5.0.0 + rollup: 2.79.2 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tree-kill: 1.2.2 + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color + - ts-node + + typescript@5.7.2: {} + + url-pattern@1.0.3: {} + + use-sync-external-store@1.4.0(react@19.0.0): + dependencies: + react: 19.0.0 + + webidl-conversions@4.0.2: {} + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yaml@1.10.2: {} diff --git a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--dark.png b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--dark.png index 03fe4c6ea65ee..86e174a35b133 100644 Binary files a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--dark.png and b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--light.png b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--light.png index 1c3d88882025b..ebfa8fb83e3c4 100644 Binary files a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--light.png and b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--light.png differ diff --git a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--dark.png b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--dark.png index 0a32462d3ed9e..138b7936c4170 100644 Binary files a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--dark.png and b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--light.png b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--light.png index 0aae7580cd5c2..72d5a07bbae98 100644 Binary files a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--light.png and b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--light.png differ diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx index 7701538ffd36c..000310fd8a093 100644 --- a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx +++ b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx @@ -1,5 +1,6 @@ import { IconAI, + IconBook, IconChevronDown, IconDatabase, IconFeatures, @@ -16,6 +17,7 @@ import { } from '@posthog/icons' import { LemonBanner, LemonButton, Link } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' +import { FlaggedFeature } from 'lib/components/FlaggedFeature' import { SupportForm } from 'lib/components/Support/SupportForm' import { getPublicSupportSnippet, supportLogic } from 'lib/components/Support/supportLogic' import { FEATURE_FLAGS } from 'lib/constants' @@ -30,8 +32,18 @@ import { urls } from 'scenes/urls' import { AvailableFeature, ProductKey, SidePanelTab } from '~/types' import AlgoliaSearch from '../../components/AlgoliaSearch' -import { SidePanelPaneHeader } from '../components/SidePanelPaneHeader' -import { SIDE_PANEL_TABS } from '../SidePanel' +import { MaxChatInterface } from './sidePanelMaxChatInterface' +{ + /* the next two imports are on hold until after MVP */ +} +{ + /*import { SidePanelPaneHeader } from '../components/SidePanelPaneHeader'*/ +} +{ + /*import { SIDE_PANEL_TABS } from '../SidePanel'*/ +} +import { LemonCollapse } from '@posthog/lemon-ui' + import { sidePanelStateLogic } from '../sidePanelStateLogic' import { sidePanelStatusLogic } from './sidePanelStatusLogic' @@ -91,8 +103,27 @@ const PRODUCTS = [ const Section = ({ title, children }: { title: string; children: React.ReactNode }): React.ReactElement => { return (
-

{title}

- {children} + {title === 'Explore the docs' ? ( + + + {title} + + ), + content: children, + }, + ]} + /> + ) : ( + <> +

{title}

+ {children} + + )}
) } @@ -183,19 +214,30 @@ export const SidePanelSupport = (): JSX.Element => { const { status } = useValues(sidePanelStatusLogic) const theLogic = supportLogic({ onClose: () => closeSidePanel(SidePanelTab.Support) }) - const { openEmailForm, closeEmailForm } = useActions(theLogic) - const { title, isEmailFormOpen } = useValues(theLogic) + const { openEmailForm, closeEmailForm, openMaxChatInterface, closeMaxChatInterface } = useActions(theLogic) + const { isEmailFormOpen, isMaxChatInterfaceOpen } = useValues(theLogic) const region = preflight?.region return ( <> - -
{isEmailFormOpen ? ( closeEmailForm()} /> + ) : isMaxChatInterfaceOpen ? ( +
+ + closeMaxChatInterface()} + fullWidth + center + className="mt-2" + > + End Chat + +
) : ( <>
@@ -247,8 +289,33 @@ export const SidePanelSupport = (): JSX.Element => {
) : null} - {/* only allow opening tickets on our Cloud instances */} - {isCloud ? ( + {isCloud || true ? ( + +
+ <> +

+ Max is PostHog's support AI who can answer support questions, help you + with troubleshooting, find info in our documentation, write HogQL + queries, regex expressions, etc. +

+ { + openMaxChatInterface() + }} + targetBlank={false} + className="mt-2" + > + ✨ Chat with Max + + +
+
+ ) : null} + + {isCloud || true ? (

Can't find what you need in the docs?

{
) : null} +

Questions about features, how-tos, or use cases? There are thousands of discussions diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxAILogic.ts b/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxAILogic.ts new file mode 100644 index 0000000000000..91d69ee329498 --- /dev/null +++ b/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxAILogic.ts @@ -0,0 +1,136 @@ +import { actions, kea, path, reducers } from 'kea' +import { loaders } from 'kea-loaders' + +import type { sidePanelMaxAILogicType } from './sidePanelMaxAILogicType' +import { sidePanelMaxAPI } from './sidePanelMaxAPI' + +export interface ChatMessage { + role: 'user' | 'assistant' + content: string + timestamp: string + isRateLimited?: boolean + isError?: boolean +} + +interface MaxResponse { + content: string | { text: string; type: string } + isRateLimited?: boolean + isError?: boolean +} + +export const sidePanelMaxAILogic = kea([ + path(['scenes', 'navigation', 'sidepanel', 'sidePanelMaxAILogic']), + + actions({ + submitMessage: (message: string) => ({ message }), + clearChatHistory: true, + appendAssistantMessage: (content: string) => ({ content }), + setSearchingThinking: (isSearching: boolean) => ({ isSearching }), + setRateLimited: (isLimited: boolean) => ({ isLimited }), + setServerError: (isError: boolean) => ({ isError }), + }), + + reducers({ + currentMessages: [ + [] as ChatMessage[], + { + submitMessage: (state, { message }) => + message.trim() + ? [ + ...state, + { + role: 'user', + content: message, + timestamp: new Date().toISOString(), + }, + ] + : state, + appendAssistantMessage: (state, { content }) => [ + ...state, + { + role: 'assistant', + content, + timestamp: new Date().toISOString(), + isRateLimited: content.includes('Rate limit exceeded') || content.includes('rate-limited'), + isError: + content.includes('connect to the Anthropic API') || + content.includes('status.anthropic.com'), + }, + ], + clearChatHistory: () => [], + }, + ], + isSearchingThinking: [ + false, + { + setSearchingThinking: (_, { isSearching }) => isSearching, + }, + ], + isRateLimited: [ + false, + { + setRateLimited: (_, { isLimited }) => isLimited, + }, + ], + hasServerError: [ + false, + { + setServerError: (_, { isError }) => isError, + }, + ], + }), + + loaders(({ actions, values }) => ({ + assistantResponse: [ + null as string | null, + { + submitMessage: async ({ message }, breakpoint) => { + try { + actions.setSearchingThinking(true) + actions.setServerError(false) + if (!values.isRateLimited) { + actions.setRateLimited(false) + } + const response = (await sidePanelMaxAPI.sendMessage(message)) as MaxResponse + await breakpoint(100) + + const content = typeof response.content === 'string' ? response.content : response.content.text + + if (response.isRateLimited) { + actions.setRateLimited(true) + } else if (response.isError) { + actions.setServerError(true) + } else { + actions.setRateLimited(false) + actions.setServerError(false) + } + + actions.appendAssistantMessage(content) + setTimeout(() => actions.setSearchingThinking(false), 100) + return content + } catch (error: unknown) { + if ( + error && + typeof error === 'object' && + 'message' in error && + typeof error.message === 'string' + ) { + if (error.message.includes('429') || error.message.includes('rate limit')) { + actions.setRateLimited(true) + } else if ( + error.message.includes('500') || + error.message.includes('524') || + error.message.includes('529') + ) { + actions.setServerError(true) + } + } + setTimeout(() => actions.setSearchingThinking(false), 100) + console.error('Error sending message:', error) + return null + } + }, + }, + ], + })), +]) diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxAPI.ts b/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxAPI.ts new file mode 100644 index 0000000000000..e7e2385df0b98 --- /dev/null +++ b/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxAPI.ts @@ -0,0 +1,25 @@ +import api from 'lib/api' + +export const sidePanelMaxAPI = { + async sendMessage(message: string): Promise<{ content: string }> { + // Get or create session ID using sessionStorage + let sessionId = sessionStorage.getItem('max_session_id') + if (!sessionId) { + sessionId = crypto.randomUUID() + sessionStorage.setItem('max_session_id', sessionId) + } + + const response = await api.createResponse(`/max/chat/`, { + message, + role: 'user', + session_id: sessionId, + }) + + if (!response.ok) { + throw new Error('Failed to send message to Max') + } + + const data = await response.json() + return { content: data.content } + }, +} diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxChatInterface.scss b/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxChatInterface.scss new file mode 100644 index 0000000000000..75b206b2aea44 --- /dev/null +++ b/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxChatInterface.scss @@ -0,0 +1,61 @@ +.SidePanelMaxChatInterface { + &__label { + margin-top: 0.5rem; + margin-right: 0.5rem; + font-size: 0.875rem; + color: var(--muted); + white-space: nowrap; + } + + &__message-container { + display: flex; + width: 100%; + + &--user { + justify-content: flex-end; + } + + &--assistant { + justify-content: flex-start; + } + } + + &__message-content { + width: 90%; + min-width: 90%; + max-width: 90%; + padding: 0.5rem; + word-break: break-word; + border-radius: 0.5rem; + + &--assistant { + color: var(--default); + background: var(--bg-light); + + .dark & { + background: var(--bg-depth); + } + } + + &--user { + color: var(--default); + background: var(--bg-light); + + .dark & { + background: var(--bg-side); + } + } + } + + code { + padding: 0.125rem 0.25rem; + font-family: var(--font-mono); + font-size: 0.75rem; + background: color-mix(in sRGB, var(--bg-light) 85%, var(--primary) 15%); + border-radius: 0.25rem; + + .dark & { + background: color-mix(in sRGB, var(--bg-depth) 85%, var(--primary) 15%); + } + } +} diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxChatInterface.tsx b/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxChatInterface.tsx new file mode 100644 index 0000000000000..7b2f25e7bfe13 --- /dev/null +++ b/frontend/src/layout/navigation-3000/sidepanel/panels/sidePanelMaxChatInterface.tsx @@ -0,0 +1,419 @@ +import { useActions, useValues } from 'kea' +import { FlaggedFeature } from 'lib/components/FlaggedFeature' +import { supportLogic } from 'lib/components/Support/supportLogic' +import { FEATURE_FLAGS } from 'lib/constants' +import { LemonMarkdown } from 'lib/lemon-ui/LemonMarkdown' +import { memo, useEffect, useRef, useState } from 'react' + +import { LemonButton } from '~/lib/lemon-ui/LemonButton' +import { LemonCollapse } from '~/lib/lemon-ui/LemonCollapse' +import { LemonDivider } from '~/lib/lemon-ui/LemonDivider' +import { LemonTextArea } from '~/lib/lemon-ui/LemonTextArea' +import { Spinner } from '~/lib/lemon-ui/Spinner' + +import { sidePanelMaxAILogic } from './sidePanelMaxAILogic' +import { ChatMessage } from './sidePanelMaxAILogic' + +const MemoizedMessageContent = memo(function MemoizedMessageContent({ content }: { content: string }) { + const { openEmailForm } = useActions(supportLogic) + + const processedContent = content + .replace(new RegExp('.*?', 's'), '') + .replace(new RegExp('.*?', 'gs'), '') + .replace(new RegExp('.*?', 's'), '') + .replace(new RegExp('.*?', 's'), '') + .replace(new RegExp('.*?', 's'), '') + .replace(new RegExp('|', 'g'), '') + .trim() + + const handleClick = (e: React.MouseEvent): void => { + const target = e.target as HTMLElement + if (target.tagName === 'A' && target.getAttribute('href')?.includes('/support?panel=email')) { + e.preventDefault() + openEmailForm() + } + } + + return ( +

+ {processedContent} +
+ ) +}) + +function extractThinkingBlock(content: string): Array { + const matches = Array.from(content.matchAll(new RegExp('(.*?)', 'gs'))) + return matches.map((match) => match[1].trim()) +} + +function extractSearchReflection(content: string): Array { + const matches = Array.from( + content.matchAll(new RegExp('(.*?)', 'gs')) + ) + return matches.map((match) => match[1].trim()) +} + +function extractSearchQualityScore(content: string): { hasQualityScore: boolean; content: string | null } { + const qualityMatch = content.match(new RegExp('(.*?)', 's')) + if (!qualityMatch) { + return { hasQualityScore: false, content: null } + } + return { + hasQualityScore: true, + content: qualityMatch[1].trim(), + } +} + +function extractInfoValidation(content: string): { hasQualityScore: boolean; content: string | null } { + const qualityMatch = content.match(new RegExp('(.*?)', 's')) + if (!qualityMatch) { + return { hasQualityScore: false, content: null } + } + return { + hasQualityScore: true, + content: qualityMatch[1].trim(), + } +} + +function extractURLValidation(content: string): { hasQualityScore: boolean; content: string | null } { + const qualityMatch = content.match(new RegExp('(.*?)', 's')) + if (!qualityMatch) { + return { hasQualityScore: false, content: null } + } + return { + hasQualityScore: true, + content: qualityMatch[1].trim(), + } +} + +export function MaxChatInterface(): JSX.Element { + return ( + + + + ) +} + +function MaxChatInterfaceContent(): JSX.Element { + const { currentMessages, isSearchingThinking, isRateLimited } = useValues(sidePanelMaxAILogic) + const { submitMessage } = useActions(sidePanelMaxAILogic) + const [inputMessage, setInputMessage] = useState('') + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + submitMessage('__GREETING__') + }, [submitMessage]) + + const showInput = + !isLoading && currentMessages.length > 0 && currentMessages.some((msg) => msg.role === 'assistant') + + useEffect(() => { + if (currentMessages.some((msg) => msg.role === 'assistant')) { + setIsLoading(false) + } + }, [currentMessages]) + + const handleKeyDown = (e: React.KeyboardEvent): void => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + if (inputMessage.trim() && !isSearchingThinking) { + submitMessage(inputMessage) + setInputMessage('') + } + } + } + + const messagesEndRef = useRef(null) + const endButtonRef = useRef(null) + const prevMessageCountRef = useRef(currentMessages.length) + + useEffect(() => { + if (prevMessageCountRef.current !== currentMessages.length) { + setTimeout(() => { + endButtonRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }) + }, 100) + prevMessageCountRef.current = currentMessages.length + } + }, [currentMessages]) + + const displayMessages = currentMessages.filter((message) => message.content !== '__GREETING__') + + return ( +
+
+
+

Tips for chatting with Max:

+
    +
  • Max can't handle files or images (yet.)
  • +
  • + Max can't see what page you're on, or the contents. Copy/paste error messages or queries to + share with Max. +
  • +
  • Replies can take up to 3 mins due to rate-limiting.
  • +
  • Max can make mistakes. Please double-check responses.
  • +
+
+ {isLoading ? ( +
+ Max is crawling out of his burrow and shaking off his quills... + +
+ ) : ( + <> + {displayMessages.map( + (message: ChatMessage, idx: number): JSX.Element => ( +
+ {message.role === 'user' &&
You
} + +
+ {message.role === 'assistant' && ( +
Max
+ )} +
+ {message.role === 'assistant' + ? typeof message.content === 'string' && + (message.content.includes('started') ? ( +
+ Max is searching... + +
+ ) : message.content.includes( + 'completed' + ) ? ( +
Max searched
+ ) : ( + <> + + + {/* Only show analysis for non-greeting messages */} + {idx === 0 + ? null + : (extractThinkingBlock(message.content).length > 0 || + extractSearchReflection(message.content).length > + 0 || + extractSearchQualityScore(message.content) + .hasQualityScore || + extractInfoValidation(message.content) + .hasQualityScore || + extractURLValidation(message.content) + .hasQualityScore) && ( + + What was Max thinking? + + ), + content: ( +
+ {/* Thinking blocks */} + {extractThinkingBlock( + message.content + ).map((content, index) => ( + + { + content + } +
+ ), + }, + ]} + /> + ))} + + {/* Search Reflection blocks */} + {extractSearchReflection( + message.content + ).map((content, index) => ( + + { + content + } +
+ ), + }, + ]} + /> + ))} + + {/* Search Quality Score */} + {extractSearchQualityScore( + message.content + ).hasQualityScore && ( + + { + extractSearchQualityScore( + message.content + ) + .content + } +
+ ), + }, + ]} + /> + )} + + {/* Info Validation */} + {extractInfoValidation( + message.content + ).hasQualityScore && ( + + { + extractInfoValidation( + message.content + ) + .content + } +
+ ), + }, + ]} + /> + )} + + {/* URL Validation */} + {extractURLValidation( + message.content + ).hasQualityScore && ( + + { + extractURLValidation( + message.content + ) + .content + } +
+ ), + }, + ]} + /> + )} +
+ ), + }, + ]} + /> + )} + + )) + : message.content} +
+
+ + ) + )} + {isSearchingThinking && + displayMessages.length > 0 && + displayMessages[displayMessages.length - 1].role === 'user' && ( +
+
+
Max
+
+
+ + {isRateLimited + ? "🫣 Uh-oh, I'm really popular today, we've been rate-limited. I just need to catch my breath. Hang on, I'll resume searching in less than a minute..." + : 'Searching and thinking...'} + + +
+
+
+
+ )} + + )} +
+
+ + + + {showInput && ( + <> +
+ +
+ `enter` to send, `shift+enter` for a new line +
+ { + e.preventDefault() + if (inputMessage.trim() && !isSearchingThinking) { + submitMessage(inputMessage) + setInputMessage('') + } + }} + > + Send + + +
+ + )} +
+ ) +} diff --git a/frontend/src/lib/components/Support/supportLogic.ts b/frontend/src/lib/components/Support/supportLogic.ts index 3e0f670074598..516989aa05050 100644 --- a/frontend/src/lib/components/Support/supportLogic.ts +++ b/frontend/src/lib/components/Support/supportLogic.ts @@ -325,6 +325,8 @@ export const supportLogic = kea([ updateUrlParams: true, openEmailForm: true, closeEmailForm: true, + openMaxChatInterface: true, + closeMaxChatInterface: true, })), reducers(() => ({ isSupportFormOpen: [ @@ -332,6 +334,7 @@ export const supportLogic = kea([ { openSupportForm: () => true, closeSupportForm: () => false, + openMaxChatInterface: () => false, }, ], isEmailFormOpen: [ @@ -341,6 +344,14 @@ export const supportLogic = kea([ closeEmailForm: () => false, }, ], + isMaxChatInterfaceOpen: [ + false, + { + openMaxChatInterface: () => true, + closeMaxChatInterface: () => false, + openEmailForm: () => false, + }, + ], })), forms(({ actions, values }) => ({ sendSupportRequest: { @@ -564,6 +575,19 @@ export const supportLogic = kea([ setSendSupportRequestValue: () => { actions.updateUrlParams() }, + openMaxChatInterface: async () => { + const panelOptions = [ + 'max-chat', + '', // No target area needed for Max (yet) + '', // No severity level needed for Max + 'false', // Make sure we don't open the email form instead + ].join(':') + + if (values.sidePanelAvailable) { + actions.setSidePanelOptions(panelOptions) + } + actions.updateUrlParams() + }, })), urlToAction(({ actions, values }) => ({ diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index d41a232518b18..76e69419a8e23 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -232,6 +232,7 @@ export const FEATURE_FLAGS = { REPLAY_HOGQL_FILTERS: 'replay-hogql-filters', // owner: @pauldambra #team-replay REPLAY_LIST_RECORDINGS_AS_QUERY: 'replay-list-recordings-as-query', // owner: @pauldambra #team-replay SUPPORT_MESSAGE_OVERRIDE: 'support-message-override', // owner: @abigail + SUPPORT_SIDEBAR_MAX: 'support-sidebar-max', // owner: @steven #team-max BILLING_SKIP_FORECASTING: 'billing-skip-forecasting', // owner: @zach EXPERIMENT_STATS_V2: 'experiment-stats-v2', // owner: @danielbachhuber #team-experiments WEB_ANALYTICS_PERIOD_COMPARISON: 'web-analytics-period-comparison', // owner: @rafaeelaudibert #team-web-analytics diff --git a/frontend/src/lib/lemon-ui/LemonMarkdown/LemonMarkdown.tsx b/frontend/src/lib/lemon-ui/LemonMarkdown/LemonMarkdown.tsx index 118f182c52b5c..8c11e90484220 100644 --- a/frontend/src/lib/lemon-ui/LemonMarkdown/LemonMarkdown.tsx +++ b/frontend/src/lib/lemon-ui/LemonMarkdown/LemonMarkdown.tsx @@ -10,18 +10,25 @@ export interface LemonMarkdownProps { children: string /** Whether headings should just be text. Recommended for item descriptions. */ lowKeyHeadings?: boolean + /** Whether to disable the docs sidebar panel behavior and always open links in a new tab */ + disableDocsRedirect?: boolean className?: string } /** Beautifully rendered Markdown. */ -export function LemonMarkdown({ children, lowKeyHeadings = false, className }: LemonMarkdownProps): JSX.Element { +export function LemonMarkdown({ + children, + lowKeyHeadings = false, + disableDocsRedirect = false, + className, +}: LemonMarkdownProps): JSX.Element { return (
{/* eslint-disable-next-line react/forbid-elements */} ( - + {children} ), diff --git a/frontend/src/lib/lemon-ui/Link/Link.tsx b/frontend/src/lib/lemon-ui/Link/Link.tsx index a8ce49710e678..869579bc95b70 100644 --- a/frontend/src/lib/lemon-ui/Link/Link.tsx +++ b/frontend/src/lib/lemon-ui/Link/Link.tsx @@ -21,6 +21,8 @@ export type LinkProps = Pick, 'target' | 'cla to?: string | [string, RoutePart?, RoutePart?] /** If true, in-app navigation will not be used and the link will navigate with a page load */ disableClientSideRouting?: boolean + /** If true, docs links will not be opened in the docs panel */ + disableDocsPanel?: boolean preventClick?: boolean onClick?: (event: React.MouseEvent) => void onMouseDown?: (event: React.MouseEvent) => void @@ -80,6 +82,7 @@ export const Link: React.FC> = Reac target, subtle, disableClientSideRouting, + disableDocsPanel = false, preventClick = false, onClick: onClickRaw, className, @@ -110,7 +113,7 @@ export const Link: React.FC> = Reac const mountedSidePanelLogic = sidePanelStateLogic.findMounted() - if (typeof to === 'string' && isPostHogComDocs(to) && mountedSidePanelLogic) { + if (!disableDocsPanel && typeof to === 'string' && isPostHogComDocs(to) && mountedSidePanelLogic) { // TRICKY: We do this instead of hooks as there is some weird cyclic issue in tests const { sidePanelOpen } = mountedSidePanelLogic.values const { openSidePanel } = mountedSidePanelLogic.actions diff --git a/package.json b/package.json index 1304a866fa9b8..a90db10f39927 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "mypy-baseline-sync": "mypy -p posthog | mypy-baseline sync" }, "dependencies": { + "@anthropic-ai/sdk": "^0.33.1", "@babel/runtime": "^7.24.0", "@dnd-kit/core": "^6.0.8", "@dnd-kit/modifiers": "^6.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e30b9898bfd1..d0ec56e914f1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ patchedDependencies: path: patches/rrweb@2.0.0-alpha.13.patch dependencies: + '@anthropic-ai/sdk': + specifier: ^0.33.1 + version: 0.33.1 '@babel/runtime': specifier: ^7.24.0 version: 7.24.0 @@ -906,6 +909,20 @@ packages: '@jridgewell/gen-mapping': 0.1.1 '@jridgewell/trace-mapping': 0.3.17 + /@anthropic-ai/sdk@0.33.1: + resolution: {integrity: sha512-VrlbxiAdVRGuKP2UQlCnsShDHJKWepzvfRCkZMpU+oaUdKLpOfmylLMRojGrAgebV+kDtPjewCVP0laHXg+vsA==} + dependencies: + '@types/node': 18.18.4 + '@types/node-fetch': 2.6.4 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.6.7 + transitivePeerDependencies: + - encoding + dev: false + /@aw-web-design/x-default-browser@1.4.126: resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==} hasBin: true @@ -8509,14 +8526,12 @@ packages: dependencies: '@types/node': 18.18.4 form-data: 3.0.1 - dev: true /@types/node@18.11.9: resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==} /@types/node@18.18.4: resolution: {integrity: sha512-t3rNFBgJRugIhackit2mVcLfF6IRc0JE4oeizPQL8Zrm8n2WY/0wOdpOPhdtG0V9Q2TlW/axbF1MJ6z+Yj/kKQ==} - dev: true /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -9157,6 +9172,13 @@ packages: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: false + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -9234,6 +9256,13 @@ packages: - supports-color dev: true + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: false + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -9623,7 +9652,6 @@ packages: /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true /at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} @@ -10516,7 +10544,6 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: true /comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} @@ -11588,7 +11615,6 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: true /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} @@ -12596,6 +12622,11 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: false + /eventemitter2@6.4.7: resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} dev: true @@ -13112,6 +13143,10 @@ packages: webpack: 5.88.2(@swc/core@1.3.93)(esbuild@0.19.8)(webpack-cli@5.1.4) dev: true + /form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + dev: false + /form-data@2.3.3: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} @@ -13128,7 +13163,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: true /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} @@ -13144,6 +13178,14 @@ packages: engines: {node: '>=0.4.x'} dev: false + /formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + dev: false + /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -13909,6 +13951,12 @@ packages: engines: {node: '>=10.17.0'} dev: true + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: false + /humps@2.0.1: resolution: {integrity: sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==} dev: false @@ -16405,6 +16453,11 @@ packages: minimatch: 3.1.2 dev: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: false + /node-fetch-native@1.2.0: resolution: {integrity: sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==} dev: true @@ -18520,7 +18573,7 @@ packages: react: '>=15' dependencies: react: 18.2.0 - unlayer-types: 1.182.0 + unlayer-types: 1.188.0 dev: false /react-error-boundary@3.1.4(react@18.2.0): @@ -21107,8 +21160,8 @@ packages: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} - /unlayer-types@1.182.0: - resolution: {integrity: sha512-x+YSeA7/Wb/znKDtRws8M3Mu6TyKP3d+MddPVX/iUyDPVEOapoPWk0QxjIaNYtWt6troADZdhzgr2EwsZ61HrA==} + /unlayer-types@1.188.0: + resolution: {integrity: sha512-tnn+FjUZv1qUOoRUYRFxSDz9kHfhy7dLxzMZgnU5+k6GDSBlpa8mA+r4+r0D83M+mUUd/XwuM+gvfRLGzrqZ+g==} dev: false /unpipe@1.0.0: @@ -21439,6 +21492,11 @@ packages: '@zxing/text-encoding': 0.9.0 dev: true + /web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + dev: false + /web-vitals@4.2.4: resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} dev: false diff --git a/posthog/api/test/__snapshots__/test_api_docs.ambr b/posthog/api/test/__snapshots__/test_api_docs.ambr index 862d8507dbb75..de67b4a316bcc 100644 --- a/posthog/api/test/__snapshots__/test_api_docs.ambr +++ b/posthog/api/test/__snapshots__/test_api_docs.ambr @@ -2,21 +2,12 @@ # name: TestAPIDocsSchema.test_api_docs_generation_warnings_snapshot list([ '', - '/home/runner/work/posthog/posthog/ee/api/dashboard_collaborator.py: Warning [DashboardCollaboratorViewSet]: could not derive type of path parameter "project_id" because model "ee.models.dashboard_privilege.DashboardPrivilege" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', - '/home/runner/work/posthog/posthog/ee/api/explicit_team_member.py: Warning [ExplicitTeamMemberViewSet]: could not derive type of path parameter "project_id" because model "ee.models.explicit_team_membership.ExplicitTeamMembership" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', - '/home/runner/work/posthog/posthog/ee/api/feature_flag_role_access.py: Warning [FeatureFlagRoleAccessViewSet]: could not derive type of path parameter "project_id" because model "ee.models.feature_flag_role_access.FeatureFlagRoleAccess" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', - '/home/runner/work/posthog/posthog/ee/api/rbac/role.py: Warning [RoleMembershipViewSet]: could not derive type of path parameter "organization_id" because model "ee.models.rbac.role.RoleMembership" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', - '/home/runner/work/posthog/posthog/ee/api/rbac/role.py: Warning [RoleViewSet > RoleSerializer]: unable to resolve type hint for function "get_members". Consider using a type hint or @extend_schema_field. Defaulting to string.', - '/home/runner/work/posthog/posthog/ee/api/subscription.py: Warning [SubscriptionViewSet > SubscriptionSerializer]: unable to resolve type hint for function "summary". Consider using a type hint or @extend_schema_field. Defaulting to string.', - '/home/runner/work/posthog/posthog/ee/api/subscription.py: Warning [SubscriptionViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.subscription.Subscription" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', '/home/runner/work/posthog/posthog/ee/clickhouse/views/experiment_holdouts.py: Warning [ExperimentHoldoutViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.experiment.ExperimentHoldout" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', '/home/runner/work/posthog/posthog/ee/clickhouse/views/experiment_saved_metrics.py: Warning [ExperimentSavedMetricViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.experiment.ExperimentSavedMetric" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', '/home/runner/work/posthog/posthog/ee/clickhouse/views/experiments.py: Warning [EnterpriseExperimentsViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.experiment.Experiment" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', '/home/runner/work/posthog/posthog/ee/clickhouse/views/groups.py: Warning [GroupsViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.group.group.Group" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', '/home/runner/work/posthog/posthog/ee/clickhouse/views/insights.py: Warning [EnterpriseInsightsViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.insight.Insight" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', '/home/runner/work/posthog/posthog/ee/clickhouse/views/person.py: Warning [EnterprisePersonViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.person.person.Person" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', - '/home/runner/work/posthog/posthog/ee/session_recordings/session_recording_playlist.py: Warning [SessionRecordingPlaylistViewSet]: could not derive type of path parameter "project_id" because model "posthog.session_recordings.models.session_recording_playlist.SessionRecordingPlaylist" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', - '/home/runner/work/posthog/posthog/ee/session_recordings/session_recording_playlist.py: Warning [SessionRecordingPlaylistViewSet]: could not derive type of path parameter "session_recording_id" because model "posthog.session_recordings.models.session_recording_playlist.SessionRecordingPlaylist" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', '/home/runner/work/posthog/posthog/posthog/api/action.py: Warning [ActionViewSet > ActionSerializer]: unable to resolve type hint for function "get_creation_context". Consider using a type hint or @extend_schema_field. Defaulting to string.', '/home/runner/work/posthog/posthog/posthog/api/action.py: Warning [ActionViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.action.action.Action" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', '/home/runner/work/posthog/posthog/posthog/api/activity_log.py: Warning [ActivityLogViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.activity_logging.activity_log.ActivityLog" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', @@ -84,9 +75,9 @@ '/home/runner/work/posthog/posthog/posthog/session_recordings/session_recording_api.py: Warning [SessionRecordingViewSet > SessionRecordingSerializer]: unable to resolve type hint for function "storage". Consider using a type hint or @extend_schema_field. Defaulting to string.', '/home/runner/work/posthog/posthog/posthog/session_recordings/session_recording_api.py: Warning [SessionRecordingViewSet]: could not derive type of path parameter "project_id" because model "posthog.session_recordings.models.session_recording.SessionRecording" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', '/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py: Warning [QueryViewSet > ModelMetaclass]: Encountered 2 components with identical names "Person" and different classes and . This will very likely result in an incorrect schema. Try renaming one.', + 'Warning: encountered multiple names for the same choice set (EffectiveMembershipLevelEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: encountered multiple names for the same choice set (EffectivePrivilegeLevelEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: encountered multiple names for the same choice set (HrefMatchingEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.', - 'Warning: encountered multiple names for the same choice set (MembershipLevelEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "kind". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "Kind069Enum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "kind". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "Kind0ddEnum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "kind". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "Kind496Enum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', diff --git a/posthog/settings/common.py b/posthog/settings/common.py new file mode 100644 index 0000000000000..cf26aebd3fa34 --- /dev/null +++ b/posthog/settings/common.py @@ -0,0 +1,6 @@ +import sys +from pathlib import Path + +# Add support-sidebar-max to Python path +ee_path = Path(__file__).parents[2] / "ee" +sys.path.append(str(ee_path)) diff --git a/requirements-dev.txt b/requirements-dev.txt index cc0e410715cd7..b439564c2a820 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -167,10 +167,6 @@ googleapis-common-protos==1.60.0 # via # -c requirements.txt # opentelemetry-exporter-otlp-proto-grpc -greenlet==3.1.1 - # via - # -c requirements.txt - # sqlalchemy grpcio==1.63.2 # via # -c requirements.txt @@ -189,7 +185,7 @@ httpx==0.26.0 # -c requirements.txt # langsmith # openai -huggingface-hub==0.26.2 +huggingface-hub==0.26.5 # via datasets icdiff==2.0.5 # via pytest-icdiff @@ -215,7 +211,7 @@ isort==5.2.2 # via datamodel-code-generator jinja2==3.1.4 # via datamodel-code-generator -jiter==0.5.0 +jiter==0.8.2 # via # -c requirements.txt # openai @@ -277,7 +273,7 @@ lupa==2.2 # via fakeredis markdown-it-py==3.0.0 # via rich -markupsafe==2.1.5 +markupsafe==3.0.2 # via jinja2 marshmallow==3.23.1 # via dataclasses-json diff --git a/requirements.in b/requirements.in index 16ee79fb66e03..73e5c15fc717d 100644 --- a/requirements.in +++ b/requirements.in @@ -113,3 +113,5 @@ xmlsec==1.3.13 # Do not change this version - it will break SAML lxml==4.9.4 # Do not change this version - it will break SAML grpcio~=1.63.2 # Version constrained so that `deepeval` can be installed in in dev tenacity~=8.4.2 # Version constrained so that `deepeval` can be installed in in dev +anthropic==0.40.0 +beautifulsoup4==4.12.3 diff --git a/requirements.txt b/requirements.txt index cd9ac20220391..abebe3620272a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,10 +25,13 @@ amqp==5.1.1 # via kombu annotated-types==0.7.0 # via pydantic +anthropic==0.40.0 + # via -r requirements.in antlr4-python3-runtime==4.13.1 # via -r requirements.in anyio==4.6.2.post1 # via + # anthropic # httpx # langfuse # openai @@ -56,6 +59,8 @@ backoff==2.2.1 # posthoganalytics bcrypt==4.1.3 # via paramiko +beautifulsoup4==4.12.3 + # via -r requirements.in billiard==4.1.0 # via celery boto3==1.28.16 @@ -137,7 +142,9 @@ defusedxml==0.6.0 deltalake==0.19.1 # via dlt distro==1.9.0 - # via openai + # via + # anthropic + # openai dj-database-url==0.5.0 # via -r requirements.in django==4.2.15 @@ -269,8 +276,6 @@ googleapis-common-protos==1.60.0 # via # google-api-core # grpcio-status -greenlet==3.1.1 - # via sqlalchemy grpcio==1.63.2 # via # -r requirements.in @@ -293,6 +298,7 @@ httpcore==1.0.2 # via httpx httpx==0.26.0 # via + # anthropic # langfuse # langgraph-sdk # langsmith @@ -319,8 +325,10 @@ isodate==0.6.1 # via # python3-saml # zeep -jiter==0.5.0 - # via openai +jiter==0.8.2 + # via + # anthropic + # openai jmespath==1.0.0 # via # boto3 @@ -514,6 +522,7 @@ pycparser==2.20 pydantic==2.9.2 # via # -r requirements.in + # anthropic # langchain # langchain-core # langfuse @@ -672,6 +681,7 @@ smmap==5.0.1 # via gitdb sniffio==1.3.1 # via + # anthropic # anyio # httpx # openai @@ -692,6 +702,8 @@ sortedcontainers==2.4.0 # via # snowflake-connector-python # trio +soupsieve==2.6 + # via beautifulsoup4 sqlalchemy==2.0.31 # via # -r requirements.in @@ -752,6 +764,7 @@ types-setuptools==69.0.0.0 # via requirements-parser typing-extensions==4.12.2 # via + # anthropic # dlt # langchain-core # openai diff --git a/rust/cyclotron-node/pnpm-lock.yaml b/rust/cyclotron-node/pnpm-lock.yaml index 9866808970bae..49306b7234844 100644 --- a/rust/cyclotron-node/pnpm-lock.yaml +++ b/rust/cyclotron-node/pnpm-lock.yaml @@ -1,31 +1,39 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -devDependencies: - '@types/node': - specifier: ^22.4.1 - version: 22.4.1 - typescript: - specifier: ^4.7.4 - version: 4.9.5 +importers: + + .: + devDependencies: + '@types/node': + specifier: ^22.4.1 + version: 22.4.1 + typescript: + specifier: ^4.7.4 + version: 4.9.5 packages: - /@types/node@22.4.1: + '@types/node@22.4.1': resolution: {integrity: sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg==} - dependencies: - undici-types: 6.19.8 - dev: true - /typescript@4.9.5: + typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} hasBin: true - dev: true - /undici-types@6.19.8: + undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - dev: true + +snapshots: + + '@types/node@22.4.1': + dependencies: + undici-types: 6.19.8 + + typescript@4.9.5: {} + + undici-types@6.19.8: {}