├── .gitignore ├── requirements.txt ├── htdoc ├── mirobody.json ├── __ │ ├── auth │ │ ├── init.json │ │ ├── iframe │ │ ├── handler │ │ └── experiments.js │ └── firebase │ │ └── init.json ├── mirobody.svg ├── index.html ├── assets │ ├── logo-MLEW-e8g.svg │ └── polyfills-legacy-DglrV6q4.js └── github-markdown-light.css ├── .env ├── docker ├── Dockerfile.backend.cloud ├── package.json ├── Dockerfile.backend ├── docker-compose.yaml └── init_fake_data.sh ├── main.py ├── config └── config.example.yaml ├── shell └── update_mirobody.sh ├── tools ├── health │ ├── user_service.py │ ├── health_indicator_service.py │ └── genetic_service.py └── finance │ └── fundamentals_service.py ├── deploy.sh ├── README.md └── user_case └── 1m_users.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | config/config.yaml 3 | .cursorindexingignore 4 | .env 5 | .specstory/ 6 | __pycache__/ 7 | .DS_Store -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | --extra-index-url https://repo.thetahealth.ai/repository/pypi-ai-releases/simple/ 2 | mirobody==20251212.205900 3 | 4 | yfinance 5 | -------------------------------------------------------------------------------- /htdoc/mirobody.json: -------------------------------------------------------------------------------- 1 | { 2 | "__IS_EHR_CONFIG_ON__": true, 3 | "__IS_API_CONFIG_ON__": true, 4 | "__IS_QR_LOGIN_ON__": false, 5 | "__IS_GOOGLE_LOGIN_ON__": true, 6 | "__IS_APPLE_LOGIN_ON__": true 7 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # [OPTIONAL] Configuartion sets. 2 | # "localdb", "test", "gray", or "prod". 3 | ENV=localdb 4 | 5 | # [OPTIONAL] Encryption of sensitive configuration values. 6 | CONFIG_ENCRYPTION_KEY=SecretKey 7 | 8 | # [OPTIONAL] Remote configuration server. 9 | CONFIG_SERVER= 10 | CONFIG_TOKEN= -------------------------------------------------------------------------------- /htdoc/__/auth/init.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiKey": "__FIREBASE_API_KEY__", 3 | "authDomain": "__FIREBASE_AUTH_DOMAIN__", 4 | "projectId": "__FIREBASE_PROJECT_ID__", 5 | "storageBucket": "__FIREBASE_STORAGE_BUCKET__", 6 | "messagingSenderId": "__FIREBASE_MESSAGING_SENDER_ID__", 7 | "appId": "__FIREBASE_APP_ID__", 8 | "measurementId": "__FIREBASE_MEASUREMENT_ID__" 9 | } -------------------------------------------------------------------------------- /htdoc/__/firebase/init.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiKey": "__FIREBASE_API_KEY__", 3 | "authDomain": "__FIREBASE_AUTH_DOMAIN__", 4 | "projectId": "__FIREBASE_PROJECT_ID__", 5 | "storageBucket": "__FIREBASE_STORAGE_BUCKET__", 6 | "messagingSenderId": "__FIREBASE_MESSAGING_SENDER_ID__", 7 | "appId": "__FIREBASE_APP_ID__", 8 | "measurementId": "__FIREBASE_MEASUREMENT_ID__" 9 | } -------------------------------------------------------------------------------- /docker/Dockerfile.backend.cloud: -------------------------------------------------------------------------------- 1 | # This Dockerfile requires THETA_REGISTRY to be passed as a build argument 2 | # Example: docker build --build-arg THETA_REGISTRY="registry-url/" ... 3 | ARG THETA_REGISTRY 4 | FROM ${THETA_REGISTRY}theta/mirobody-backend:latest 5 | 6 | WORKDIR /app 7 | 8 | COPY requirements.txt . 9 | RUN --mount=type=cache,target=/root/.cache/pip \ 10 | pip install -r requirements.txt && \ 11 | pip list 12 | 13 | -------------------------------------------------------------------------------- /htdoc/__/auth/iframe: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mirobody-chart-renderer", 3 | "version": "1.0.0", 4 | "description": "Chart rendering service for Mirobody using @antv/gpt-vis-ssr", 5 | "private": true, 6 | "scripts": { 7 | "test": "node pub/tools/render-chart.js" 8 | }, 9 | "dependencies": { 10 | "@antv/gpt-vis-ssr": "^0.3.1" 11 | }, 12 | "engines": { 13 | "node": ">=18.0.0", 14 | "npm": ">=9.0.0" 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /htdoc/mirobody.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /htdoc/__/auth/handler: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /htdoc/__/auth/experiments.js: -------------------------------------------------------------------------------- 1 | (function(){window.EXPERIMENTS={DISPLAY_CONTINUE_BUTTON_IF_NOT_REDIRECT:{id:1000003,rollout:1,defaultValue:!0,expiration:new Date("April 1, 2020"),stagingRollout:1},CHECK_CONTINUE_URL_IS_AUTHORIZED:{id:1000004,rollout:1,defaultValue:!0,expiration:new Date("September 1, 2020"),stagingRollout:1},POPUP_POST_MESSAGE_TO_IFRAME:{id:1000005,rollout:1,defaultValue:!0,expiration:new Date("October 1, 2020"),stagingRollout:1},CHECK_OAUTH_STATE_STORED_BEFORE_REDIRECT:{id:1000006,rollout:1,defaultValue:!0,expiration:new Date("April 1, 2021"), 2 | stagingRollout:1},CHECK_REDIRECT_URL_IS_AUTHORIZED:{id:1000007,rollout:1,defaultValue:!0,expiration:new Date("June 1, 2024"),stagingRollout:1}};}).call(this); 3 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from mirobody.server import Server 2 | from mirobody.utils import Config 3 | from mirobody.utils.db_initializer import initialize_database 4 | #----------------------------------------------------------------------------- 5 | 6 | async def main(): 7 | yaml_filenames = ['config/config.yaml'] 8 | 9 | config = await Config.init(yaml_filenames=yaml_filenames) 10 | 11 | # Initialize database schema before starting server 12 | pg_config = config.get_postgresql() 13 | db_init_success = await initialize_database(pg_config, enable_idempotency=False) 14 | if not db_init_success: 15 | print("Warning: Database initialization encountered errors. Check logs for details.") 16 | 17 | await Server.start(yaml_filenames) 18 | 19 | #----------------------------------------------------------------------------- 20 | 21 | if __name__ == "__main__": 22 | import asyncio 23 | asyncio.run(main()) 24 | 25 | #----------------------------------------------------------------------------- 26 | -------------------------------------------------------------------------------- /docker/Dockerfile.backend: -------------------------------------------------------------------------------- 1 | ARG BASE_REGISTRY="" 2 | FROM ${BASE_REGISTRY}ubuntu:24.04 3 | 4 | # Install system dependencies 5 | RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ 6 | --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ 7 | apt-get update && \ 8 | apt-get install -y --no-install-recommends \ 9 | g++ gfortran build-essential \ 10 | libfftw3-dev libhdf5-dev libblas-dev liblapack-dev \ 11 | libmagic-dev \ 12 | python3 python3-venv \ 13 | nodejs npm \ 14 | fonts-wqy-microhei fonts-wqy-zenhei fontconfig && \ 15 | fc-cache -fv && \ 16 | mkdir /root/venv && \ 17 | python3 -m venv /root/venv 18 | 19 | ENV PATH=/root/venv/bin:${PATH} 20 | 21 | # Install npm packages 22 | RUN mkdir -p /app/mirobody 23 | WORKDIR /app/mirobody 24 | 25 | COPY docker/package.json . 26 | RUN --mount=type=cache,target=/root/.npm \ 27 | npm config set registry https://registry.npmmirror.com && \ 28 | npm install --omit=dev && \ 29 | npm list 30 | 31 | # Install Python packages 32 | WORKDIR /app 33 | 34 | COPY requirements.txt . 35 | RUN --mount=type=cache,target=/root/.cache/pip \ 36 | pip install -r requirements.txt && \ 37 | pip list 38 | -------------------------------------------------------------------------------- /config/config.example.yaml: -------------------------------------------------------------------------------- 1 | OPENROUTER_API_KEY: '' 2 | 3 | SERPER_API_KEY: '' 4 | 5 | FAL_KEY: '' 6 | 7 | E2B_API_KEY: '' 8 | 9 | ALLOWED_TOOLS_SYNERGY: 10 | - genetic_service 11 | 12 | DISALLOWED_TOOLS_SYNERGY: 13 | - get_user_health_profile 14 | 15 | LOG_NAME: '' 16 | LOG_DIR: '' 17 | LOG_LEVEL: INFO 18 | HTTP_SERVER_NAME: mirobody 19 | HTTP_SERVER_VERSION: 1.0.1 20 | HTTP_HOST: 0.0.0.0 21 | HTTP_PORT: 18080 22 | HTTP_URI_PREFIX: '' 23 | HTTP_HTDOC: htdoc 24 | 25 | HTTP_HEADERS: 26 | Access-Control-Allow-Origin: '*' 27 | Access-Control-Allow-Credentials: 'true' 28 | Access-Control-Allow-Methods: '*' 29 | Access-Control-Allow-Headers: '*' 30 | Access-Control-Max-Age: '86400' 31 | 32 | MCP_FRONTEND_URL: http://localhost:18080 33 | MCP_PUBLIC_URL: http://localhost:18080 34 | DATA_PUBLIC_URL: http://localhost:18080 35 | QR_LOGIN_URL: '' 36 | DATABASE_DECRYPTION_KEY: 'mydatabaseencryptionkey' 37 | JWT_KEY: 'myjwtkey' 38 | 39 | REDIS_HOST: redis 40 | REDIS_PORT: 6379 41 | REDIS_DB: 0 42 | REDIS_SSL: false 43 | REDIS_SSL_CHECK_HOSTNAME: false 44 | REDIS_SSL_CERT_REQS: none 45 | REDIS_PASSWORD: 'your_secure_password_here' 46 | PG_HOST: db 47 | PG_PORT: 5432 48 | PG_USER: holistic_user 49 | PG_DBNAME: holistic_db 50 | PG_SCHEMA: theta_ai 51 | PG_MIN_CONNECTION: 5 52 | PG_MAX_CONNECTION: 20 53 | PG_PASSWORD: 'holistic_password' 54 | PG_ENCRYPTION_KEY: 'mydatabaseencryptionkey' 55 | 56 | S3_KEY: '' 57 | S3_TOKEN: '' 58 | S3_REGION: '' 59 | S3_BUCKET: '' 60 | S3_PREFIX: '' 61 | S3_CDN: '' 62 | MCP_TOOL_DIRS: 63 | - mirobody/pub/tools 64 | - tools/health 65 | - tools/finance 66 | 67 | MCP_RESOURCE_DIRS: 68 | - mirobody/pub/resources 69 | AGENT_DIRS: 70 | - mirobody/pub/agents 71 | JWT_PRIVATE_KEY: '' 72 | 73 | GOOGLE_CLIENT_ID: '' 74 | FIREBASE_PROJECT_ID: '' 75 | 76 | APPLE_TEAM_ID: '' 77 | APPLE_KEY_ID: '' 78 | APPLE_PRIVATE_KEY: '' 79 | APPLE_CLIENT_ID: '' 80 | APPLE_AUTH_CLIENT_ID: '' 81 | 82 | EMAIL_FROM: '' 83 | EMAIL_FROM_NAME: '' 84 | EMAIL_TEMPLATE: '' 85 | EMAIL_SMTP_PASS: '' 86 | EMAIL_PREDEFINE_CODES: 87 | demo1@mirobody.ai: '777777' 88 | demo2@mirobody.ai: '777777' 89 | demo3@mirobody.ai: '777777' 90 | ANTHROPIC_API_KEY: '' 91 | OPENAI_API_KEY_RTC: '' 92 | -------------------------------------------------------------------------------- /htdoc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |154 | Built with ❤️ for the AI Open Source Community. 155 |
156 | -------------------------------------------------------------------------------- /htdoc/assets/logo-MLEW-e8g.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /tools/finance/fundamentals_service.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Fundamentals Service - yfinance Integration 4 | Fetch key fundamental metrics and financial ratios using yfinance 5 | Provides comprehensive fundamental analysis data including valuation ratios, profitability metrics, and financial health indicators 6 | """ 7 | 8 | import logging 9 | from datetime import datetime 10 | from typing import Any, Dict, Optional 11 | 12 | try: 13 | import yfinance as yf 14 | YFINANCE_AVAILABLE = True 15 | except ImportError: 16 | YFINANCE_AVAILABLE = False 17 | logging.warning("yfinance not installed. Fundamentals service will not be available.") 18 | 19 | 20 | class FundamentalsService: 21 | """Fundamentals Service - Fetch key fundamental metrics using yfinance""" 22 | 23 | def __init__(self): 24 | self.name = "Fundamentals Service" 25 | self.version = "1.0.0" 26 | 27 | if not YFINANCE_AVAILABLE: 28 | logging.error("yfinance library is not available. Please install with: pip install yfinance") 29 | else: 30 | logging.info(f"Fundamentals Service v{self.version} initialized with yfinance integration") 31 | 32 | async def get_key_metrics( 33 | self, 34 | symbol: str, 35 | user_info: Optional[Dict[str, Any]] = None, 36 | ) -> Dict[str, Any]: 37 | """ 38 | Fetch key fundamental metrics and financial ratios using yfinance. 39 | 40 | This tool retrieves comprehensive fundamental analysis data including valuation ratios, 41 | profitability metrics, financial health indicators, and other key metrics for investment analysis. 42 | 43 | Args: 44 | symbol (str): Stock ticker symbol (e.g., "AAPL", "TSLA", "MSFT"). 45 | This is a required parameter. Case-insensitive. 46 | 47 | user_info (Optional[Dict[str, Any]]): User information for logging and tracking purposes. 48 | Not used for authentication in this service. 49 | 50 | Returns: 51 | Dict[str, Any]: A dictionary containing: 52 | - success (bool): Whether the operation succeeded 53 | - data (Dict): Dictionary of fundamental metrics including: 54 | * Valuation Metrics: 55 | - market_cap: Market capitalization 56 | - trailing_pe: Trailing P/E ratio 57 | - forward_pe: Forward P/E ratio 58 | - price_to_book: Price-to-Book ratio 59 | - price_to_sales: Price-to-Sales ratio 60 | - peg_ratio: PEG ratio (P/E to Growth) 61 | - enterprise_value: Enterprise value 62 | - ev_to_revenue: EV/Revenue ratio 63 | - ev_to_ebitda: EV/EBITDA ratio 64 | * Profitability Metrics: 65 | - profit_margins: Net profit margin 66 | - operating_margins: Operating margin 67 | - gross_margins: Gross margin 68 | - return_on_equity: ROE (Return on Equity) 69 | - return_on_assets: ROA (Return on Assets) 70 | - ebitda: EBITDA 71 | * Per-Share Metrics: 72 | - trailing_eps: Trailing Earnings Per Share 73 | - forward_eps: Forward Earnings Per Share 74 | - book_value: Book value per share 75 | - revenue_per_share: Revenue per share 76 | * Dividend Metrics: 77 | - dividend_yield: Dividend yield (as decimal) 78 | - dividend_rate: Annual dividend rate 79 | - payout_ratio: Dividend payout ratio 80 | * Financial Health: 81 | - debt_to_equity: Debt-to-Equity ratio 82 | - current_ratio: Current ratio 83 | - quick_ratio: Quick ratio (Acid-test ratio) 84 | - total_cash: Total cash 85 | - total_debt: Total debt 86 | - free_cashflow: Free cash flow 87 | * Growth & Other: 88 | - revenue_growth: Revenue growth rate 89 | - earnings_growth: Earnings growth rate 90 | - beta: Beta (volatility measure) 91 | - shares_outstanding: Number of shares outstanding 92 | - float_shares: Float shares 93 | - held_percent_insiders: Insider ownership percentage 94 | - held_percent_institutions: Institutional ownership percentage 95 | - metadata (Dict): Metadata about the response: 96 | * symbol: Stock symbol queried 97 | * query_time: Timestamp of the query 98 | * data_source: Data provider (yfinance) 99 | * currency: Currency of financial data 100 | - error (Optional[str]): Error message if the operation failed 101 | 102 | Raises: 103 | ValueError: If required parameters are missing or invalid 104 | Exception: If yfinance API call fails or data processing encounters an error 105 | 106 | Examples: 107 | # Get fundamental metrics for Apple stock 108 | result = await service.get_key_metrics("AAPL") 109 | 110 | # Access specific metrics 111 | if result['success']: 112 | pe_ratio = result['data']['trailing_pe'] 113 | market_cap = result['data']['market_cap'] 114 | roe = result['data']['return_on_equity'] 115 | """ 116 | try: 117 | # Check if yfinance is available 118 | if not YFINANCE_AVAILABLE: 119 | return { 120 | "success": False, 121 | "error": "yfinance library is not installed. Please install with: pip install yfinance", 122 | "data": {}, 123 | "metadata": {} 124 | } 125 | 126 | # Validate required parameters 127 | if not symbol or not symbol.strip(): 128 | raise ValueError("Stock symbol is required and cannot be empty") 129 | 130 | symbol = symbol.strip().upper() 131 | 132 | # Log request information 133 | user_id = user_info.get("user_id") if user_info else "anonymous" 134 | logging.info( 135 | f"Fundamentals request - User: {user_id}, Symbol: {symbol}" 136 | ) 137 | 138 | # Call yfinance API 139 | try: 140 | ticker = yf.Ticker(symbol) 141 | info = ticker.info 142 | except Exception as api_error: 143 | logging.error(f"yfinance API call failed: {str(api_error)}") 144 | raise ValueError(f"Failed to fetch data from yfinance: {str(api_error)}") 145 | 146 | if not info or len(info) == 0: 147 | logging.warning(f"No data returned for symbol: {symbol}") 148 | return { 149 | "success": True, 150 | "data": {}, 151 | "metadata": { 152 | "symbol": symbol, 153 | "query_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 154 | "data_source": "yfinance", 155 | "message": "No data available for the specified symbol" 156 | } 157 | } 158 | 159 | # Helper function to safely extract numeric values 160 | def get_numeric(key: str, default=None) -> Optional[float]: 161 | value = info.get(key) 162 | if value is None or value == 'null' or value == '': 163 | return default 164 | try: 165 | return float(value) 166 | except (ValueError, TypeError): 167 | return default 168 | 169 | # Helper function to safely extract string values 170 | def get_string(key: str, default=None) -> Optional[str]: 171 | value = info.get(key) 172 | return value if value and value != 'null' else default 173 | 174 | # Extract fundamental metrics 175 | metrics = { 176 | # Valuation Metrics 177 | "market_cap": get_numeric("marketCap"), 178 | "trailing_pe": get_numeric("trailingPE"), 179 | "forward_pe": get_numeric("forwardPE"), 180 | "price_to_book": get_numeric("priceToBook"), 181 | "price_to_sales": get_numeric("priceToSalesTrailing12Months"), 182 | "peg_ratio": get_numeric("pegRatio"), 183 | "enterprise_value": get_numeric("enterpriseValue"), 184 | "ev_to_revenue": get_numeric("enterpriseToRevenue"), 185 | "ev_to_ebitda": get_numeric("enterpriseToEbitda"), 186 | 187 | # Profitability Metrics 188 | "profit_margins": get_numeric("profitMargins"), 189 | "operating_margins": get_numeric("operatingMargins"), 190 | "gross_margins": get_numeric("grossMargins"), 191 | "return_on_equity": get_numeric("returnOnEquity"), 192 | "return_on_assets": get_numeric("returnOnAssets"), 193 | "ebitda": get_numeric("ebitda"), 194 | 195 | # Per-Share Metrics 196 | "trailing_eps": get_numeric("trailingEps"), 197 | "forward_eps": get_numeric("forwardEps"), 198 | "book_value": get_numeric("bookValue"), 199 | "revenue_per_share": get_numeric("revenuePerShare"), 200 | 201 | # Dividend Metrics 202 | "dividend_yield": get_numeric("dividendYield"), 203 | "dividend_rate": get_numeric("dividendRate"), 204 | "payout_ratio": get_numeric("payoutRatio"), 205 | 206 | # Financial Health 207 | "debt_to_equity": get_numeric("debtToEquity"), 208 | "current_ratio": get_numeric("currentRatio"), 209 | "quick_ratio": get_numeric("quickRatio"), 210 | "total_cash": get_numeric("totalCash"), 211 | "total_debt": get_numeric("totalDebt"), 212 | "free_cashflow": get_numeric("freeCashflow"), 213 | 214 | # Growth & Other 215 | "revenue_growth": get_numeric("revenueGrowth"), 216 | "earnings_growth": get_numeric("earningsGrowth"), 217 | "beta": get_numeric("beta"), 218 | "shares_outstanding": get_numeric("sharesOutstanding"), 219 | "float_shares": get_numeric("floatShares"), 220 | "held_percent_insiders": get_numeric("heldPercentInsiders"), 221 | "held_percent_institutions": get_numeric("heldPercentInstitutions"), 222 | } 223 | 224 | # Extract metadata 225 | metadata = { 226 | "symbol": symbol, 227 | "query_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 228 | "data_source": "yfinance", 229 | "currency": get_string("currency", "USD"), 230 | "company_name": get_string("longName"), 231 | "sector": get_string("sector"), 232 | "industry": get_string("industry"), 233 | } 234 | 235 | # Count non-null metrics 236 | non_null_count = sum(1 for v in metrics.values() if v is not None) 237 | 238 | logging.info( 239 | f"Successfully fetched {non_null_count} fundamental metrics for {symbol}" 240 | ) 241 | 242 | return { 243 | "success": True, 244 | "data": metrics, 245 | "metadata": metadata 246 | } 247 | 248 | except ValueError as ve: 249 | # Handle validation errors 250 | logging.error(f"Validation error: {str(ve)}") 251 | return { 252 | "success": False, 253 | "error": str(ve), 254 | "data": {}, 255 | "metadata": { 256 | "symbol": symbol if 'symbol' in locals() else None, 257 | "query_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") 258 | } 259 | } 260 | 261 | except Exception as e: 262 | # Handle unexpected errors 263 | logging.error(f"Unexpected error in get_key_metrics: {str(e)}", exc_info=True) 264 | return { 265 | "success": False, 266 | "error": f"An unexpected error occurred: {str(e)}", 267 | "data": {}, 268 | "metadata": { 269 | "symbol": symbol if 'symbol' in locals() else None, 270 | "query_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /user_case/1m_users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Create test users and test API 4 | Complete in one script: create users → configure agents → create profiles → test API calls 5 | """ 6 | 7 | import argparse 8 | import asyncio 9 | import asyncpg 10 | import jwt 11 | import json 12 | import time 13 | import requests 14 | import yaml 15 | import sys 16 | 17 | # Hardcoded JWT key (should match config.optional.yaml) 18 | # Decrypted from: gAAAAABpJiizBb7BmtHTYPTQc7kcKb8qNulxcTcOCPXxXw2zPo6w4H-QCb-htLdQMZl0ZuFFZx2y0jzwZufZ3Nz6s0DvkI7G6A== 19 | HARDCODED_JWT_KEY = "myjwtkey" 20 | 21 | USER_CONFIGS = [ 22 | { 23 | "email": "demo_user_alpha@test.com", 24 | "name": "Test User Alpha", 25 | "gender": 1, 26 | "agents": { 27 | "agent1": { 28 | "system_prompt": "You are a professional health advisor who excels at analyzing user health data and providing personalized recommendations. Your answers should be based on scientific evidence while considering the user's individual circumstances.", 29 | "temperature": 0.7, 30 | "max_tokens": 2000 31 | } 32 | }, 33 | "profile": "This is a male user in his 30s who is focused on cardiovascular health and weight management. He hopes to improve his health through scientific methods, including proper diet and regular exercise. The user has a strong interest in health data analysis and seeks personalized health advice." 34 | }, 35 | { 36 | "email": "demo_user_beta@test.com", 37 | "name": "Test User Beta", 38 | "gender": 2, 39 | "agents": { 40 | "agent2": { 41 | "system_prompt": "You are an experienced health advisor specializing in women's health management and nutritional consulting. You provide comprehensive health advice, including diet, exercise, and lifestyle.", 42 | "temperature": 0.7, 43 | "max_tokens": 2000 44 | }, 45 | "agent3": { 46 | "system_prompt": "You are a professional fitness coach who helps users develop scientific exercise plans and supervises their execution. You provide personalized training programs based on the user's physical condition and goals.", 47 | "temperature": 0.8, 48 | "max_tokens": 2500 49 | } 50 | }, 51 | "profile": "This is a female user in her 30s who is particularly concerned about women's health, nutritional balance, and mental health. She hopes to maintain a balance between work and life and improve overall well-being through a healthy lifestyle. The user is interested in yoga and aerobic exercise, and also pays attention to dietary nutrition." 52 | } 53 | ] 54 | 55 | 56 | async def get_db_connection(): 57 | """Get database connection""" 58 | # Load config 59 | config = {} 60 | for config_file in ['config.required.yaml', 'config.optional.yaml']: 61 | try: 62 | with open(config_file, 'r') as f: 63 | file_config = yaml.safe_load(f) 64 | if file_config: 65 | config.update(file_config) 66 | except FileNotFoundError: 67 | pass 68 | 69 | # Database connection 70 | pg_host = config.get('PG_HOST', 'localhost') 71 | if pg_host == 'db': 72 | pg_host = 'localhost' # Use localhost when running outside Docker 73 | 74 | pg_password = config.get('PG_PASSWORD', 'holistic_password') 75 | if isinstance(pg_password, str) and pg_password.startswith('gAAAAAB'): 76 | # Encrypted password, use Docker default 77 | pg_password = 'holistic_password' 78 | 79 | conn = await asyncpg.connect( 80 | host=pg_host, 81 | port=config.get('PG_PORT', 5432), 82 | user=config.get('PG_USER', 'holistic_user'), 83 | password=pg_password, 84 | database=config.get('PG_DBNAME', 'holistic_db') 85 | ) 86 | 87 | return conn 88 | 89 | 90 | async def create_or_get_user(conn, user_config): 91 | """Create or get user""" 92 | email = user_config['email'] 93 | name = user_config['name'] 94 | gender = user_config['gender'] 95 | 96 | # Check if user already exists 97 | user = await conn.fetchrow(''' 98 | SELECT id, email, name, gender 99 | FROM theta_ai.health_app_user 100 | WHERE email = $1 AND is_del = false 101 | ''', email) 102 | 103 | if user: 104 | print(f" ℹ️ User already exists: {email} (ID: {user['id']})") 105 | return user['id'] 106 | 107 | # Create new user 108 | user_id = await conn.fetchval(''' 109 | INSERT INTO theta_ai.health_app_user (email, name, gender, is_del) 110 | VALUES ($1, $2, $3, false) 111 | RETURNING id 112 | ''', email, name, gender) 113 | 114 | print(f" ✓ Created successfully: {email} (ID: {user_id})") 115 | return user_id 116 | 117 | 118 | async def upsert_user_agents(conn, user_id, agents): 119 | """Insert or update user agents configuration""" 120 | await conn.execute(''' 121 | INSERT INTO theta_ai.user_agent_prompt (user_id, prompt, created_at, updated_at) 122 | VALUES ($1, $2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) 123 | ON CONFLICT (user_id) 124 | DO UPDATE SET 125 | prompt = EXCLUDED.prompt, 126 | updated_at = CURRENT_TIMESTAMP 127 | ''', str(user_id), json.dumps(agents)) 128 | 129 | agent_names = ', '.join(agents.keys()) 130 | print(f" ✓ Agents configuration completed: {agent_names}") 131 | 132 | 133 | async def upsert_user_profile(conn, user_id, name, profile): 134 | await conn.execute(f"delete from theta_ai.health_user_profile_by_system where user_id ='{user_id}'") 135 | await conn.execute(''' 136 | INSERT INTO theta_ai.health_user_profile_by_system 137 | (user_id, name, version, common_part, create_time, last_update_time, is_deleted, last_execute_doc_id) 138 | VALUES ($1, $2, 1, $3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, false, 0) 139 | ''', str(user_id), f'{name} Health Profile', profile) 140 | 141 | print(f" ✓ Health profile created") 142 | 143 | 144 | def generate_jwt_token(email, user_id): 145 | """Generate JWT token""" 146 | jwt_key = HARDCODED_JWT_KEY 147 | 148 | payload = { 149 | 'sub': email, 150 | 'user_id': str(user_id), 151 | 'iss': 'theta_oauth', 152 | 'aud': 'theta', 153 | 'iat': int(time.time()), 154 | 'exp': int(time.time()) + 3600 155 | } 156 | 157 | token = jwt.encode(payload, jwt_key, algorithm='HS256') 158 | return token 159 | 160 | 161 | def call_chat_api(token, question, agent_name, server='http://localhost:18080'): 162 | """Call chat API""" 163 | url = f"{server}/api/chat" 164 | headers = { 165 | 'Content-Type': 'application/json', 166 | 'Authorization': f'Bearer {token}' 167 | } 168 | 169 | payload = { 170 | 'question': question, 171 | 'prompt_name': agent_name 172 | } 173 | 174 | try: 175 | response = requests.post(url, json=payload, headers=headers, timeout=60, stream=True) 176 | 177 | if response.status_code == 200: 178 | content_type = response.headers.get('Content-Type', '') 179 | 180 | # Check if it's a streaming response (SSE) 181 | if 'text/event-stream' in content_type or 'stream' in content_type.lower(): 182 | print(f" 📡 Streaming response:") 183 | print(f" {'─'*50}") 184 | full_content = "" 185 | has_content = False 186 | 187 | for line in response.iter_lines(): 188 | if line: 189 | line_str = line.decode('utf-8') 190 | # SSE format: data: {...} 191 | if line_str.startswith('data: '): 192 | data_str = line_str[6:] 193 | try: 194 | data = json.loads(data_str) 195 | if 'content' in data and data['content']: 196 | print(data['content'], end='', flush=True) 197 | full_content += data['content'] 198 | has_content = True 199 | elif 'delta' in data and 'content' in data['delta']: 200 | content = data['delta']['content'] 201 | if content: 202 | print(content, end='', flush=True) 203 | full_content += content 204 | has_content = True 205 | elif 'error' in data: 206 | print(f"\n ❌ Error: {json.dumps(data['error'], ensure_ascii=False)}") 207 | has_content = True 208 | except json.JSONDecodeError: 209 | if data_str.strip(): 210 | print(data_str, end='', flush=True) 211 | full_content += data_str 212 | has_content = True 213 | 214 | print(f"\n {'─'*50}") 215 | 216 | if not has_content: 217 | print(" ⚠️ Warning: Received empty response") 218 | return None 219 | 220 | return {'content': full_content, 'type': 'stream'} 221 | else: 222 | # Regular JSON response 223 | result = response.json() 224 | print(f" ✓ Response: {json.dumps(result, ensure_ascii=False)[:100]}...") 225 | return result 226 | else: 227 | print(f" ❌ API call failed: {response.status_code}") 228 | print(f" Response: {response.text[:200]}") 229 | return None 230 | 231 | except Exception as e: 232 | print(f" ❌ Call error: {e}") 233 | return None 234 | 235 | 236 | async def process_user(conn, user_config, args): 237 | """Process single user: create → configure → test""" 238 | print(f"\n{'='*60}") 239 | print(f"📝 Processing user: {user_config['name']} ({user_config['email']})") 240 | print(f"{'='*60}") 241 | 242 | try: 243 | # 1. Create user 244 | print("\n▶ Creating user account...") 245 | user_id = await create_or_get_user(conn, user_config) 246 | 247 | # 2. Configure agents 248 | print("\n▶ Configuring user agents...") 249 | await upsert_user_agents(conn, user_id, user_config['agents']) 250 | 251 | # 3. Create health profile 252 | print("\n▶ Creating health profile...") 253 | await upsert_user_profile(conn, user_id, user_config['name'], user_config['profile']) 254 | 255 | # 4. Generate JWT token 256 | print("\n▶ Generating JWT token...") 257 | token = generate_jwt_token(user_config['email'], user_id) 258 | print(f" ✓ Token generated successfully (first 50 chars): {token[:50]}...") 259 | 260 | # 5. Test API (if not skipped) 261 | if not args.skip_test: 262 | print("\n▶ Testing API call...") 263 | # Get first agent name 264 | first_agent = list(user_config['agents'].keys())[0] 265 | print(f" Agent: {first_agent}") 266 | print(f" Question: {args.question}") 267 | 268 | result = call_chat_api(token, args.question, first_agent, args.server) 269 | 270 | if result: 271 | print(f"\n ✅ API test successful!") 272 | else: 273 | print(f"\n ❌ API test failed!") 274 | 275 | print(f"\n✅ User {user_config['name']} processing completed!") 276 | print(f" ID: {user_id}") 277 | print(f" Email: {user_config['email']}") 278 | print(f" Agents: {', '.join(user_config['agents'].keys())}") 279 | 280 | return True 281 | 282 | except Exception as e: 283 | print(f"\n❌ User processing failed: {e}") 284 | import traceback 285 | traceback.print_exc() 286 | return False 287 | 288 | 289 | async def main(): 290 | parser = argparse.ArgumentParser(description='Create test users and test API') 291 | parser.add_argument('--question', '-q', default='Hello, please introduce yourself', help='Test question') 292 | parser.add_argument('--server', default='http://localhost:18080', help='Server address') 293 | parser.add_argument('--skip-test', action='store_true', help='Skip API test') 294 | 295 | args = parser.parse_args() 296 | 297 | print("="*60) 298 | print("🚀 Creating test users and data") 299 | print("="*60) 300 | 301 | # Connect to database 302 | try: 303 | conn = await get_db_connection() 304 | print("✓ Database connection successful\n") 305 | except Exception as e: 306 | print(f"❌ Database connection failed: {e}") 307 | sys.exit(1) 308 | 309 | # Process all users 310 | success_count = 0 311 | fail_count = 0 312 | 313 | for user_config in USER_CONFIGS: 314 | success = await process_user(conn, user_config, args) 315 | if success: 316 | success_count += 1 317 | else: 318 | fail_count += 1 319 | 320 | # Close connection 321 | await conn.close() 322 | 323 | # Display summary 324 | print("\n" + "="*60) 325 | print("📊 Creation completed") 326 | print("="*60) 327 | print(f"✅ Successful: {success_count} users") 328 | if fail_count > 0: 329 | print(f"❌ Failed: {fail_count} users") 330 | 331 | print("\n💡 Next steps:") 332 | print(" 1. Login with the following emails:") 333 | for user_config in USER_CONFIGS: 334 | print(f" - {user_config['email']}") 335 | print(" 2. Verification code: 000000") 336 | print(" 3. Start using the configured agents") 337 | print() 338 | 339 | if fail_count > 0: 340 | sys.exit(1) 341 | 342 | 343 | if __name__ == '__main__': 344 | asyncio.run(main()) 345 | 346 | -------------------------------------------------------------------------------- /tools/health/genetic_service.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Genetic Service 4 | Responsible for genetic data management and querying 5 | """ 6 | 7 | import traceback 8 | from typing import Any, Dict, List, Optional, Union 9 | 10 | from mirobody.utils.data import DataConverter 11 | from mirobody.utils.db import execute_query 12 | from mirobody.utils.log import log_setter 13 | 14 | 15 | class GeneticService(): 16 | """Genetic data service""" 17 | 18 | # Constants 19 | MAX_NEARBY_VARIANTS_PER_QUERY = 20 20 | DEFAULT_NEARBY_RANGE = 1000000 # 1M base pairs 21 | 22 | def __init__(self): 23 | self.name = "Genetic Service" 24 | self.version = "1.0.0" 25 | self.data_converter = DataConverter() 26 | 27 | def _build_rsid_conditions( 28 | self, rsid: Union[str, List[str]], params: Dict[str, Any] 29 | ) -> str: 30 | """ 31 | Build SQL conditions for rsid parameter. 32 | 33 | Args: 34 | rsid: Variant identifier(s), supports single string or string list 35 | params: Parameter dictionary to update 36 | 37 | Returns: 38 | SQL condition string 39 | """ 40 | # Handle comma-separated string 41 | if isinstance(rsid, str) and "," in rsid: 42 | rsid_list = [r.strip() for r in rsid.split(",") if r.strip()] 43 | placeholders = [f":rsid_{i}" for i in range(len(rsid_list))] 44 | for i, r in enumerate(rsid_list): 45 | params[f"rsid_{i}"] = r 46 | return f" AND rsid IN ({', '.join(placeholders)})" 47 | 48 | # Handle list 49 | elif isinstance(rsid, list): 50 | if len(rsid) == 1: 51 | params["rsid"] = rsid[0] 52 | return " AND rsid = :rsid" 53 | else: 54 | placeholders = [f":rsid_{i}" for i in range(len(rsid))] 55 | for i, r in enumerate(rsid): 56 | params[f"rsid_{i}"] = r 57 | return f" AND rsid IN ({', '.join(placeholders)})" 58 | 59 | # Single rsid 60 | else: 61 | params["rsid"] = rsid 62 | return " AND rsid = :rsid" 63 | 64 | def _format_compact_record( 65 | self, record: Dict[str, Any], distance: Optional[int] = None, query_rsid: Optional[str] = None 66 | ) -> Dict[str, Any]: 67 | """ 68 | Format record into compact format. 69 | 70 | Args: 71 | record: Original record 72 | distance: Distance from query position (for nearby variants) 73 | query_rsid: Query rsid (for nearby variants) 74 | 75 | Returns: 76 | Compact formatted record 77 | """ 78 | compact_record = { 79 | "r": record.get("rsid"), 80 | "c": record.get("chromosome"), 81 | "p": record.get("position"), 82 | "g": record.get("genotype"), 83 | } 84 | 85 | if distance is not None: 86 | compact_record["d"] = distance 87 | if query_rsid is not None: 88 | compact_record["q"] = query_rsid 89 | 90 | # Keep only non-null values 91 | return {k: v for k, v in compact_record.items() if v is not None} 92 | 93 | async def _query_nearby_variants( 94 | self, 95 | user_id: str, 96 | queried_positions: Dict[str, List[int]], 97 | queried_rsids: set, 98 | nearby_range: int, 99 | limit: int, 100 | result: List[Dict[str, Any]], 101 | ) -> List[Dict[str, Any]]: 102 | """ 103 | Query nearby variants for all queried positions in a single optimized query. 104 | 105 | Args: 106 | user_id: User identifier 107 | queried_positions: Dictionary of chromosome -> positions 108 | queried_rsids: Set of already queried rsids to exclude 109 | nearby_range: Search range in base pairs 110 | limit: Maximum results per variant 111 | result: Original query results 112 | 113 | Returns: 114 | List of nearby variants in compact format 115 | """ 116 | if not queried_positions: 117 | return [] 118 | 119 | nearby_results = [] 120 | nearby_limit = min(self.MAX_NEARBY_VARIANTS_PER_QUERY, limit) 121 | 122 | # Build optimized query using UNION ALL for multiple positions 123 | union_queries = [] 124 | union_params = {"user_id": user_id, "nearby_limit": nearby_limit} 125 | param_index = 0 126 | 127 | for chr_key, positions in queried_positions.items(): 128 | for pos in positions: 129 | # Create unique parameter names for each position 130 | chr_param = f"chr_{param_index}" 131 | min_pos_param = f"min_pos_{param_index}" 132 | max_pos_param = f"max_pos_{param_index}" 133 | target_pos_param = f"target_pos_{param_index}" 134 | 135 | union_params[chr_param] = chr_key 136 | union_params[min_pos_param] = pos - nearby_range 137 | union_params[max_pos_param] = pos + nearby_range 138 | union_params[target_pos_param] = pos 139 | 140 | # Build exclude clause 141 | exclude_clause = "" 142 | if queried_rsids: 143 | exclude_rsids_list = list(queried_rsids) 144 | exclude_params = [f"exclude_{param_index}_{i}" for i in range(len(exclude_rsids_list))] 145 | for i, rsid in enumerate(exclude_rsids_list): 146 | union_params[f"exclude_{param_index}_{i}"] = rsid 147 | exclude_clause = f"AND rsid NOT IN ({', '.join([':' + p for p in exclude_params])})" 148 | 149 | union_queries.append(f""" 150 | SELECT 151 | rsid, chromosome, position, genotype, 152 | ABS(position - :{target_pos_param}) as distance, 153 | :{target_pos_param} as query_position, 154 | :{chr_param} as query_chromosome 155 | FROM theta_ai.th_series_data_genetic 156 | WHERE user_id = :user_id 157 | AND is_deleted = false 158 | AND chromosome = :{chr_param} 159 | AND position BETWEEN :{min_pos_param} AND :{max_pos_param} 160 | {exclude_clause} 161 | """) 162 | 163 | param_index += 1 164 | 165 | # Combine all queries with UNION ALL and apply global ordering and limit 166 | if union_queries: 167 | combined_sql = f""" 168 | SELECT * FROM ( 169 | {' UNION ALL '.join(union_queries)} 170 | ) AS combined 171 | ORDER BY query_chromosome, query_position, distance 172 | LIMIT :nearby_limit 173 | """ 174 | 175 | nearby_data = await execute_query( 176 | combined_sql, 177 | union_params, 178 | mode="async", 179 | query_type="select", 180 | ) 181 | 182 | if nearby_data: 183 | nearby_converted = await self.data_converter.convert_list(nearby_data) 184 | 185 | # Format results 186 | for nearby_record in nearby_converted: 187 | # Find the corresponding query rsid 188 | query_pos = nearby_record.get("query_position") 189 | query_chr = nearby_record.get("query_chromosome") 190 | query_rsid = next( 191 | (r.get("rsid") for r in result 192 | if r.get("position") == query_pos and r.get("chromosome") == query_chr), 193 | None 194 | ) 195 | 196 | compact_record = self._format_compact_record( 197 | nearby_record, 198 | distance=nearby_record.get("distance"), 199 | query_rsid=query_rsid 200 | ) 201 | nearby_results.append(compact_record) 202 | 203 | return nearby_results 204 | 205 | async def get_genetic_data( 206 | self, 207 | rsid: Union[str, List[str]], 208 | user_info: Dict[str, Any], 209 | chromosome: Optional[str] = None, 210 | position: Optional[int] = None, 211 | genotype: Optional[str] = None, 212 | limit: int = 100, 213 | offset: int = 0, 214 | include_nearby: bool = True, 215 | nearby_range: Optional[int] = None, 216 | ) -> Dict[str, Any]: 217 | """ 218 | Retrieve variant information by rsid, with optional lookup of nearby related variants. 219 | 220 | Args: 221 | rsid: Variant identifier(s), supports single string or string list 222 | chromosome: Chromosome reference 223 | position: Genomic position 224 | genotype: Genotype information (if available) 225 | limit: Maximum number of records to return 226 | offset: Pagination offset 227 | include_nearby: Whether to include nearby variants 228 | nearby_range: Search range for nearby variants (in base pairs) 229 | 230 | Returns: 231 | Dictionary containing variant data for the requested rsid(s), optionally 232 | including related variants in the specified nearby range. 233 | """ 234 | try: 235 | # Get user ID from user_info 236 | user_id = user_info.get("user_id") 237 | if not user_id: 238 | return { 239 | "success": False, 240 | "error": "User ID is required", 241 | "data": None, 242 | } 243 | 244 | # Set default nearby range 245 | if nearby_range is None: 246 | nearby_range = self.DEFAULT_NEARBY_RANGE 247 | 248 | # Build basic query 249 | sql = """ 250 | SELECT id, user_id, rsid, chromosome, position, genotype, 251 | create_time, update_time 252 | FROM theta_ai.th_series_data_genetic 253 | WHERE user_id = :user_id AND is_deleted = false 254 | """ 255 | 256 | # Build parameter dictionary 257 | params = {"user_id": user_id} 258 | 259 | # Handle rsid parameter using helper method 260 | sql += self._build_rsid_conditions(rsid, params) 261 | 262 | if chromosome: 263 | sql += " AND chromosome = :chromosome" 264 | params["chromosome"] = chromosome 265 | 266 | if position: 267 | sql += " AND position = :position" 268 | params["position"] = position 269 | 270 | if genotype: 271 | sql += " AND genotype = :genotype" 272 | params["genotype"] = genotype 273 | 274 | # Add sorting and pagination 275 | sql += " ORDER BY chromosome, position" 276 | sql += " LIMIT :limit OFFSET :offset" 277 | params["limit"] = limit 278 | params["offset"] = offset 279 | 280 | # Execute query 281 | result = await execute_query(sql, params, mode="async", query_type="select") 282 | 283 | # Debug logging 284 | log_setter(level="info", _input=f"Query results type: {type(result)}, length: {len(result) if result else 0}") 285 | 286 | # Data conversion 287 | result = await self.data_converter.convert_list(result) 288 | 289 | # Convert to compact format using helper method 290 | compact_result = [self._format_compact_record(record) for record in result] 291 | 292 | # Collect queried variant information (optimized) 293 | queried_positions = {} 294 | queried_rsids = {record.get("rsid") for record in result if record.get("rsid")} 295 | 296 | for record in result: 297 | if record.get("chromosome") and record.get("position"): 298 | chr_key = record["chromosome"] 299 | queried_positions.setdefault(chr_key, []).append(record["position"]) 300 | 301 | # Query nearby variants using optimized single-query approach 302 | nearby_results = [] 303 | if include_nearby and queried_positions: 304 | nearby_results = await self._query_nearby_variants( 305 | user_id=user_id, 306 | queried_positions=queried_positions, 307 | queried_rsids=queried_rsids, 308 | nearby_range=nearby_range, 309 | limit=limit, 310 | result=result, 311 | ) 312 | 313 | log_setter(level="info", _input=f"Query completed, returning {len(result)} genetic records, {len(nearby_results)} nearby variants") 314 | 315 | # Fallback strategy: if no genetic data 316 | if not result: 317 | log_setter(level="info", _input="No genetic data found, returning structured no-data response") 318 | 319 | return { 320 | "success": True, 321 | "message": "No genetic data found. To access genetic analysis including SNPs, genotypes, chromosomes, and positions, please upload your genetic information first.", 322 | "data": "No genetic data available for the requested variant(s). Please upload your genetic test results from services like 23andMe, AncestryDNA, or medical genetic testing to access personalized genetic insights.", 323 | "limit": limit, 324 | "offset": offset, 325 | "upload_suggestion": { 326 | "message": "No genetic data available for analysis. To get personalized genetic insights and understand your genetic variations, please upload your genetic test results.", 327 | "upload_url": "https://localhost:18080/drive", 328 | "instructions": "Upload your genetic test results from services like 23andMe, AncestryDNA, or medical genetic testing to enable comprehensive genetic analysis and health risk assessment.", 329 | }, 330 | "redirect_to_upload": True, 331 | } 332 | 333 | # Apply data truncation with compact format 334 | response_data = { 335 | "success": True, 336 | "data": { 337 | "q": compact_result, # queried variants 338 | "n": nearby_results if include_nearby else [], # nearby variants 339 | "s": { # summary 340 | "tq": len(result), # total queried 341 | "tn": len(nearby_results) if include_nearby else 0, # total nearby 342 | "chr": list(queried_positions.keys()), # chromosomes 343 | "range_kb": nearby_range // 1000 if include_nearby else 0, # range in kb 344 | }, 345 | "_legend": { 346 | "r": "rsid", 347 | "c": "chromosome", 348 | "p": "position", 349 | "g": "genotype", 350 | "d": "distance_from_query", 351 | "q": "query_rsid", 352 | "tq": "total_queried", 353 | "tn": "total_nearby", 354 | "chr": "chromosomes", 355 | }, 356 | }, 357 | "limit": limit, 358 | "offset": offset, 359 | } 360 | return response_data 361 | 362 | except Exception as e: 363 | log_setter(level="error", _input=f"{str(e)}, {traceback.format_exc()}") 364 | 365 | return { 366 | "success": False, 367 | "error": f"Failed to get genetic data: {str(e)}", 368 | "data": None, 369 | "redirect_to_upload": True, 370 | } 371 | -------------------------------------------------------------------------------- /htdoc/github-markdown-light.css: -------------------------------------------------------------------------------- 1 | .markdown-body { 2 | -ms-text-size-adjust: 100%; 3 | -webkit-text-size-adjust: 100%; 4 | margin: 0; 5 | color: #24292f; 6 | background-color: #ffffff; 7 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; 8 | font-size: 16px; 9 | line-height: 1.5; 10 | word-wrap: break-word; 11 | } 12 | 13 | .markdown-body .octicon { 14 | display: inline-block; 15 | fill: currentColor; 16 | vertical-align: text-bottom; 17 | } 18 | 19 | .markdown-body h1:hover .anchor .octicon-link:before, 20 | .markdown-body h2:hover .anchor .octicon-link:before, 21 | .markdown-body h3:hover .anchor .octicon-link:before, 22 | .markdown-body h4:hover .anchor .octicon-link:before, 23 | .markdown-body h5:hover .anchor .octicon-link:before, 24 | .markdown-body h6:hover .anchor .octicon-link:before { 25 | width: 16px; 26 | height: 16px; 27 | content: ' '; 28 | display: inline-block; 29 | background-color: currentColor; 30 | -webkit-mask-image: url("data:image/svg+xml,"); 31 | mask-image: url("data:image/svg+xml,"); 32 | } 33 | 34 | .markdown-body details, 35 | .markdown-body figcaption, 36 | .markdown-body figure { 37 | display: block; 38 | } 39 | 40 | .markdown-body summary { 41 | display: list-item; 42 | } 43 | 44 | .markdown-body [hidden] { 45 | display: none !important; 46 | } 47 | 48 | .markdown-body a { 49 | background-color: transparent; 50 | color: #0969da; 51 | text-decoration: none; 52 | } 53 | 54 | .markdown-body abbr[title] { 55 | border-bottom: none; 56 | text-decoration: underline dotted; 57 | } 58 | 59 | .markdown-body b, 60 | .markdown-body strong { 61 | font-weight: 600; 62 | } 63 | 64 | .markdown-body dfn { 65 | font-style: italic; 66 | } 67 | 68 | .markdown-body h1 { 69 | margin: .67em 0; 70 | font-weight: 600; 71 | padding-bottom: .3em; 72 | font-size: 2em; 73 | border-bottom: 1px solid hsla(210,18%,87%,1); 74 | } 75 | 76 | .markdown-body mark { 77 | background-color: #fff8c5; 78 | color: #24292f; 79 | } 80 | 81 | .markdown-body small { 82 | font-size: 90%; 83 | } 84 | 85 | .markdown-body sub, 86 | .markdown-body sup { 87 | font-size: 75%; 88 | line-height: 0; 89 | position: relative; 90 | vertical-align: baseline; 91 | } 92 | 93 | .markdown-body sub { 94 | bottom: -0.25em; 95 | } 96 | 97 | .markdown-body sup { 98 | top: -0.5em; 99 | } 100 | 101 | .markdown-body img { 102 | border-style: none; 103 | max-width: 100%; 104 | box-sizing: content-box; 105 | background-color: #ffffff; 106 | } 107 | 108 | .markdown-body code, 109 | .markdown-body kbd, 110 | .markdown-body pre, 111 | .markdown-body samp { 112 | font-family: monospace; 113 | font-size: 1em; 114 | } 115 | 116 | .markdown-body figure { 117 | margin: 1em 40px; 118 | } 119 | 120 | .markdown-body hr { 121 | box-sizing: content-box; 122 | overflow: hidden; 123 | background: transparent; 124 | border-bottom: 1px solid hsla(210,18%,87%,1); 125 | height: .25em; 126 | padding: 0; 127 | margin: 24px 0; 128 | background-color: #d0d7de; 129 | border: 0; 130 | } 131 | 132 | .markdown-body input { 133 | font: inherit; 134 | margin: 0; 135 | overflow: visible; 136 | font-family: inherit; 137 | font-size: inherit; 138 | line-height: inherit; 139 | } 140 | 141 | .markdown-body [type=button], 142 | .markdown-body [type=reset], 143 | .markdown-body [type=submit] { 144 | -webkit-appearance: button; 145 | } 146 | 147 | .markdown-body [type=checkbox], 148 | .markdown-body [type=radio] { 149 | box-sizing: border-box; 150 | padding: 0; 151 | } 152 | 153 | .markdown-body [type=number]::-webkit-inner-spin-button, 154 | .markdown-body [type=number]::-webkit-outer-spin-button { 155 | height: auto; 156 | } 157 | 158 | .markdown-body [type=search]::-webkit-search-cancel-button, 159 | .markdown-body [type=search]::-webkit-search-decoration { 160 | -webkit-appearance: none; 161 | } 162 | 163 | .markdown-body ::-webkit-input-placeholder { 164 | color: inherit; 165 | opacity: .54; 166 | } 167 | 168 | .markdown-body ::-webkit-file-upload-button { 169 | -webkit-appearance: button; 170 | font: inherit; 171 | } 172 | 173 | .markdown-body a:hover { 174 | text-decoration: underline; 175 | } 176 | 177 | .markdown-body ::placeholder { 178 | color: #6e7781; 179 | opacity: 1; 180 | } 181 | 182 | .markdown-body hr::before { 183 | display: table; 184 | content: ""; 185 | } 186 | 187 | .markdown-body hr::after { 188 | display: table; 189 | clear: both; 190 | content: ""; 191 | } 192 | 193 | .markdown-body table { 194 | border-spacing: 0; 195 | border-collapse: collapse; 196 | display: block; 197 | width: max-content; 198 | max-width: 100%; 199 | overflow: auto; 200 | } 201 | 202 | .markdown-body td, 203 | .markdown-body th { 204 | padding: 0; 205 | } 206 | 207 | .markdown-body details summary { 208 | cursor: pointer; 209 | } 210 | 211 | .markdown-body details:not([open])>*:not(summary) { 212 | display: none !important; 213 | } 214 | 215 | .markdown-body a:focus, 216 | .markdown-body [role=button]:focus, 217 | .markdown-body input[type=radio]:focus, 218 | .markdown-body input[type=checkbox]:focus { 219 | outline: 2px solid #0969da; 220 | outline-offset: -2px; 221 | box-shadow: none; 222 | } 223 | 224 | .markdown-body a:focus:not(:focus-visible), 225 | .markdown-body [role=button]:focus:not(:focus-visible), 226 | .markdown-body input[type=radio]:focus:not(:focus-visible), 227 | .markdown-body input[type=checkbox]:focus:not(:focus-visible) { 228 | outline: solid 1px transparent; 229 | } 230 | 231 | .markdown-body a:focus-visible, 232 | .markdown-body [role=button]:focus-visible, 233 | .markdown-body input[type=radio]:focus-visible, 234 | .markdown-body input[type=checkbox]:focus-visible { 235 | outline: 2px solid #0969da; 236 | outline-offset: -2px; 237 | box-shadow: none; 238 | } 239 | 240 | .markdown-body a:not([class]):focus, 241 | .markdown-body a:not([class]):focus-visible, 242 | .markdown-body input[type=radio]:focus, 243 | .markdown-body input[type=radio]:focus-visible, 244 | .markdown-body input[type=checkbox]:focus, 245 | .markdown-body input[type=checkbox]:focus-visible { 246 | outline-offset: 0; 247 | } 248 | 249 | .markdown-body kbd { 250 | display: inline-block; 251 | padding: 3px 5px; 252 | font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 253 | line-height: 10px; 254 | color: #24292f; 255 | vertical-align: middle; 256 | background-color: #f6f8fa; 257 | border: solid 1px rgba(175,184,193,0.2); 258 | border-bottom-color: rgba(175,184,193,0.2); 259 | border-radius: 6px; 260 | box-shadow: inset 0 -1px 0 rgba(175,184,193,0.2); 261 | } 262 | 263 | .markdown-body h1, 264 | .markdown-body h2, 265 | .markdown-body h3, 266 | .markdown-body h4, 267 | .markdown-body h5, 268 | .markdown-body h6 { 269 | margin-top: 24px; 270 | margin-bottom: 16px; 271 | font-weight: 600; 272 | line-height: 1.25; 273 | } 274 | 275 | .markdown-body h2 { 276 | font-weight: 600; 277 | padding-bottom: .3em; 278 | font-size: 1.5em; 279 | border-bottom: 1px solid hsla(210,18%,87%,1); 280 | } 281 | 282 | .markdown-body h3 { 283 | font-weight: 600; 284 | font-size: 1.25em; 285 | } 286 | 287 | .markdown-body h4 { 288 | font-weight: 600; 289 | font-size: 1em; 290 | } 291 | 292 | .markdown-body h5 { 293 | font-weight: 600; 294 | font-size: .875em; 295 | } 296 | 297 | .markdown-body h6 { 298 | font-weight: 600; 299 | font-size: .85em; 300 | color: #57606a; 301 | } 302 | 303 | .markdown-body p { 304 | margin-top: 0; 305 | margin-bottom: 10px; 306 | } 307 | 308 | .markdown-body blockquote { 309 | margin: 0; 310 | padding: 0 1em; 311 | color: #57606a; 312 | border-left: .25em solid #d0d7de; 313 | } 314 | 315 | .markdown-body ul, 316 | .markdown-body ol { 317 | margin-top: 0; 318 | margin-bottom: 0; 319 | padding-left: 2em; 320 | } 321 | 322 | .markdown-body ol ol, 323 | .markdown-body ul ol { 324 | list-style-type: lower-roman; 325 | } 326 | 327 | .markdown-body ul ul ol, 328 | .markdown-body ul ol ol, 329 | .markdown-body ol ul ol, 330 | .markdown-body ol ol ol { 331 | list-style-type: lower-alpha; 332 | } 333 | 334 | .markdown-body dd { 335 | margin-left: 0; 336 | } 337 | 338 | .markdown-body tt, 339 | .markdown-body code, 340 | .markdown-body samp { 341 | font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 342 | font-size: 12px; 343 | } 344 | 345 | .markdown-body pre { 346 | margin-top: 0; 347 | margin-bottom: 0; 348 | font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 349 | font-size: 12px; 350 | word-wrap: normal; 351 | } 352 | 353 | .markdown-body .octicon { 354 | display: inline-block; 355 | overflow: visible !important; 356 | vertical-align: text-bottom; 357 | fill: currentColor; 358 | } 359 | 360 | .markdown-body input::-webkit-outer-spin-button, 361 | .markdown-body input::-webkit-inner-spin-button { 362 | margin: 0; 363 | -webkit-appearance: none; 364 | appearance: none; 365 | } 366 | 367 | .markdown-body::before { 368 | display: table; 369 | content: ""; 370 | } 371 | 372 | .markdown-body::after { 373 | display: table; 374 | clear: both; 375 | content: ""; 376 | } 377 | 378 | .markdown-body>*:first-child { 379 | margin-top: 0 !important; 380 | } 381 | 382 | .markdown-body>*:last-child { 383 | margin-bottom: 0 !important; 384 | } 385 | 386 | .markdown-body a:not([href]) { 387 | color: inherit; 388 | text-decoration: none; 389 | } 390 | 391 | .markdown-body .absent { 392 | color: #cf222e; 393 | } 394 | 395 | .markdown-body .anchor { 396 | float: left; 397 | padding-right: 4px; 398 | margin-left: -20px; 399 | line-height: 1; 400 | } 401 | 402 | .markdown-body .anchor:focus { 403 | outline: none; 404 | } 405 | 406 | .markdown-body p, 407 | .markdown-body blockquote, 408 | .markdown-body ul, 409 | .markdown-body ol, 410 | .markdown-body dl, 411 | .markdown-body table, 412 | .markdown-body pre, 413 | .markdown-body details { 414 | margin-top: 0; 415 | margin-bottom: 16px; 416 | } 417 | 418 | .markdown-body blockquote>:first-child { 419 | margin-top: 0; 420 | } 421 | 422 | .markdown-body blockquote>:last-child { 423 | margin-bottom: 0; 424 | } 425 | 426 | .markdown-body h1 .octicon-link, 427 | .markdown-body h2 .octicon-link, 428 | .markdown-body h3 .octicon-link, 429 | .markdown-body h4 .octicon-link, 430 | .markdown-body h5 .octicon-link, 431 | .markdown-body h6 .octicon-link { 432 | color: #24292f; 433 | vertical-align: middle; 434 | visibility: hidden; 435 | } 436 | 437 | .markdown-body h1:hover .anchor, 438 | .markdown-body h2:hover .anchor, 439 | .markdown-body h3:hover .anchor, 440 | .markdown-body h4:hover .anchor, 441 | .markdown-body h5:hover .anchor, 442 | .markdown-body h6:hover .anchor { 443 | text-decoration: none; 444 | } 445 | 446 | .markdown-body h1:hover .anchor .octicon-link, 447 | .markdown-body h2:hover .anchor .octicon-link, 448 | .markdown-body h3:hover .anchor .octicon-link, 449 | .markdown-body h4:hover .anchor .octicon-link, 450 | .markdown-body h5:hover .anchor .octicon-link, 451 | .markdown-body h6:hover .anchor .octicon-link { 452 | visibility: visible; 453 | } 454 | 455 | .markdown-body h1 tt, 456 | .markdown-body h1 code, 457 | .markdown-body h2 tt, 458 | .markdown-body h2 code, 459 | .markdown-body h3 tt, 460 | .markdown-body h3 code, 461 | .markdown-body h4 tt, 462 | .markdown-body h4 code, 463 | .markdown-body h5 tt, 464 | .markdown-body h5 code, 465 | .markdown-body h6 tt, 466 | .markdown-body h6 code { 467 | padding: 0 .2em; 468 | font-size: inherit; 469 | } 470 | 471 | .markdown-body summary h1, 472 | .markdown-body summary h2, 473 | .markdown-body summary h3, 474 | .markdown-body summary h4, 475 | .markdown-body summary h5, 476 | .markdown-body summary h6 { 477 | display: inline-block; 478 | } 479 | 480 | .markdown-body summary h1 .anchor, 481 | .markdown-body summary h2 .anchor, 482 | .markdown-body summary h3 .anchor, 483 | .markdown-body summary h4 .anchor, 484 | .markdown-body summary h5 .anchor, 485 | .markdown-body summary h6 .anchor { 486 | margin-left: -40px; 487 | } 488 | 489 | .markdown-body summary h1, 490 | .markdown-body summary h2 { 491 | padding-bottom: 0; 492 | border-bottom: 0; 493 | } 494 | 495 | .markdown-body ul.no-list, 496 | .markdown-body ol.no-list { 497 | padding: 0; 498 | list-style-type: none; 499 | } 500 | 501 | .markdown-body ol[type=a] { 502 | list-style-type: lower-alpha; 503 | } 504 | 505 | .markdown-body ol[type=A] { 506 | list-style-type: upper-alpha; 507 | } 508 | 509 | .markdown-body ol[type=i] { 510 | list-style-type: lower-roman; 511 | } 512 | 513 | .markdown-body ol[type=I] { 514 | list-style-type: upper-roman; 515 | } 516 | 517 | .markdown-body ol[type="1"] { 518 | list-style-type: decimal; 519 | } 520 | 521 | .markdown-body div>ol:not([type]) { 522 | list-style-type: decimal; 523 | } 524 | 525 | .markdown-body ul ul, 526 | .markdown-body ul ol, 527 | .markdown-body ol ol, 528 | .markdown-body ol ul { 529 | margin-top: 0; 530 | margin-bottom: 0; 531 | } 532 | 533 | .markdown-body li>p { 534 | margin-top: 16px; 535 | } 536 | 537 | .markdown-body li+li { 538 | margin-top: .25em; 539 | } 540 | 541 | .markdown-body dl { 542 | padding: 0; 543 | } 544 | 545 | .markdown-body dl dt { 546 | padding: 0; 547 | margin-top: 16px; 548 | font-size: 1em; 549 | font-style: italic; 550 | font-weight: 600; 551 | } 552 | 553 | .markdown-body dl dd { 554 | padding: 0 16px; 555 | margin-bottom: 16px; 556 | } 557 | 558 | .markdown-body table th { 559 | font-weight: 600; 560 | } 561 | 562 | .markdown-body table th, 563 | .markdown-body table td { 564 | padding: 6px 13px; 565 | border: 1px solid #d0d7de; 566 | } 567 | 568 | .markdown-body table tr { 569 | background-color: #ffffff; 570 | border-top: 1px solid hsla(210,18%,87%,1); 571 | } 572 | 573 | .markdown-body table tr:nth-child(2n) { 574 | background-color: #f6f8fa; 575 | } 576 | 577 | .markdown-body table img { 578 | background-color: transparent; 579 | } 580 | 581 | .markdown-body img[align=right] { 582 | padding-left: 20px; 583 | } 584 | 585 | .markdown-body img[align=left] { 586 | padding-right: 20px; 587 | } 588 | 589 | .markdown-body .emoji { 590 | max-width: none; 591 | vertical-align: text-top; 592 | background-color: transparent; 593 | } 594 | 595 | .markdown-body span.frame { 596 | display: block; 597 | overflow: hidden; 598 | } 599 | 600 | .markdown-body span.frame>span { 601 | display: block; 602 | float: left; 603 | width: auto; 604 | padding: 7px; 605 | margin: 13px 0 0; 606 | overflow: hidden; 607 | border: 1px solid #d0d7de; 608 | } 609 | 610 | .markdown-body span.frame span img { 611 | display: block; 612 | float: left; 613 | } 614 | 615 | .markdown-body span.frame span span { 616 | display: block; 617 | padding: 5px 0 0; 618 | clear: both; 619 | color: #24292f; 620 | } 621 | 622 | .markdown-body span.align-center { 623 | display: block; 624 | overflow: hidden; 625 | clear: both; 626 | } 627 | 628 | .markdown-body span.align-center>span { 629 | display: block; 630 | margin: 13px auto 0; 631 | overflow: hidden; 632 | text-align: center; 633 | } 634 | 635 | .markdown-body span.align-center span img { 636 | margin: 0 auto; 637 | text-align: center; 638 | } 639 | 640 | .markdown-body span.align-right { 641 | display: block; 642 | overflow: hidden; 643 | clear: both; 644 | } 645 | 646 | .markdown-body span.align-right>span { 647 | display: block; 648 | margin: 13px 0 0; 649 | overflow: hidden; 650 | text-align: right; 651 | } 652 | 653 | .markdown-body span.align-right span img { 654 | margin: 0; 655 | text-align: right; 656 | } 657 | 658 | .markdown-body span.float-left { 659 | display: block; 660 | float: left; 661 | margin-right: 13px; 662 | overflow: hidden; 663 | } 664 | 665 | .markdown-body span.float-left span { 666 | margin: 13px 0 0; 667 | } 668 | 669 | .markdown-body span.float-right { 670 | display: block; 671 | float: right; 672 | margin-left: 13px; 673 | overflow: hidden; 674 | } 675 | 676 | .markdown-body span.float-right>span { 677 | display: block; 678 | margin: 13px auto 0; 679 | overflow: hidden; 680 | text-align: right; 681 | } 682 | 683 | .markdown-body code, 684 | .markdown-body tt { 685 | padding: .2em .4em; 686 | margin: 0; 687 | font-size: 85%; 688 | white-space: break-spaces; 689 | background-color: rgba(175,184,193,0.2); 690 | border-radius: 6px; 691 | } 692 | 693 | .markdown-body code br, 694 | .markdown-body tt br { 695 | display: none; 696 | } 697 | 698 | .markdown-body del code { 699 | text-decoration: inherit; 700 | } 701 | 702 | .markdown-body samp { 703 | font-size: 85%; 704 | } 705 | 706 | .markdown-body pre code { 707 | font-size: 100%; 708 | } 709 | 710 | .markdown-body pre>code { 711 | padding: 0; 712 | margin: 0; 713 | word-break: normal; 714 | white-space: pre; 715 | background: transparent; 716 | border: 0; 717 | } 718 | 719 | .markdown-body .highlight { 720 | margin-bottom: 16px; 721 | } 722 | 723 | .markdown-body .highlight pre { 724 | margin-bottom: 0; 725 | word-break: normal; 726 | } 727 | 728 | .markdown-body .highlight pre, 729 | .markdown-body pre { 730 | padding: 16px; 731 | overflow: auto; 732 | font-size: 85%; 733 | line-height: 1.45; 734 | background-color: #f6f8fa; 735 | border-radius: 6px; 736 | } 737 | 738 | .markdown-body pre code, 739 | .markdown-body pre tt { 740 | display: inline; 741 | max-width: auto; 742 | padding: 0; 743 | margin: 0; 744 | overflow: visible; 745 | line-height: inherit; 746 | word-wrap: normal; 747 | background-color: transparent; 748 | border: 0; 749 | } 750 | 751 | .markdown-body .csv-data td, 752 | .markdown-body .csv-data th { 753 | padding: 5px; 754 | overflow: hidden; 755 | font-size: 12px; 756 | line-height: 1; 757 | text-align: left; 758 | white-space: nowrap; 759 | } 760 | 761 | .markdown-body .csv-data .blob-num { 762 | padding: 10px 8px 9px; 763 | text-align: right; 764 | background: #ffffff; 765 | border: 0; 766 | } 767 | 768 | .markdown-body .csv-data tr { 769 | border-top: 0; 770 | } 771 | 772 | .markdown-body .csv-data th { 773 | font-weight: 600; 774 | background: #f6f8fa; 775 | border-top: 0; 776 | } 777 | 778 | .markdown-body [data-footnote-ref]::before { 779 | content: "["; 780 | } 781 | 782 | .markdown-body [data-footnote-ref]::after { 783 | content: "]"; 784 | } 785 | 786 | .markdown-body .footnotes { 787 | font-size: 12px; 788 | color: #57606a; 789 | border-top: 1px solid #d0d7de; 790 | } 791 | 792 | .markdown-body .footnotes ol { 793 | padding-left: 16px; 794 | } 795 | 796 | .markdown-body .footnotes ol ul { 797 | display: inline-block; 798 | padding-left: 16px; 799 | margin-top: 16px; 800 | } 801 | 802 | .markdown-body .footnotes li { 803 | position: relative; 804 | } 805 | 806 | .markdown-body .footnotes li:target::before { 807 | position: absolute; 808 | top: -8px; 809 | right: -8px; 810 | bottom: -8px; 811 | left: -24px; 812 | pointer-events: none; 813 | content: ""; 814 | border: 2px solid #0969da; 815 | border-radius: 6px; 816 | } 817 | 818 | .markdown-body .footnotes li:target { 819 | color: #24292f; 820 | } 821 | 822 | .markdown-body .footnotes .data-footnote-backref g-emoji { 823 | font-family: monospace; 824 | } 825 | 826 | .markdown-body .pl-c { 827 | color: #6e7781; 828 | } 829 | 830 | .markdown-body .pl-c1, 831 | .markdown-body .pl-s .pl-v { 832 | color: #0550ae; 833 | } 834 | 835 | .markdown-body .pl-e, 836 | .markdown-body .pl-en { 837 | color: #8250df; 838 | } 839 | 840 | .markdown-body .pl-smi, 841 | .markdown-body .pl-s .pl-s1 { 842 | color: #24292f; 843 | } 844 | 845 | .markdown-body .pl-ent { 846 | color: #116329; 847 | } 848 | 849 | .markdown-body .pl-k { 850 | color: #cf222e; 851 | } 852 | 853 | .markdown-body .pl-s, 854 | .markdown-body .pl-pds, 855 | .markdown-body .pl-s .pl-pse .pl-s1, 856 | .markdown-body .pl-sr, 857 | .markdown-body .pl-sr .pl-cce, 858 | .markdown-body .pl-sr .pl-sre, 859 | .markdown-body .pl-sr .pl-sra { 860 | color: #0a3069; 861 | } 862 | 863 | .markdown-body .pl-v, 864 | .markdown-body .pl-smw { 865 | color: #953800; 866 | } 867 | 868 | .markdown-body .pl-bu { 869 | color: #82071e; 870 | } 871 | 872 | .markdown-body .pl-ii { 873 | color: #f6f8fa; 874 | background-color: #82071e; 875 | } 876 | 877 | .markdown-body .pl-c2 { 878 | color: #f6f8fa; 879 | background-color: #cf222e; 880 | } 881 | 882 | .markdown-body .pl-sr .pl-cce { 883 | font-weight: bold; 884 | color: #116329; 885 | } 886 | 887 | .markdown-body .pl-ml { 888 | color: #3b2300; 889 | } 890 | 891 | .markdown-body .pl-mh, 892 | .markdown-body .pl-mh .pl-en, 893 | .markdown-body .pl-ms { 894 | font-weight: bold; 895 | color: #0550ae; 896 | } 897 | 898 | .markdown-body .pl-mi { 899 | font-style: italic; 900 | color: #24292f; 901 | } 902 | 903 | .markdown-body .pl-mb { 904 | font-weight: bold; 905 | color: #24292f; 906 | } 907 | 908 | .markdown-body .pl-md { 909 | color: #82071e; 910 | background-color: #ffebe9; 911 | } 912 | 913 | .markdown-body .pl-mi1 { 914 | color: #116329; 915 | background-color: #dafbe1; 916 | } 917 | 918 | .markdown-body .pl-mc { 919 | color: #953800; 920 | background-color: #ffd8b5; 921 | } 922 | 923 | .markdown-body .pl-mi2 { 924 | color: #eaeef2; 925 | background-color: #0550ae; 926 | } 927 | 928 | .markdown-body .pl-mdr { 929 | font-weight: bold; 930 | color: #8250df; 931 | } 932 | 933 | .markdown-body .pl-ba { 934 | color: #57606a; 935 | } 936 | 937 | .markdown-body .pl-sg { 938 | color: #8c959f; 939 | } 940 | 941 | .markdown-body .pl-corl { 942 | text-decoration: underline; 943 | color: #0a3069; 944 | } 945 | 946 | .markdown-body g-emoji { 947 | display: inline-block; 948 | min-width: 1ch; 949 | font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; 950 | font-size: 1em; 951 | font-style: normal !important; 952 | font-weight: 400; 953 | line-height: 1; 954 | vertical-align: -0.075em; 955 | } 956 | 957 | .markdown-body g-emoji img { 958 | width: 1em; 959 | height: 1em; 960 | } 961 | 962 | .markdown-body .task-list-item { 963 | list-style-type: none; 964 | } 965 | 966 | .markdown-body .task-list-item label { 967 | font-weight: 400; 968 | } 969 | 970 | .markdown-body .task-list-item.enabled label { 971 | cursor: pointer; 972 | } 973 | 974 | .markdown-body .task-list-item+.task-list-item { 975 | margin-top: 4px; 976 | } 977 | 978 | .markdown-body .task-list-item .handle { 979 | display: none; 980 | } 981 | 982 | .markdown-body .task-list-item-checkbox { 983 | margin: 0 .2em .25em -1.4em; 984 | vertical-align: middle; 985 | } 986 | 987 | .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { 988 | margin: 0 -1.6em .25em .2em; 989 | } 990 | 991 | .markdown-body .contains-task-list { 992 | position: relative; 993 | } 994 | 995 | .markdown-body .contains-task-list:hover .task-list-item-convert-container, 996 | .markdown-body .contains-task-list:focus-within .task-list-item-convert-container { 997 | display: block; 998 | width: auto; 999 | height: 24px; 1000 | overflow: visible; 1001 | clip: auto; 1002 | } 1003 | 1004 | .markdown-body ::-webkit-calendar-picker-indicator { 1005 | filter: invert(50%); 1006 | } 1007 | -------------------------------------------------------------------------------- /htdoc/assets/polyfills-legacy-DglrV6q4.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var r,t,e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n={};function i(){if(t)return r;t=1;var n=function(r){return r&&r.Math===Math&&r};return r=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof e&&e)||n("object"==typeof r&&r)||function(){return this}()||Function("return this")()}var o,u,a,c,f,s,l,h,v={};function p(){return u?o:(u=1,o=function(r){try{return!!r()}catch(t){return!0}})}function d(){if(c)return a;c=1;var r=p();return a=!r(function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]})}function y(){if(s)return f;s=1;var r=p();return f=!r(function(){var r=function(){}.bind();return"function"!=typeof r||r.hasOwnProperty("prototype")})}function g(){if(h)return l;h=1;var r=y(),t=Function.prototype.call;return l=r?t.bind(t):function(){return t.apply(t,arguments)},l}var w,m,b,E,S,O,A,R,I,T,x,k,D,j,P,C,_,M,U,B,L,N,F,z,W,H,V,Y,G,q,$,J,Q,X,K,Z,rr,tr,er,nr,ir,or={};function ur(){return b?m:(b=1,m=function(r,t){return{enumerable:!(1&r),configurable:!(2&r),writable:!(4&r),value:t}})}function ar(){if(S)return E;S=1;var r=y(),t=Function.prototype,e=t.call,n=r&&t.bind.bind(e,e);return E=r?n:function(r){return function(){return e.apply(r,arguments)}},E}function cr(){if(A)return O;A=1;var r=ar(),t=r({}.toString),e=r("".slice);return O=function(r){return e(t(r),8,-1)}}function fr(){if(I)return R;I=1;var r=ar(),t=p(),e=cr(),n=Object,i=r("".split);return R=t(function(){return!n("z").propertyIsEnumerable(0)})?function(r){return"String"===e(r)?i(r,""):n(r)}:n}function sr(){return x?T:(x=1,T=function(r){return null==r})}function lr(){if(D)return k;D=1;var r=sr(),t=TypeError;return k=function(e){if(r(e))throw new t("Can't call method on "+e);return e}}function hr(){if(P)return j;P=1;var r=fr(),t=lr();return j=function(e){return r(t(e))}}function vr(){if(_)return C;_=1;var r="object"==typeof document&&document.all;return C=void 0===r&&void 0!==r?function(t){return"function"==typeof t||t===r}:function(r){return"function"==typeof r}}function pr(){if(U)return M;U=1;var r=vr();return M=function(t){return"object"==typeof t?null!==t:r(t)}}function dr(){if(L)return B;L=1;var r=i(),t=vr();return B=function(e,n){return arguments.length<2?(i=r[e],t(i)?i:void 0):r[e]&&r[e][n];var i},B}function yr(){if(F)return N;F=1;var r=ar();return N=r({}.isPrototypeOf)}function gr(){if(W)return z;W=1;var r=i().navigator,t=r&&r.userAgent;return z=t?String(t):""}function wr(){if(V)return H;V=1;var r,t,e=i(),n=gr(),o=e.process,u=e.Deno,a=o&&o.versions||u&&u.version,c=a&&a.v8;return c&&(t=(r=c.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!t&&n&&(!(r=n.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=n.match(/Chrome\/(\d+)/))&&(t=+r[1]),H=t}function mr(){if(G)return Y;G=1;var r=wr(),t=p(),e=i().String;return Y=!!Object.getOwnPropertySymbols&&!t(function(){var t=Symbol("symbol detection");return!e(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&r&&r<41})}function br(){if($)return q;$=1;var r=mr();return q=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator}function Er(){if(Q)return J;Q=1;var r=dr(),t=vr(),e=yr(),n=br(),i=Object;return J=n?function(r){return"symbol"==typeof r}:function(n){var o=r("Symbol");return t(o)&&e(o.prototype,i(n))}}function Sr(){if(K)return X;K=1;var r=String;return X=function(t){try{return r(t)}catch(e){return"Object"}}}function Or(){if(rr)return Z;rr=1;var r=vr(),t=Sr(),e=TypeError;return Z=function(n){if(r(n))return n;throw new e(t(n)+" is not a function")}}function Ar(){if(er)return tr;er=1;var r=Or(),t=sr();return tr=function(e,n){var i=e[n];return t(i)?void 0:r(i)}}function Rr(){if(ir)return nr;ir=1;var r=g(),t=vr(),e=pr(),n=TypeError;return nr=function(i,o){var u,a;if("string"===o&&t(u=i.toString)&&!e(a=r(u,i)))return a;if(t(u=i.valueOf)&&!e(a=r(u,i)))return a;if("string"!==o&&t(u=i.toString)&&!e(a=r(u,i)))return a;throw new n("Can't convert object to primitive value")}}var Ir,Tr,xr,kr,Dr,jr,Pr,Cr,_r,Mr,Ur,Br,Lr,Nr,Fr,zr,Wr,Hr,Vr,Yr,Gr,qr,$r,Jr,Qr={exports:{}};function Xr(){return Tr?Ir:(Tr=1,Ir=!1)}function Kr(){if(kr)return xr;kr=1;var r=i(),t=Object.defineProperty;return xr=function(e,n){try{t(r,e,{value:n,configurable:!0,writable:!0})}catch(i){r[e]=n}return n}}function Zr(){if(Dr)return Qr.exports;Dr=1;var r=Xr(),t=i(),e=Kr(),n="__core-js_shared__",o=Qr.exports=t[n]||e(n,{});return(o.versions||(o.versions=[])).push({version:"3.45.1",mode:r?"pure":"global",copyright:"© 2014-2025 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.45.1/LICENSE",source:"https://github.com/zloirock/core-js"}),Qr.exports}function rt(){if(Pr)return jr;Pr=1;var r=Zr();return jr=function(t,e){return r[t]||(r[t]=e||{})}}function tt(){if(_r)return Cr;_r=1;var r=lr(),t=Object;return Cr=function(e){return t(r(e))}}function et(){if(Ur)return Mr;Ur=1;var r=ar(),t=tt(),e=r({}.hasOwnProperty);return Mr=Object.hasOwn||function(r,n){return e(t(r),n)}}function nt(){if(Lr)return Br;Lr=1;var r=ar(),t=0,e=Math.random(),n=r(1.1.toString);return Br=function(r){return"Symbol("+(void 0===r?"":r)+")_"+n(++t+e,36)}}function it(){if(Fr)return Nr;Fr=1;var r=i(),t=rt(),e=et(),n=nt(),o=mr(),u=br(),a=r.Symbol,c=t("wks"),f=u?a.for||a:a&&a.withoutSetter||n;return Nr=function(r){return e(c,r)||(c[r]=o&&e(a,r)?a[r]:f("Symbol."+r)),c[r]}}function ot(){if(Wr)return zr;Wr=1;var r=g(),t=pr(),e=Er(),n=Ar(),i=Rr(),o=it(),u=TypeError,a=o("toPrimitive");return zr=function(o,c){if(!t(o)||e(o))return o;var f,s=n(o,a);if(s){if(void 0===c&&(c="default"),f=r(s,o,c),!t(f)||e(f))return f;throw new u("Can't convert object to primitive value")}return void 0===c&&(c="number"),i(o,c)}}function ut(){if(Vr)return Hr;Vr=1;var r=ot(),t=Er();return Hr=function(e){var n=r(e,"string");return t(n)?n:n+""}}function at(){if(Gr)return Yr;Gr=1;var r=i(),t=pr(),e=r.document,n=t(e)&&t(e.createElement);return Yr=function(r){return n?e.createElement(r):{}}}function ct(){if($r)return qr;$r=1;var r=d(),t=p(),e=at();return qr=!r&&!t(function(){return 7!==Object.defineProperty(e("div"),"a",{get:function(){return 7}}).a})}function ft(){if(Jr)return v;Jr=1;var r=d(),t=g(),e=function(){if(w)return or;w=1;var r={}.propertyIsEnumerable,t=Object.getOwnPropertyDescriptor,e=t&&!r.call({1:2},1);return or.f=e?function(r){var e=t(this,r);return!!e&&e.enumerable}:r,or}(),n=ur(),i=hr(),o=ut(),u=et(),a=ct(),c=Object.getOwnPropertyDescriptor;return v.f=r?c:function(r,f){if(r=i(r),f=o(f),a)try{return c(r,f)}catch(s){}if(u(r,f))return n(!t(e.f,r,f),r[f])},v}var st,lt,ht,vt,pt,dt,yt,gt={};function wt(){if(lt)return st;lt=1;var r=d(),t=p();return st=r&&t(function(){return 42!==Object.defineProperty(function(){},"prototype",{value:42,writable:!1}).prototype})}function mt(){if(vt)return ht;vt=1;var r=pr(),t=String,e=TypeError;return ht=function(n){if(r(n))return n;throw new e(t(n)+" is not an object")}}function bt(){if(pt)return gt;pt=1;var r=d(),t=ct(),e=wt(),n=mt(),i=ut(),o=TypeError,u=Object.defineProperty,a=Object.getOwnPropertyDescriptor,c="enumerable",f="configurable",s="writable";return gt.f=r?e?function(r,t,e){if(n(r),t=i(t),n(e),"function"==typeof r&&"prototype"===t&&"value"in e&&s in e&&!e[s]){var o=a(r,t);o&&o[s]&&(r[t]=e.value,e={configurable:f in e?e[f]:o[f],enumerable:c in e?e[c]:o[c],writable:!1})}return u(r,t,e)}:u:function(r,e,a){if(n(r),e=i(e),n(a),t)try{return u(r,e,a)}catch(c){}if("get"in a||"set"in a)throw new o("Accessors not supported");return"value"in a&&(r[e]=a.value),r},gt}function Et(){if(yt)return dt;yt=1;var r=d(),t=bt(),e=ur();return dt=r?function(r,n,i){return t.f(r,n,e(1,i))}:function(r,t,e){return r[t]=e,r}}var St,Ot,At,Rt,It,Tt,xt,kt,Dt,jt,Pt,Ct,_t,Mt,Ut,Bt={exports:{}};function Lt(){if(Rt)return At;Rt=1;var r=ar(),t=vr(),e=Zr(),n=r(Function.toString);return t(e.inspectSource)||(e.inspectSource=function(r){return n(r)}),At=e.inspectSource}function Nt(){if(kt)return xt;kt=1;var r=rt(),t=nt(),e=r("keys");return xt=function(r){return e[r]||(e[r]=t(r))}}function Ft(){return jt?Dt:(jt=1,Dt={})}function zt(){if(Ct)return Pt;Ct=1;var r,t,e,n=function(){if(Tt)return It;Tt=1;var r=i(),t=vr(),e=r.WeakMap;return It=t(e)&&/native code/.test(String(e))}(),o=i(),u=pr(),a=Et(),c=et(),f=Zr(),s=Nt(),l=Ft(),h="Object already initialized",v=o.TypeError,p=o.WeakMap;if(n||f.state){var d=f.state||(f.state=new p);d.get=d.get,d.has=d.has,d.set=d.set,r=function(r,t){if(d.has(r))throw new v(h);return t.facade=r,d.set(r,t),t},t=function(r){return d.get(r)||{}},e=function(r){return d.has(r)}}else{var y=s("state");l[y]=!0,r=function(r,t){if(c(r,y))throw new v(h);return t.facade=r,a(r,y,t),t},t=function(r){return c(r,y)?r[y]:{}},e=function(r){return c(r,y)}}return Pt={set:r,get:t,has:e,enforce:function(n){return e(n)?t(n):r(n,{})},getterFor:function(r){return function(e){var n;if(!u(e)||(n=t(e)).type!==r)throw new v("Incompatible receiver, "+r+" required");return n}}}}function Wt(){if(_t)return Bt.exports;_t=1;var r=ar(),t=p(),e=vr(),n=et(),i=d(),o=function(){if(Ot)return St;Ot=1;var r=d(),t=et(),e=Function.prototype,n=r&&Object.getOwnPropertyDescriptor,i=t(e,"name"),o=i&&"something"===function(){}.name,u=i&&(!r||r&&n(e,"name").configurable);return St={EXISTS:i,PROPER:o,CONFIGURABLE:u}}().CONFIGURABLE,u=Lt(),a=zt(),c=a.enforce,f=a.get,s=String,l=Object.defineProperty,h=r("".slice),v=r("".replace),y=r([].join),g=i&&!t(function(){return 8!==l(function(){},"length",{value:8}).length}),w=String(String).split("String"),m=Bt.exports=function(r,t,e){"Symbol("===h(s(t),0,7)&&(t="["+v(s(t),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),e&&e.getter&&(t="get "+t),e&&e.setter&&(t="set "+t),(!n(r,"name")||o&&r.name!==t)&&(i?l(r,"name",{value:t,configurable:!0}):r.name=t),g&&e&&n(e,"arity")&&r.length!==e.arity&&l(r,"length",{value:e.arity});try{e&&n(e,"constructor")&&e.constructor?i&&l(r,"prototype",{writable:!1}):r.prototype&&(r.prototype=void 0)}catch(a){}var u=c(r);return n(u,"source")||(u.source=y(w,"string"==typeof t?t:"")),r};return Function.prototype.toString=m(function(){return e(this)&&f(this).source||u(this)},"toString"),Bt.exports}function Ht(){if(Ut)return Mt;Ut=1;var r=vr(),t=bt(),e=Wt(),n=Kr();return Mt=function(i,o,u,a){a||(a={});var c=a.enumerable,f=void 0!==a.name?a.name:o;if(r(u)&&e(u,f,a),a.global)c?i[o]=u:n(o,u);else{try{a.unsafe?i[o]&&(c=!0):delete i[o]}catch(s){}c?i[o]=u:t.f(i,o,{value:u,enumerable:!1,configurable:!a.nonConfigurable,writable:!a.nonWritable})}return i}}var Vt,Yt,Gt,qt,$t,Jt,Qt,Xt,Kt,Zt,re,te,ee,ne,ie,oe,ue,ae={};function ce(){if(qt)return Gt;qt=1;var r=function(){if(Yt)return Vt;Yt=1;var r=Math.ceil,t=Math.floor;return Vt=Math.trunc||function(e){var n=+e;return(n>0?t:r)(n)}}();return Gt=function(t){var e=+t;return e!=e||0===e?0:r(e)}}function fe(){if(Jt)return $t;Jt=1;var r=ce(),t=Math.max,e=Math.min;return $t=function(n,i){var o=r(n);return o<0?t(o+i,0):e(o,i)}}function se(){if(Xt)return Qt;Xt=1;var r=ce(),t=Math.min;return Qt=function(e){var n=r(e);return n>0?t(n,9007199254740991):0}}function le(){if(Zt)return Kt;Zt=1;var r=se();return Kt=function(t){return r(t.length)}}function he(){if(ne)return ee;ne=1;var r=ar(),t=et(),e=hr(),n=function(){if(te)return re;te=1;var r=hr(),t=fe(),e=le(),n=function(n){return function(i,o,u){var a=r(i),c=e(a);if(0===c)return!n&&-1;var f,s=t(u,c);if(n&&o!=o){for(;c>s;)if((f=a[s++])!=f)return!0}else for(;c>s;s++)if((n||s in a)&&a[s]===o)return n||s||0;return!n&&-1}};return re={includes:n(!0),indexOf:n(!1)}}().indexOf,i=Ft(),o=r([].push);return ee=function(r,u){var a,c=e(r),f=0,s=[];for(a in c)!t(i,a)&&t(c,a)&&o(s,a);for(;u.length>f;)t(c,a=u[f++])&&(~n(s,a)||o(s,a));return s}}function ve(){return oe?ie:(oe=1,ie=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"])}var pe,de,ye,ge,we,me,be,Ee,Se,Oe,Ae,Re,Ie,Te,xe,ke,De={};function je(){if(ye)return de;ye=1;var r=dr(),t=ar(),e=function(){if(ue)return ae;ue=1;var r=he(),t=ve().concat("length","prototype");return ae.f=Object.getOwnPropertyNames||function(e){return r(e,t)},ae}(),n=(pe||(pe=1,De.f=Object.getOwnPropertySymbols),De),i=mt(),o=t([].concat);return de=r("Reflect","ownKeys")||function(r){var t=e.f(i(r)),u=n.f;return u?o(t,u(r)):t}}function Pe(){if(we)return ge;we=1;var r=et(),t=je(),e=ft(),n=bt();return ge=function(i,o,u){for(var a=t(o),c=n.f,f=e.f,s=0;si,l=n(o)?o:f(o),h=s?a(arguments,i):[],v=s?function(){e(l,this,h)}:l;return t?r(v,u):r(v)}:r},Pc}Mc||(Mc=1,function(){if(Tc)return xc;Tc=1;var r=Ce(),t=i(),e=jc().clear;r({global:!0,bind:!0,enumerable:!0,forced:t.clearImmediate!==e},{clearImmediate:e})}(),function(){if(_c)return Uc;_c=1;var r=Ce(),t=i(),e=jc().set,n=Bc(),o=t.setImmediate?n(e,!1):e;r({global:!0,bind:!0,enumerable:!0,forced:t.setImmediate!==o},{setImmediate:o})}());var Lc,Nc,Fc,zc,Wc,Hc,Vc,Yc,Gc,qc={};function $c(){if(Nc)return Lc;Nc=1;var r=ar(),t=p(),e=vr(),n=Di(),i=dr(),o=Lt(),u=function(){},a=i("Reflect","construct"),c=/^\s*(?:class|function)\b/,f=r(c.exec),s=!c.test(u),l=function(r){if(!e(r))return!1;try{return a(u,[],r),!0}catch(t){return!1}},h=function(r){if(!e(r))return!1;switch(n(r)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return s||!!f(c,o(r))}catch(t){return!0}};return h.sham=!0,Lc=!a||t(function(){var r;return l(l.call)||!l(Object)||!l(function(){r=!0})||r})?h:l}function Jc(){if(Hc)return Wc;Hc=1;var r=ar(),t=Map.prototype;return Wc={Map:Map,set:r(t.set),get:r(t.get),has:r(t.has),remove:r(t.delete),proto:t}}function Qc(){if(Yc)return Vc;Yc=1;var r=p(),t=ur();return Vc=!r(function(){var r=new Error("a");return!("stack"in r)||(Object.defineProperty(r,"stack",t(1,7)),7!==r.stack)})}!function(){if(Gc)return qc;Gc=1;var r,t=Xr(),e=Ce(),n=i(),o=dr(),u=ar(),a=p(),c=nt(),f=vr(),s=$c(),l=sr(),h=pr(),v=Er(),d=_i(),y=mt(),w=Di(),m=et(),b=Nn(),E=Et(),S=le(),O=Dc(),A=function(){if(zc)return Fc;zc=1;var r=g(),t=et(),e=yr(),n=Do(),i=jo(),o=RegExp.prototype;return Fc=n.correct?function(r){return r.flags}:function(u){return n.correct||!e(o,u)||t(u,"flags")?u.flags:r(i,u)}}(),R=Jc(),I=Ko(),T=tu(),x=An(),k=Qc(),D=On(),j=n.Object,P=n.Array,C=n.Date,_=n.Error,M=n.TypeError,U=n.PerformanceMark,B=o("DOMException"),L=R.Map,N=R.has,F=R.get,z=R.set,W=I.Set,H=I.add,V=I.has,Y=o("Object","keys"),G=u([].push),q=u((!0).valueOf),$=u(1.1.valueOf),J=u("".valueOf),Q=u(C.prototype.getTime),X=c("structuredClone"),K="DataCloneError",Z="Transferring",rr=function(r){return!a(function(){var t=new n.Set([7]),e=r(t),i=r(j(7));return e===t||!e.has(7)||!h(i)||7!==+i})&&r},tr=function(r,t){return!a(function(){var e=new t,n=r({a:e,b:e});return!(n&&n.a===n.b&&n.a instanceof t&&n.a.stack===e.stack)})},er=n.structuredClone,nr=t||!tr(er,_)||!tr(er,B)||(r=er,!!a(function(){var t=r(new n.AggregateError([1],X,{cause:3}));return"AggregateError"!==t.name||1!==t.errors[0]||t.message!==X||3!==t.cause})),ir=!er&&rr(function(r){return new U(X,{detail:r}).detail}),or=rr(er)||ir,ur=function(r){throw new B("Uncloneable type: "+r,K)},cr=function(r,t){throw new B((t||"Cloning")+" of "+r+" cannot be properly polyfilled in this engine",K)},fr=function(r,t){return or||cr(t),or(r)},lr=function(r,t,e){if(N(t,r))return F(t,r);var i,o,u,a,c,s;if("SharedArrayBuffer"===(e||w(r)))i=or?or(r):r;else{var l=n.DataView;l||f(r.slice)||cr("ArrayBuffer");try{if(f(r.slice)&&!r.resizable)i=r.slice(0);else{o=r.byteLength,u="maxByteLength"in r?{maxByteLength:r.maxByteLength}:void 0,i=new ArrayBuffer(o,u),a=new l(r),c=new l(i);for(s=0;s