From 79ce0361ca3fabb26bfacd0817f5708cb73d9ec3 Mon Sep 17 00:00:00 2001 From: Dylan Huang Date: Mon, 11 Aug 2025 10:10:47 -0700 Subject: [PATCH 1/9] add --port arg to ep logs --- eval_protocol/cli.py | 1 + eval_protocol/cli_commands/logs.py | 7 +- eval_protocol/utils/logs_server.py | 48 ++++++++++-- eval_protocol/utils/vite_server.py | 45 +++++++++-- vite-app/dist/assets/index-CGYj40Gx.css | 1 + vite-app/dist/assets/index-DWfIf2rx.css | 1 - vite-app/dist/assets/index-D_nkLTVA.js | 88 ---------------------- vite-app/dist/assets/index-D_nkLTVA.js.map | 1 - vite-app/dist/assets/index-t_hsfGP1.js | 88 ++++++++++++++++++++++ vite-app/dist/assets/index-t_hsfGP1.js.map | 1 + vite-app/dist/index.html | 6 +- vite-app/docs/runtime-configuration.md | 73 ++++++++++++++++++ vite-app/src/App.tsx | 11 ++- vite-app/src/config.ts | 63 ++++++++++++++++ vite-app/src/types/global.d.ts | 13 ++++ 15 files changed, 334 insertions(+), 113 deletions(-) create mode 100644 vite-app/dist/assets/index-CGYj40Gx.css delete mode 100644 vite-app/dist/assets/index-DWfIf2rx.css delete mode 100644 vite-app/dist/assets/index-D_nkLTVA.js delete mode 100644 vite-app/dist/assets/index-D_nkLTVA.js.map create mode 100644 vite-app/dist/assets/index-t_hsfGP1.js create mode 100644 vite-app/dist/assets/index-t_hsfGP1.js.map create mode 100644 vite-app/docs/runtime-configuration.md create mode 100644 vite-app/src/config.ts create mode 100644 vite-app/src/types/global.d.ts diff --git a/eval_protocol/cli.py b/eval_protocol/cli.py index f94d14ba..24307eb0 100644 --- a/eval_protocol/cli.py +++ b/eval_protocol/cli.py @@ -289,6 +289,7 @@ def parse_args(args=None): # Logs command logs_parser = subparsers.add_parser("logs", help="Serve logs with file watching and real-time updates") + logs_parser.add_argument("--port", type=int, default=8000, help="Port to bind to (default: 8000)") # Run command (for Hydra-based evaluations) # This subparser intentionally defines no arguments itself. diff --git a/eval_protocol/cli_commands/logs.py b/eval_protocol/cli_commands/logs.py index 3f4eda7e..07c211be 100644 --- a/eval_protocol/cli_commands/logs.py +++ b/eval_protocol/cli_commands/logs.py @@ -11,15 +11,16 @@ def logs_command(args): """Serve logs with file watching and real-time updates""" + port = args.port print(f"šŸš€ Starting Eval Protocol Logs Server") - print(f"🌐 URL: http://localhost:8000") - print(f"šŸ”Œ WebSocket: ws://localhost:8000/ws") + print(f"🌐 URL: http://localhost:{port}") + print(f"šŸ”Œ WebSocket: ws://localhost:{port}/ws") print(f"šŸ‘€ Watching paths: {['current directory']}") print("Press Ctrl+C to stop the server") print("-" * 50) try: - serve_logs() + serve_logs(port=args.port) return 0 except KeyboardInterrupt: print("\nšŸ›‘ Server stopped by user") diff --git a/eval_protocol/utils/logs_server.py b/eval_protocol/utils/logs_server.py index 97199ccc..815d33ef 100644 --- a/eval_protocol/utils/logs_server.py +++ b/eval_protocol/utils/logs_server.py @@ -319,20 +319,52 @@ def run(self): asyncio.run(self.run_async()) -server = LogsServer() -app = server.app +def create_app(host: str = "localhost", port: int = 8000, build_dir: Optional[str] = None) -> FastAPI: + """ + Factory function to create a FastAPI app instance. + + This allows uvicorn to call it with parameters and avoids top-level variable instantiation. + + Args: + host: Host to bind to + port: Port to bind to + build_dir: Optional custom build directory path + + Returns: + FastAPI app instance + """ + if build_dir is None: + build_dir = os.path.abspath( + os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "vite-app", "dist") + ) + server = LogsServer(host=host, port=port, build_dir=build_dir) + return server.app -def serve_logs(): + +# For backward compatibility and direct usage +def serve_logs(port: Optional[int] = None): """ Convenience function to create and run a LogsServer. """ - global server, app - if server is None: - server = LogsServer() - app = server.app + server = LogsServer(port=port) server.run() if __name__ == "__main__": - serve_logs() + import argparse + + parser = argparse.ArgumentParser(description="Start the evaluation logs server") + parser.add_argument("--host", default="localhost", help="Host to bind to (default: localhost)") + parser.add_argument("--port", type=int, default=8000, help="Port to bind to (default: 8000)") + parser.add_argument("--build-dir", help="Path to Vite build directory") + + args = parser.parse_args() + + # Create server with command line arguments + if args.build_dir: + server = LogsServer(host=args.host, port=args.port, build_dir=args.build_dir) + else: + server = LogsServer(host=args.host, port=args.port) + + server.run() diff --git a/eval_protocol/utils/vite_server.py b/eval_protocol/utils/vite_server.py index c3df4640..4c3143e2 100644 --- a/eval_protocol/utils/vite_server.py +++ b/eval_protocol/utils/vite_server.py @@ -5,7 +5,7 @@ import uvicorn from fastapi import FastAPI, HTTPException -from fastapi.responses import FileResponse +from fastapi.responses import FileResponse, HTMLResponse from fastapi.staticfiles import StaticFiles logger = logging.getLogger(__name__) @@ -57,6 +57,40 @@ def __init__( # Setup routes self._setup_routes() + def _inject_config_into_html(self, html_content: str) -> str: + """Inject server configuration into the HTML content.""" + config_script = f""" + +""" + + # Insert the config script before the closing tag + if "" in html_content: + return html_content.replace("", f"{config_script}") + else: + # If no tag, insert at the beginning + return f"{config_script}{html_content}" + + def _serve_index_with_config(self) -> HTMLResponse: + """Serve the index.html file with injected configuration.""" + index_path = self.build_dir / self.index_file + if index_path.exists(): + with open(index_path, "r", encoding="utf-8") as f: + html_content = f.read() + + # Inject server configuration + enhanced_html = self._inject_config_into_html(html_content) + return HTMLResponse(content=enhanced_html) + + raise HTTPException(status_code=404, detail="Index file not found") + def _setup_routes(self): """Set up the API routes for serving the SPA.""" @@ -81,18 +115,15 @@ async def serve_spa(path: str): # For SPA routing, serve index.html for non-existent routes # but exclude API routes and asset requests if not path.startswith(("api/", "assets/")): - index_path = self.build_dir / self.index_file - if index_path.exists(): - return FileResponse(index_path) + return self._serve_index_with_config() # If we get here, the file doesn't exist and it's not a SPA route raise HTTPException(status_code=404, detail="File not found") @self.app.get("/") async def root(): - """Serve the main index.html file.""" - index_path = self.build_dir / self.index_file - return FileResponse(index_path) + """Serve the main index.html file with injected configuration.""" + return self._serve_index_with_config() @self.app.get("/health") async def health(): diff --git a/vite-app/dist/assets/index-CGYj40Gx.css b/vite-app/dist/assets/index-CGYj40Gx.css new file mode 100644 index 00000000..292784b4 --- /dev/null +++ b/vite-app/dist/assets/index-CGYj40Gx.css @@ -0,0 +1 @@ +/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-red-700:oklch(50.5% .213 27.518);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-500:oklch(72.3% .219 149.579);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-900:oklch(37.9% .146 265.522);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--font-weight-medium:500;--font-weight-semibold:600;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:calc(var(--spacing)*0)}.right-0{right:calc(var(--spacing)*0)}.left-0{left:calc(var(--spacing)*0)}.\!container{width:100%!important}@media (min-width:40rem){.\!container{max-width:40rem!important}}@media (min-width:48rem){.\!container{max-width:48rem!important}}@media (min-width:64rem){.\!container{max-width:64rem!important}}@media (min-width:80rem){.\!container{max-width:80rem!important}}@media (min-width:96rem){.\!container{max-width:96rem!important}}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mb-0\.5{margin-bottom:calc(var(--spacing)*.5)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.flex{display:flex}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.h-1{height:calc(var(--spacing)*1)}.h-1\.5{height:calc(var(--spacing)*1.5)}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.min-h-screen{min-height:100vh}.w-1{width:calc(var(--spacing)*1)}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-8{width:calc(var(--spacing)*8)}.w-12{width:calc(var(--spacing)*12)}.w-\[500px\]{width:500px}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-max{min-width:max-content}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.grow{flex-grow:1}.rotate-90{rotate:90deg}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-nw-resize{cursor:nw-resize}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.resize{resize:both}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-blue-200{border-color:var(--color-blue-200)}.border-current{border-color:currentColor}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-900{border-color:var(--color-gray-900)}.border-green-200{border-color:var(--color-green-200)}.border-transparent{border-color:#0000}.border-yellow-200{border-color:var(--color-yellow-200)}.border-t-transparent{border-top-color:#0000}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-red-500{background-color:var(--color-red-500)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-500{background-color:var(--color-yellow-500)}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-8{padding:calc(var(--spacing)*8)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-2{padding-top:calc(var(--spacing)*2)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-700{color:var(--color-blue-700)}.text-blue-900{color:var(--color-blue-900)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-red-700{color:var(--color-red-700)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.text-yellow-900{color:var(--color-yellow-900)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:no-underline:hover{text-decoration-line:none}}.focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}@media (min-width:64rem){.lg\:max-w-md{max-width:var(--container-md)}}@media (min-width:80rem){.xl\:max-w-lg{max-width:var(--container-lg)}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} diff --git a/vite-app/dist/assets/index-DWfIf2rx.css b/vite-app/dist/assets/index-DWfIf2rx.css deleted file mode 100644 index 10fe5c29..00000000 --- a/vite-app/dist/assets/index-DWfIf2rx.css +++ /dev/null @@ -1 +0,0 @@ -/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-red-700:oklch(50.5% .213 27.518);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-500:oklch(72.3% .219 149.579);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-900:oklch(37.9% .146 265.522);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--font-weight-medium:500;--font-weight-semibold:600;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:calc(var(--spacing)*0)}.right-0{right:calc(var(--spacing)*0)}.left-0{left:calc(var(--spacing)*0)}.\!container{width:100%!important}@media (min-width:40rem){.\!container{max-width:40rem!important}}@media (min-width:48rem){.\!container{max-width:48rem!important}}@media (min-width:64rem){.\!container{max-width:64rem!important}}@media (min-width:80rem){.\!container{max-width:80rem!important}}@media (min-width:96rem){.\!container{max-width:96rem!important}}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mb-0\.5{margin-bottom:calc(var(--spacing)*.5)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.flex{display:flex}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.h-1{height:calc(var(--spacing)*1)}.h-1\.5{height:calc(var(--spacing)*1.5)}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.min-h-screen{min-height:100vh}.w-1{width:calc(var(--spacing)*1)}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-8{width:calc(var(--spacing)*8)}.w-12{width:calc(var(--spacing)*12)}.w-\[500px\]{width:500px}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-max{min-width:max-content}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.grow{flex-grow:1}.rotate-90{rotate:90deg}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-nw-resize{cursor:nw-resize}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.resize{resize:both}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-blue-200{border-color:var(--color-blue-200)}.border-current{border-color:currentColor}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-900{border-color:var(--color-gray-900)}.border-green-200{border-color:var(--color-green-200)}.border-transparent{border-color:#0000}.border-yellow-200{border-color:var(--color-yellow-200)}.border-t-transparent{border-top-color:#0000}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-red-500{background-color:var(--color-red-500)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-500{background-color:var(--color-yellow-500)}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-8{padding:calc(var(--spacing)*8)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-2{padding-top:calc(var(--spacing)*2)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-700{color:var(--color-blue-700)}.text-blue-900{color:var(--color-blue-900)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-red-700{color:var(--color-red-700)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.text-yellow-900{color:var(--color-yellow-900)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:no-underline:hover{text-decoration-line:none}}.focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}@media (min-width:64rem){.lg\:max-w-md{max-width:var(--container-md)}}@media (min-width:80rem){.xl\:max-w-lg{max-width:var(--container-lg)}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} diff --git a/vite-app/dist/assets/index-D_nkLTVA.js b/vite-app/dist/assets/index-D_nkLTVA.js deleted file mode 100644 index e813086a..00000000 --- a/vite-app/dist/assets/index-D_nkLTVA.js +++ /dev/null @@ -1,88 +0,0 @@ -(function(){const l=document.createElement("link").relList;if(l&&l.supports&&l.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const f of o)if(f.type==="childList")for(const d of f.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&r(d)}).observe(document,{childList:!0,subtree:!0});function u(o){const f={};return o.integrity&&(f.integrity=o.integrity),o.referrerPolicy&&(f.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?f.credentials="include":o.crossOrigin==="anonymous"?f.credentials="omit":f.credentials="same-origin",f}function r(o){if(o.ep)return;o.ep=!0;const f=u(o);fetch(o.href,f)}})();function ag(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var Xc={exports:{}},Wi={};/** - * @license React - * react-jsx-runtime.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var km;function cb(){if(km)return Wi;km=1;var n=Symbol.for("react.transitional.element"),l=Symbol.for("react.fragment");function u(r,o,f){var d=null;if(f!==void 0&&(d=""+f),o.key!==void 0&&(d=""+o.key),"key"in o){f={};for(var v in o)v!=="key"&&(f[v]=o[v])}else f=o;return o=f.ref,{$$typeof:n,type:r,key:d,ref:o!==void 0?o:null,props:f}}return Wi.Fragment=l,Wi.jsx=u,Wi.jsxs=u,Wi}var qm;function fb(){return qm||(qm=1,Xc.exports=cb()),Xc.exports}var x=fb(),Kc={exports:{}},oe={};/** - * @license React - * react.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Vm;function db(){if(Vm)return oe;Vm=1;var n=Symbol.for("react.transitional.element"),l=Symbol.for("react.portal"),u=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),f=Symbol.for("react.consumer"),d=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),m=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),_=Symbol.for("react.lazy"),O=Symbol.iterator;function w(b){return b===null||typeof b!="object"?null:(b=O&&b[O]||b["@@iterator"],typeof b=="function"?b:null)}var Z={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},k=Object.assign,B={};function U(b,H,K){this.props=b,this.context=H,this.refs=B,this.updater=K||Z}U.prototype.isReactComponent={},U.prototype.setState=function(b,H){if(typeof b!="object"&&typeof b!="function"&&b!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,b,H,"setState")},U.prototype.forceUpdate=function(b){this.updater.enqueueForceUpdate(this,b,"forceUpdate")};function $(){}$.prototype=U.prototype;function X(b,H,K){this.props=b,this.context=H,this.refs=B,this.updater=K||Z}var q=X.prototype=new $;q.constructor=X,k(q,U.prototype),q.isPureReactComponent=!0;var W=Array.isArray,Q={H:null,A:null,T:null,S:null,V:null},he=Object.prototype.hasOwnProperty;function xe(b,H,K,Y,ee,pe){return K=pe.ref,{$$typeof:n,type:b,key:H,ref:K!==void 0?K:null,props:pe}}function Xe(b,H){return xe(b.type,H,void 0,void 0,void 0,b.props)}function ae(b){return typeof b=="object"&&b!==null&&b.$$typeof===n}function qe(b){var H={"=":"=0",":":"=2"};return"$"+b.replace(/[=:]/g,function(K){return H[K]})}var Fe=/\/+/g;function Ke(b,H){return typeof b=="object"&&b!==null&&b.key!=null?qe(""+b.key):H.toString(36)}function Pt(){}function Kn(b){switch(b.status){case"fulfilled":return b.value;case"rejected":throw b.reason;default:switch(typeof b.status=="string"?b.then(Pt,Pt):(b.status="pending",b.then(function(H){b.status==="pending"&&(b.status="fulfilled",b.value=H)},function(H){b.status==="pending"&&(b.status="rejected",b.reason=H)})),b.status){case"fulfilled":return b.value;case"rejected":throw b.reason}}throw b}function lt(b,H,K,Y,ee){var pe=typeof b;(pe==="undefined"||pe==="boolean")&&(b=null);var re=!1;if(b===null)re=!0;else switch(pe){case"bigint":case"string":case"number":re=!0;break;case"object":switch(b.$$typeof){case n:case l:re=!0;break;case _:return re=b._init,lt(re(b._payload),H,K,Y,ee)}}if(re)return ee=ee(b),re=Y===""?"."+Ke(b,0):Y,W(ee)?(K="",re!=null&&(K=re.replace(Fe,"$&/")+"/"),lt(ee,H,K,"",function(Qn){return Qn})):ee!=null&&(ae(ee)&&(ee=Xe(ee,K+(ee.key==null||b&&b.key===ee.key?"":(""+ee.key).replace(Fe,"$&/")+"/")+re)),H.push(ee)),1;re=0;var St=Y===""?".":Y+":";if(W(b))for(var je=0;je>>1,b=N[Te];if(0>>1;Teo(Y,le))eeo(pe,Y)?(N[Te]=pe,N[ee]=le,Te=ee):(N[Te]=Y,N[K]=le,Te=K);else if(eeo(pe,le))N[Te]=pe,N[ee]=le,Te=ee;else break e}}return G}function o(N,G){var le=N.sortIndex-G.sortIndex;return le!==0?le:N.id-G.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var f=performance;n.unstable_now=function(){return f.now()}}else{var d=Date,v=d.now();n.unstable_now=function(){return d.now()-v}}var m=[],p=[],_=1,O=null,w=3,Z=!1,k=!1,B=!1,U=!1,$=typeof setTimeout=="function"?setTimeout:null,X=typeof clearTimeout=="function"?clearTimeout:null,q=typeof setImmediate<"u"?setImmediate:null;function W(N){for(var G=u(p);G!==null;){if(G.callback===null)r(p);else if(G.startTime<=N)r(p),G.sortIndex=G.expirationTime,l(m,G);else break;G=u(p)}}function Q(N){if(B=!1,W(N),!k)if(u(m)!==null)k=!0,he||(he=!0,Ke());else{var G=u(p);G!==null&<(Q,G.startTime-N)}}var he=!1,xe=-1,Xe=5,ae=-1;function qe(){return U?!0:!(n.unstable_now()-aeN&&qe());){var Te=O.callback;if(typeof Te=="function"){O.callback=null,w=O.priorityLevel;var b=Te(O.expirationTime<=N);if(N=n.unstable_now(),typeof b=="function"){O.callback=b,W(N),G=!0;break t}O===u(m)&&r(m),W(N)}else r(m);O=u(m)}if(O!==null)G=!0;else{var H=u(p);H!==null&<(Q,H.startTime-N),G=!1}}break e}finally{O=null,w=le,Z=!1}G=void 0}}finally{G?Ke():he=!1}}}var Ke;if(typeof q=="function")Ke=function(){q(Fe)};else if(typeof MessageChannel<"u"){var Pt=new MessageChannel,Kn=Pt.port2;Pt.port1.onmessage=Fe,Ke=function(){Kn.postMessage(null)}}else Ke=function(){$(Fe,0)};function lt(N,G){xe=$(function(){N(n.unstable_now())},G)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(N){N.callback=null},n.unstable_forceFrameRate=function(N){0>N||125Te?(N.sortIndex=le,l(p,N),u(m)===null&&N===u(p)&&(B?(X(xe),xe=-1):B=!0,lt(Q,le-Te))):(N.sortIndex=b,l(m,N),k||Z||(k=!0,he||(he=!0,Ke()))),N},n.unstable_shouldYield=qe,n.unstable_wrapCallback=function(N){var G=w;return function(){var le=w;w=G;try{return N.apply(this,arguments)}finally{w=le}}}}(Pc)),Pc}var Xm;function vb(){return Xm||(Xm=1,Jc.exports=hb()),Jc.exports}var Wc={exports:{}},dt={};/** - * @license React - * react-dom.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Km;function mb(){if(Km)return dt;Km=1;var n=fo();function l(m){var p="https://react.dev/errors/"+m;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(l){console.error(l)}}return n(),Wc.exports=mb(),Wc.exports}/** - * @license React - * react-dom-client.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Jm;function pb(){if(Jm)return Fi;Jm=1;var n=vb(),l=fo(),u=lg();function r(e){var t="https://react.dev/errors/"+e;if(1b||(e.current=Te[b],Te[b]=null,b--)}function Y(e,t){b++,Te[b]=e.current,e.current=t}var ee=H(null),pe=H(null),re=H(null),St=H(null);function je(e,t){switch(Y(re,t),Y(pe,e),Y(ee,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?mm(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=mm(t),e=pm(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}K(ee),Y(ee,e)}function Qn(){K(ee),K(pe),K(re)}function No(e){e.memoizedState!==null&&Y(St,e);var t=ee.current,a=pm(t,e.type);t!==a&&(Y(pe,e),Y(ee,a))}function bu(e){pe.current===e&&(K(ee),K(pe)),St.current===e&&(K(St),Xi._currentValue=le)}var jo=Object.prototype.hasOwnProperty,Mo=n.unstable_scheduleCallback,Co=n.unstable_cancelCallback,q_=n.unstable_shouldYield,V_=n.unstable_requestPaint,on=n.unstable_now,Y_=n.unstable_getCurrentPriorityLevel,Jf=n.unstable_ImmediatePriority,Pf=n.unstable_UserBlockingPriority,Su=n.unstable_NormalPriority,G_=n.unstable_LowPriority,Wf=n.unstable_IdlePriority,X_=n.log,K_=n.unstable_setDisableYieldValue,ei=null,xt=null;function Jn(e){if(typeof X_=="function"&&K_(e),xt&&typeof xt.setStrictMode=="function")try{xt.setStrictMode(ei,e)}catch{}}var Et=Math.clz32?Math.clz32:P_,Q_=Math.log,J_=Math.LN2;function P_(e){return e>>>=0,e===0?32:31-(Q_(e)/J_|0)|0}var xu=256,Eu=4194304;function Oa(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ou(e,t,a){var i=e.pendingLanes;if(i===0)return 0;var s=0,c=e.suspendedLanes,h=e.pingedLanes;e=e.warmLanes;var g=i&134217727;return g!==0?(i=g&~c,i!==0?s=Oa(i):(h&=g,h!==0?s=Oa(h):a||(a=g&~e,a!==0&&(s=Oa(a))))):(g=i&~c,g!==0?s=Oa(g):h!==0?s=Oa(h):a||(a=i&~e,a!==0&&(s=Oa(a)))),s===0?0:t!==0&&t!==s&&(t&c)===0&&(c=s&-s,a=t&-t,c>=a||c===32&&(a&4194048)!==0)?t:s}function ti(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function W_(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ff(){var e=xu;return xu<<=1,(xu&4194048)===0&&(xu=256),e}function If(){var e=Eu;return Eu<<=1,(Eu&62914560)===0&&(Eu=4194304),e}function Uo(e){for(var t=[],a=0;31>a;a++)t.push(e);return t}function ni(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function F_(e,t,a,i,s,c){var h=e.pendingLanes;e.pendingLanes=a,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=a,e.entangledLanes&=a,e.errorRecoveryDisabledLanes&=a,e.shellSuspendCounter=0;var g=e.entanglements,y=e.expirationTimes,T=e.hiddenUpdates;for(a=h&~a;0)":-1s||y[i]!==T[s]){var j=` -`+y[i].replace(" at new "," at ");return e.displayName&&j.includes("")&&(j=j.replace("",e.displayName)),j}while(1<=i&&0<=s);break}}}finally{ko=!1,Error.prepareStackTrace=a}return(a=e?e.displayName||e.name:"")?il(a):""}function ly(e){switch(e.tag){case 26:case 27:case 5:return il(e.type);case 16:return il("Lazy");case 13:return il("Suspense");case 19:return il("SuspenseList");case 0:case 15:return qo(e.type,!1);case 11:return qo(e.type.render,!1);case 1:return qo(e.type,!0);case 31:return il("Activity");default:return""}}function sd(e){try{var t="";do t+=ly(e),e=e.return;while(e);return t}catch(a){return` -Error generating stack: `+a.message+` -`+a.stack}}function Bt(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function cd(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function iy(e){var t=cd(e)?"checked":"value",a=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),i=""+e[t];if(!e.hasOwnProperty(t)&&typeof a<"u"&&typeof a.get=="function"&&typeof a.set=="function"){var s=a.get,c=a.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return s.call(this)},set:function(h){i=""+h,c.call(this,h)}}),Object.defineProperty(e,t,{enumerable:a.enumerable}),{getValue:function(){return i},setValue:function(h){i=""+h},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function wu(e){e._valueTracker||(e._valueTracker=iy(e))}function fd(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var a=t.getValue(),i="";return e&&(i=cd(e)?e.checked?"true":"false":e.value),e=i,e!==a?(t.setValue(e),!0):!1}function zu(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var uy=/[\n"\\]/g;function Lt(e){return e.replace(uy,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Vo(e,t,a,i,s,c,h,g){e.name="",h!=null&&typeof h!="function"&&typeof h!="symbol"&&typeof h!="boolean"?e.type=h:e.removeAttribute("type"),t!=null?h==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+Bt(t)):e.value!==""+Bt(t)&&(e.value=""+Bt(t)):h!=="submit"&&h!=="reset"||e.removeAttribute("value"),t!=null?Yo(e,h,Bt(t)):a!=null?Yo(e,h,Bt(a)):i!=null&&e.removeAttribute("value"),s==null&&c!=null&&(e.defaultChecked=!!c),s!=null&&(e.checked=s&&typeof s!="function"&&typeof s!="symbol"),g!=null&&typeof g!="function"&&typeof g!="symbol"&&typeof g!="boolean"?e.name=""+Bt(g):e.removeAttribute("name")}function dd(e,t,a,i,s,c,h,g){if(c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"&&(e.type=c),t!=null||a!=null){if(!(c!=="submit"&&c!=="reset"||t!=null))return;a=a!=null?""+Bt(a):"",t=t!=null?""+Bt(t):a,g||t===e.value||(e.value=t),e.defaultValue=t}i=i??s,i=typeof i!="function"&&typeof i!="symbol"&&!!i,e.checked=g?e.checked:!!i,e.defaultChecked=!!i,h!=null&&typeof h!="function"&&typeof h!="symbol"&&typeof h!="boolean"&&(e.name=h)}function Yo(e,t,a){t==="number"&&zu(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function ul(e,t,a,i){if(e=e.options,t){t={};for(var s=0;s"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Jo=!1;if(On)try{var ui={};Object.defineProperty(ui,"passive",{get:function(){Jo=!0}}),window.addEventListener("test",ui,ui),window.removeEventListener("test",ui,ui)}catch{Jo=!1}var Wn=null,Po=null,Du=null;function yd(){if(Du)return Du;var e,t=Po,a=t.length,i,s="value"in Wn?Wn.value:Wn.textContent,c=s.length;for(e=0;e=si),Ad=" ",Td=!1;function wd(e,t){switch(e){case"keyup":return Cy.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function zd(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var cl=!1;function Zy(e,t){switch(e){case"compositionend":return zd(t);case"keypress":return t.which!==32?null:(Td=!0,Ad);case"textInput":return e=t.data,e===Ad&&Td?null:e;default:return null}}function By(e,t){if(cl)return e==="compositionend"||!ts&&wd(e,t)?(e=yd(),Du=Po=Wn=null,cl=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:a,offset:t-e};e=i}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=Zd(a)}}function Ld(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ld(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function $d(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=zu(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=zu(e.document)}return t}function ls(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Gy=On&&"documentMode"in document&&11>=document.documentMode,fl=null,is=null,hi=null,us=!1;function Hd(e,t,a){var i=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;us||fl==null||fl!==zu(i)||(i=fl,"selectionStart"in i&&ls(i)?i={start:i.selectionStart,end:i.selectionEnd}:(i=(i.ownerDocument&&i.ownerDocument.defaultView||window).getSelection(),i={anchorNode:i.anchorNode,anchorOffset:i.anchorOffset,focusNode:i.focusNode,focusOffset:i.focusOffset}),hi&&di(hi,i)||(hi=i,i=br(is,"onSelect"),0>=h,s-=h,Tn=1<<32-Et(t)+s|a<c?c:8;var h=N.T,g={};N.T=g,Gs(e,!1,t,a);try{var y=s(),T=N.S;if(T!==null&&T(g,y),y!==null&&typeof y=="object"&&typeof y.then=="function"){var j=e0(y,i);zi(e,t,j,Rt(e))}else zi(e,t,i,Rt(e))}catch(L){zi(e,t,{then:function(){},status:"rejected",reason:L},Rt())}finally{G.p=c,N.T=h}}function i0(){}function Vs(e,t,a,i){if(e.tag!==5)throw Error(r(476));var s=kh(e).queue;Hh(e,s,t,le,a===null?i0:function(){return qh(e),a(i)})}function kh(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:le,baseState:le,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Dn,lastRenderedState:le},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Dn,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function qh(e){var t=kh(e).next.queue;zi(e,t,{},Rt())}function Ys(){return ft(Xi)}function Vh(){return Je().memoizedState}function Yh(){return Je().memoizedState}function u0(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=Rt();e=ea(a);var i=ta(t,e,a);i!==null&&(Dt(i,t,a),xi(i,t,a)),t={cache:bs()},e.payload=t;return}t=t.return}}function r0(e,t,a){var i=Rt();a={lane:i,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null},er(e)?Xh(t,a):(a=cs(e,t,a,i),a!==null&&(Dt(a,e,i),Kh(a,t,i)))}function Gh(e,t,a){var i=Rt();zi(e,t,a,i)}function zi(e,t,a,i){var s={lane:i,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null};if(er(e))Xh(t,s);else{var c=e.alternate;if(e.lanes===0&&(c===null||c.lanes===0)&&(c=t.lastRenderedReducer,c!==null))try{var h=t.lastRenderedState,g=c(h,a);if(s.hasEagerState=!0,s.eagerState=g,Ot(g,h))return Bu(e,t,s,0),Re===null&&Zu(),!1}catch{}finally{}if(a=cs(e,t,s,i),a!==null)return Dt(a,e,i),Kh(a,t,i),!0}return!1}function Gs(e,t,a,i){if(i={lane:2,revertLane:Ec(),action:i,hasEagerState:!1,eagerState:null,next:null},er(e)){if(t)throw Error(r(479))}else t=cs(e,a,i,2),t!==null&&Dt(t,e,2)}function er(e){var t=e.alternate;return e===se||t!==null&&t===se}function Xh(e,t){Sl=Qu=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function Kh(e,t,a){if((a&4194048)!==0){var i=t.lanes;i&=e.pendingLanes,a|=i,t.lanes=a,td(e,a)}}var tr={readContext:ft,use:Pu,useCallback:Ve,useContext:Ve,useEffect:Ve,useImperativeHandle:Ve,useLayoutEffect:Ve,useInsertionEffect:Ve,useMemo:Ve,useReducer:Ve,useRef:Ve,useState:Ve,useDebugValue:Ve,useDeferredValue:Ve,useTransition:Ve,useSyncExternalStore:Ve,useId:Ve,useHostTransitionStatus:Ve,useFormState:Ve,useActionState:Ve,useOptimistic:Ve,useMemoCache:Ve,useCacheRefresh:Ve},Qh={readContext:ft,use:Pu,useCallback:function(e,t){return pt().memoizedState=[e,t===void 0?null:t],e},useContext:ft,useEffect:Nh,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,Iu(4194308,4,Uh.bind(null,t,e),a)},useLayoutEffect:function(e,t){return Iu(4194308,4,e,t)},useInsertionEffect:function(e,t){Iu(4,2,e,t)},useMemo:function(e,t){var a=pt();t=t===void 0?null:t;var i=e();if(Ba){Jn(!0);try{e()}finally{Jn(!1)}}return a.memoizedState=[i,t],i},useReducer:function(e,t,a){var i=pt();if(a!==void 0){var s=a(t);if(Ba){Jn(!0);try{a(t)}finally{Jn(!1)}}}else s=t;return i.memoizedState=i.baseState=s,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:s},i.queue=e,e=e.dispatch=r0.bind(null,se,e),[i.memoizedState,e]},useRef:function(e){var t=pt();return e={current:e},t.memoizedState=e},useState:function(e){e=$s(e);var t=e.queue,a=Gh.bind(null,se,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:ks,useDeferredValue:function(e,t){var a=pt();return qs(a,e,t)},useTransition:function(){var e=$s(!1);return e=Hh.bind(null,se,e.queue,!0,!1),pt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var i=se,s=pt();if(ye){if(a===void 0)throw Error(r(407));a=a()}else{if(a=t(),Re===null)throw Error(r(349));(ve&124)!==0||mh(i,t,a)}s.memoizedState=a;var c={value:a,getSnapshot:t};return s.queue=c,Nh(gh.bind(null,i,c,e),[e]),i.flags|=2048,El(9,Fu(),ph.bind(null,i,c,a,t),null),a},useId:function(){var e=pt(),t=Re.identifierPrefix;if(ye){var a=wn,i=Tn;a=(i&~(1<<32-Et(i)-1)).toString(32)+a,t="Ā«"+t+"R"+a,a=Ju++,0ne?(nt=I,I=null):nt=I.sibling;var ge=z(E,I,A[ne],C);if(ge===null){I===null&&(I=nt);break}e&&I&&ge.alternate===null&&t(E,I),S=c(ge,S,ne),ce===null?J=ge:ce.sibling=ge,ce=ge,I=nt}if(ne===A.length)return a(E,I),ye&&Na(E,ne),J;if(I===null){for(;nene?(nt=I,I=null):nt=I.sibling;var _a=z(E,I,ge.value,C);if(_a===null){I===null&&(I=nt);break}e&&I&&_a.alternate===null&&t(E,I),S=c(_a,S,ne),ce===null?J=_a:ce.sibling=_a,ce=_a,I=nt}if(ge.done)return a(E,I),ye&&Na(E,ne),J;if(I===null){for(;!ge.done;ne++,ge=A.next())ge=L(E,ge.value,C),ge!==null&&(S=c(ge,S,ne),ce===null?J=ge:ce.sibling=ge,ce=ge);return ye&&Na(E,ne),J}for(I=i(I);!ge.done;ne++,ge=A.next())ge=D(I,E,ne,ge.value,C),ge!==null&&(e&&ge.alternate!==null&&I.delete(ge.key===null?ne:ge.key),S=c(ge,S,ne),ce===null?J=ge:ce.sibling=ge,ce=ge);return e&&I.forEach(function(sb){return t(E,sb)}),ye&&Na(E,ne),J}function Ae(E,S,A,C){if(typeof A=="object"&&A!==null&&A.type===k&&A.key===null&&(A=A.props.children),typeof A=="object"&&A!==null){switch(A.$$typeof){case w:e:{for(var J=A.key;S!==null;){if(S.key===J){if(J=A.type,J===k){if(S.tag===7){a(E,S.sibling),C=s(S,A.props.children),C.return=E,E=C;break e}}else if(S.elementType===J||typeof J=="object"&&J!==null&&J.$$typeof===Xe&&Ph(J)===S.type){a(E,S.sibling),C=s(S,A.props),Di(C,A),C.return=E,E=C;break e}a(E,S);break}else t(E,S);S=S.sibling}A.type===k?(C=Ra(A.props.children,E.mode,C,A.key),C.return=E,E=C):(C=$u(A.type,A.key,A.props,null,E.mode,C),Di(C,A),C.return=E,E=C)}return h(E);case Z:e:{for(J=A.key;S!==null;){if(S.key===J)if(S.tag===4&&S.stateNode.containerInfo===A.containerInfo&&S.stateNode.implementation===A.implementation){a(E,S.sibling),C=s(S,A.children||[]),C.return=E,E=C;break e}else{a(E,S);break}else t(E,S);S=S.sibling}C=hs(A,E.mode,C),C.return=E,E=C}return h(E);case Xe:return J=A._init,A=J(A._payload),Ae(E,S,A,C)}if(lt(A))return ie(E,S,A,C);if(Ke(A)){if(J=Ke(A),typeof J!="function")throw Error(r(150));return A=J.call(A),te(E,S,A,C)}if(typeof A.then=="function")return Ae(E,S,nr(A),C);if(A.$$typeof===q)return Ae(E,S,Vu(E,A),C);ar(E,A)}return typeof A=="string"&&A!==""||typeof A=="number"||typeof A=="bigint"?(A=""+A,S!==null&&S.tag===6?(a(E,S.sibling),C=s(S,A),C.return=E,E=C):(a(E,S),C=ds(A,E.mode,C),C.return=E,E=C),h(E)):a(E,S)}return function(E,S,A,C){try{Ri=0;var J=Ae(E,S,A,C);return Ol=null,J}catch(I){if(I===bi||I===Gu)throw I;var ce=At(29,I,null,E.mode);return ce.lanes=C,ce.return=E,ce}finally{}}}var Al=Wh(!0),Fh=Wh(!1),Vt=H(null),cn=null;function aa(e){var t=e.alternate;Y(We,We.current&1),Y(Vt,e),cn===null&&(t===null||bl.current!==null||t.memoizedState!==null)&&(cn=e)}function Ih(e){if(e.tag===22){if(Y(We,We.current),Y(Vt,e),cn===null){var t=e.alternate;t!==null&&t.memoizedState!==null&&(cn=e)}}else la()}function la(){Y(We,We.current),Y(Vt,Vt.current)}function Nn(e){K(Vt),cn===e&&(cn=null),K(We)}var We=H(0);function lr(e){for(var t=e;t!==null;){if(t.tag===13){var a=t.memoizedState;if(a!==null&&(a=a.dehydrated,a===null||a.data==="$?"||Uc(a)))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function Xs(e,t,a,i){t=e.memoizedState,a=a(i,t),a=a==null?t:_({},t,a),e.memoizedState=a,e.lanes===0&&(e.updateQueue.baseState=a)}var Ks={enqueueSetState:function(e,t,a){e=e._reactInternals;var i=Rt(),s=ea(i);s.payload=t,a!=null&&(s.callback=a),t=ta(e,s,i),t!==null&&(Dt(t,e,i),xi(t,e,i))},enqueueReplaceState:function(e,t,a){e=e._reactInternals;var i=Rt(),s=ea(i);s.tag=1,s.payload=t,a!=null&&(s.callback=a),t=ta(e,s,i),t!==null&&(Dt(t,e,i),xi(t,e,i))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var a=Rt(),i=ea(a);i.tag=2,t!=null&&(i.callback=t),t=ta(e,i,a),t!==null&&(Dt(t,e,a),xi(t,e,a))}};function ev(e,t,a,i,s,c,h){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(i,c,h):t.prototype&&t.prototype.isPureReactComponent?!di(a,i)||!di(s,c):!0}function tv(e,t,a,i){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(a,i),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(a,i),t.state!==e&&Ks.enqueueReplaceState(t,t.state,null)}function La(e,t){var a=t;if("ref"in t){a={};for(var i in t)i!=="ref"&&(a[i]=t[i])}if(e=e.defaultProps){a===t&&(a=_({},a));for(var s in e)a[s]===void 0&&(a[s]=e[s])}return a}var ir=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)};function nv(e){ir(e)}function av(e){console.error(e)}function lv(e){ir(e)}function ur(e,t){try{var a=e.onUncaughtError;a(t.value,{componentStack:t.stack})}catch(i){setTimeout(function(){throw i})}}function iv(e,t,a){try{var i=e.onCaughtError;i(a.value,{componentStack:a.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(s){setTimeout(function(){throw s})}}function Qs(e,t,a){return a=ea(a),a.tag=3,a.payload={element:null},a.callback=function(){ur(e,t)},a}function uv(e){return e=ea(e),e.tag=3,e}function rv(e,t,a,i){var s=a.type.getDerivedStateFromError;if(typeof s=="function"){var c=i.value;e.payload=function(){return s(c)},e.callback=function(){iv(t,a,i)}}var h=a.stateNode;h!==null&&typeof h.componentDidCatch=="function"&&(e.callback=function(){iv(t,a,i),typeof s!="function"&&(ca===null?ca=new Set([this]):ca.add(this));var g=i.stack;this.componentDidCatch(i.value,{componentStack:g!==null?g:""})})}function s0(e,t,a,i,s){if(a.flags|=32768,i!==null&&typeof i=="object"&&typeof i.then=="function"){if(t=a.alternate,t!==null&&gi(t,a,s,!0),a=Vt.current,a!==null){switch(a.tag){case 13:return cn===null?_c():a.alternate===null&&ke===0&&(ke=3),a.flags&=-257,a.flags|=65536,a.lanes=s,i===Es?a.flags|=16384:(t=a.updateQueue,t===null?a.updateQueue=new Set([i]):t.add(i),bc(e,i,s)),!1;case 22:return a.flags|=65536,i===Es?a.flags|=16384:(t=a.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([i])},a.updateQueue=t):(a=t.retryQueue,a===null?t.retryQueue=new Set([i]):a.add(i)),bc(e,i,s)),!1}throw Error(r(435,a.tag))}return bc(e,i,s),_c(),!1}if(ye)return t=Vt.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=s,i!==ps&&(e=Error(r(422),{cause:i}),pi($t(e,a)))):(i!==ps&&(t=Error(r(423),{cause:i}),pi($t(t,a))),e=e.current.alternate,e.flags|=65536,s&=-s,e.lanes|=s,i=$t(i,a),s=Qs(e.stateNode,i,s),Ts(e,s),ke!==4&&(ke=2)),!1;var c=Error(r(520),{cause:i});if(c=$t(c,a),Bi===null?Bi=[c]:Bi.push(c),ke!==4&&(ke=2),t===null)return!0;i=$t(i,a),a=t;do{switch(a.tag){case 3:return a.flags|=65536,e=s&-s,a.lanes|=e,e=Qs(a.stateNode,i,e),Ts(a,e),!1;case 1:if(t=a.type,c=a.stateNode,(a.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||c!==null&&typeof c.componentDidCatch=="function"&&(ca===null||!ca.has(c))))return a.flags|=65536,s&=-s,a.lanes|=s,s=uv(s),rv(s,e,a,i),Ts(a,s),!1}a=a.return}while(a!==null);return!1}var ov=Error(r(461)),et=!1;function it(e,t,a,i){t.child=e===null?Fh(t,null,a,i):Al(t,e.child,a,i)}function sv(e,t,a,i,s){a=a.render;var c=t.ref;if("ref"in i){var h={};for(var g in i)g!=="ref"&&(h[g]=i[g])}else h=i;return Ua(t),i=Ns(e,t,a,h,c,s),g=js(),e!==null&&!et?(Ms(e,t,s),jn(e,t,s)):(ye&&g&&vs(t),t.flags|=1,it(e,t,i,s),t.child)}function cv(e,t,a,i,s){if(e===null){var c=a.type;return typeof c=="function"&&!fs(c)&&c.defaultProps===void 0&&a.compare===null?(t.tag=15,t.type=c,fv(e,t,c,i,s)):(e=$u(a.type,null,i,t,t.mode,s),e.ref=t.ref,e.return=t,t.child=e)}if(c=e.child,!nc(e,s)){var h=c.memoizedProps;if(a=a.compare,a=a!==null?a:di,a(h,i)&&e.ref===t.ref)return jn(e,t,s)}return t.flags|=1,e=An(c,i),e.ref=t.ref,e.return=t,t.child=e}function fv(e,t,a,i,s){if(e!==null){var c=e.memoizedProps;if(di(c,i)&&e.ref===t.ref)if(et=!1,t.pendingProps=i=c,nc(e,s))(e.flags&131072)!==0&&(et=!0);else return t.lanes=e.lanes,jn(e,t,s)}return Js(e,t,a,i,s)}function dv(e,t,a){var i=t.pendingProps,s=i.children,c=e!==null?e.memoizedState:null;if(i.mode==="hidden"){if((t.flags&128)!==0){if(i=c!==null?c.baseLanes|a:a,e!==null){for(s=t.child=e.child,c=0;s!==null;)c=c|s.lanes|s.childLanes,s=s.sibling;t.childLanes=c&~i}else t.childLanes=0,t.child=null;return hv(e,t,i,a)}if((a&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&Yu(t,c!==null?c.cachePool:null),c!==null?fh(t,c):zs(),Ih(t);else return t.lanes=t.childLanes=536870912,hv(e,t,c!==null?c.baseLanes|a:a,a)}else c!==null?(Yu(t,c.cachePool),fh(t,c),la(),t.memoizedState=null):(e!==null&&Yu(t,null),zs(),la());return it(e,t,s,a),t.child}function hv(e,t,a,i){var s=xs();return s=s===null?null:{parent:Pe._currentValue,pool:s},t.memoizedState={baseLanes:a,cachePool:s},e!==null&&Yu(t,null),zs(),Ih(t),e!==null&&gi(e,t,i,!0),null}function rr(e,t){var a=t.ref;if(a===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof a!="function"&&typeof a!="object")throw Error(r(284));(e===null||e.ref!==a)&&(t.flags|=4194816)}}function Js(e,t,a,i,s){return Ua(t),a=Ns(e,t,a,i,void 0,s),i=js(),e!==null&&!et?(Ms(e,t,s),jn(e,t,s)):(ye&&i&&vs(t),t.flags|=1,it(e,t,a,s),t.child)}function vv(e,t,a,i,s,c){return Ua(t),t.updateQueue=null,a=hh(t,i,a,s),dh(e),i=js(),e!==null&&!et?(Ms(e,t,c),jn(e,t,c)):(ye&&i&&vs(t),t.flags|=1,it(e,t,a,c),t.child)}function mv(e,t,a,i,s){if(Ua(t),t.stateNode===null){var c=ml,h=a.contextType;typeof h=="object"&&h!==null&&(c=ft(h)),c=new a(i,c),t.memoizedState=c.state!==null&&c.state!==void 0?c.state:null,c.updater=Ks,t.stateNode=c,c._reactInternals=t,c=t.stateNode,c.props=i,c.state=t.memoizedState,c.refs={},Os(t),h=a.contextType,c.context=typeof h=="object"&&h!==null?ft(h):ml,c.state=t.memoizedState,h=a.getDerivedStateFromProps,typeof h=="function"&&(Xs(t,a,h,i),c.state=t.memoizedState),typeof a.getDerivedStateFromProps=="function"||typeof c.getSnapshotBeforeUpdate=="function"||typeof c.UNSAFE_componentWillMount!="function"&&typeof c.componentWillMount!="function"||(h=c.state,typeof c.componentWillMount=="function"&&c.componentWillMount(),typeof c.UNSAFE_componentWillMount=="function"&&c.UNSAFE_componentWillMount(),h!==c.state&&Ks.enqueueReplaceState(c,c.state,null),Oi(t,i,c,s),Ei(),c.state=t.memoizedState),typeof c.componentDidMount=="function"&&(t.flags|=4194308),i=!0}else if(e===null){c=t.stateNode;var g=t.memoizedProps,y=La(a,g);c.props=y;var T=c.context,j=a.contextType;h=ml,typeof j=="object"&&j!==null&&(h=ft(j));var L=a.getDerivedStateFromProps;j=typeof L=="function"||typeof c.getSnapshotBeforeUpdate=="function",g=t.pendingProps!==g,j||typeof c.UNSAFE_componentWillReceiveProps!="function"&&typeof c.componentWillReceiveProps!="function"||(g||T!==h)&&tv(t,c,i,h),In=!1;var z=t.memoizedState;c.state=z,Oi(t,i,c,s),Ei(),T=t.memoizedState,g||z!==T||In?(typeof L=="function"&&(Xs(t,a,L,i),T=t.memoizedState),(y=In||ev(t,a,y,i,z,T,h))?(j||typeof c.UNSAFE_componentWillMount!="function"&&typeof c.componentWillMount!="function"||(typeof c.componentWillMount=="function"&&c.componentWillMount(),typeof c.UNSAFE_componentWillMount=="function"&&c.UNSAFE_componentWillMount()),typeof c.componentDidMount=="function"&&(t.flags|=4194308)):(typeof c.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=i,t.memoizedState=T),c.props=i,c.state=T,c.context=h,i=y):(typeof c.componentDidMount=="function"&&(t.flags|=4194308),i=!1)}else{c=t.stateNode,As(e,t),h=t.memoizedProps,j=La(a,h),c.props=j,L=t.pendingProps,z=c.context,T=a.contextType,y=ml,typeof T=="object"&&T!==null&&(y=ft(T)),g=a.getDerivedStateFromProps,(T=typeof g=="function"||typeof c.getSnapshotBeforeUpdate=="function")||typeof c.UNSAFE_componentWillReceiveProps!="function"&&typeof c.componentWillReceiveProps!="function"||(h!==L||z!==y)&&tv(t,c,i,y),In=!1,z=t.memoizedState,c.state=z,Oi(t,i,c,s),Ei();var D=t.memoizedState;h!==L||z!==D||In||e!==null&&e.dependencies!==null&&qu(e.dependencies)?(typeof g=="function"&&(Xs(t,a,g,i),D=t.memoizedState),(j=In||ev(t,a,j,i,z,D,y)||e!==null&&e.dependencies!==null&&qu(e.dependencies))?(T||typeof c.UNSAFE_componentWillUpdate!="function"&&typeof c.componentWillUpdate!="function"||(typeof c.componentWillUpdate=="function"&&c.componentWillUpdate(i,D,y),typeof c.UNSAFE_componentWillUpdate=="function"&&c.UNSAFE_componentWillUpdate(i,D,y)),typeof c.componentDidUpdate=="function"&&(t.flags|=4),typeof c.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof c.componentDidUpdate!="function"||h===e.memoizedProps&&z===e.memoizedState||(t.flags|=4),typeof c.getSnapshotBeforeUpdate!="function"||h===e.memoizedProps&&z===e.memoizedState||(t.flags|=1024),t.memoizedProps=i,t.memoizedState=D),c.props=i,c.state=D,c.context=y,i=j):(typeof c.componentDidUpdate!="function"||h===e.memoizedProps&&z===e.memoizedState||(t.flags|=4),typeof c.getSnapshotBeforeUpdate!="function"||h===e.memoizedProps&&z===e.memoizedState||(t.flags|=1024),i=!1)}return c=i,rr(e,t),i=(t.flags&128)!==0,c||i?(c=t.stateNode,a=i&&typeof a.getDerivedStateFromError!="function"?null:c.render(),t.flags|=1,e!==null&&i?(t.child=Al(t,e.child,null,s),t.child=Al(t,null,a,s)):it(e,t,a,s),t.memoizedState=c.state,e=t.child):e=jn(e,t,s),e}function pv(e,t,a,i){return mi(),t.flags|=256,it(e,t,a,i),t.child}var Ps={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Ws(e){return{baseLanes:e,cachePool:ah()}}function Fs(e,t,a){return e=e!==null?e.childLanes&~a:0,t&&(e|=Yt),e}function gv(e,t,a){var i=t.pendingProps,s=!1,c=(t.flags&128)!==0,h;if((h=c)||(h=e!==null&&e.memoizedState===null?!1:(We.current&2)!==0),h&&(s=!0,t.flags&=-129),h=(t.flags&32)!==0,t.flags&=-33,e===null){if(ye){if(s?aa(t):la(),ye){var g=He,y;if(y=g){e:{for(y=g,g=sn;y.nodeType!==8;){if(!g){g=null;break e}if(y=It(y.nextSibling),y===null){g=null;break e}}g=y}g!==null?(t.memoizedState={dehydrated:g,treeContext:Da!==null?{id:Tn,overflow:wn}:null,retryLane:536870912,hydrationErrors:null},y=At(18,null,null,0),y.stateNode=g,y.return=t,t.child=y,ht=t,He=null,y=!0):y=!1}y||Ma(t)}if(g=t.memoizedState,g!==null&&(g=g.dehydrated,g!==null))return Uc(g)?t.lanes=32:t.lanes=536870912,null;Nn(t)}return g=i.children,i=i.fallback,s?(la(),s=t.mode,g=or({mode:"hidden",children:g},s),i=Ra(i,s,a,null),g.return=t,i.return=t,g.sibling=i,t.child=g,s=t.child,s.memoizedState=Ws(a),s.childLanes=Fs(e,h,a),t.memoizedState=Ps,i):(aa(t),Is(t,g))}if(y=e.memoizedState,y!==null&&(g=y.dehydrated,g!==null)){if(c)t.flags&256?(aa(t),t.flags&=-257,t=ec(e,t,a)):t.memoizedState!==null?(la(),t.child=e.child,t.flags|=128,t=null):(la(),s=i.fallback,g=t.mode,i=or({mode:"visible",children:i.children},g),s=Ra(s,g,a,null),s.flags|=2,i.return=t,s.return=t,i.sibling=s,t.child=i,Al(t,e.child,null,a),i=t.child,i.memoizedState=Ws(a),i.childLanes=Fs(e,h,a),t.memoizedState=Ps,t=s);else if(aa(t),Uc(g)){if(h=g.nextSibling&&g.nextSibling.dataset,h)var T=h.dgst;h=T,i=Error(r(419)),i.stack="",i.digest=h,pi({value:i,source:null,stack:null}),t=ec(e,t,a)}else if(et||gi(e,t,a,!1),h=(a&e.childLanes)!==0,et||h){if(h=Re,h!==null&&(i=a&-a,i=(i&42)!==0?1:Zo(i),i=(i&(h.suspendedLanes|a))!==0?0:i,i!==0&&i!==y.retryLane))throw y.retryLane=i,vl(e,i),Dt(h,e,i),ov;g.data==="$?"||_c(),t=ec(e,t,a)}else g.data==="$?"?(t.flags|=192,t.child=e.child,t=null):(e=y.treeContext,He=It(g.nextSibling),ht=t,ye=!0,ja=null,sn=!1,e!==null&&(kt[qt++]=Tn,kt[qt++]=wn,kt[qt++]=Da,Tn=e.id,wn=e.overflow,Da=t),t=Is(t,i.children),t.flags|=4096);return t}return s?(la(),s=i.fallback,g=t.mode,y=e.child,T=y.sibling,i=An(y,{mode:"hidden",children:i.children}),i.subtreeFlags=y.subtreeFlags&65011712,T!==null?s=An(T,s):(s=Ra(s,g,a,null),s.flags|=2),s.return=t,i.return=t,i.sibling=s,t.child=i,i=s,s=t.child,g=e.child.memoizedState,g===null?g=Ws(a):(y=g.cachePool,y!==null?(T=Pe._currentValue,y=y.parent!==T?{parent:T,pool:T}:y):y=ah(),g={baseLanes:g.baseLanes|a,cachePool:y}),s.memoizedState=g,s.childLanes=Fs(e,h,a),t.memoizedState=Ps,i):(aa(t),a=e.child,e=a.sibling,a=An(a,{mode:"visible",children:i.children}),a.return=t,a.sibling=null,e!==null&&(h=t.deletions,h===null?(t.deletions=[e],t.flags|=16):h.push(e)),t.child=a,t.memoizedState=null,a)}function Is(e,t){return t=or({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function or(e,t){return e=At(22,e,null,t),e.lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function ec(e,t,a){return Al(t,e.child,null,a),e=Is(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function _v(e,t,a){e.lanes|=t;var i=e.alternate;i!==null&&(i.lanes|=t),_s(e.return,t,a)}function tc(e,t,a,i,s){var c=e.memoizedState;c===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:i,tail:a,tailMode:s}:(c.isBackwards=t,c.rendering=null,c.renderingStartTime=0,c.last=i,c.tail=a,c.tailMode=s)}function yv(e,t,a){var i=t.pendingProps,s=i.revealOrder,c=i.tail;if(it(e,t,i.children,a),i=We.current,(i&2)!==0)i=i&1|2,t.flags|=128;else{if(e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&_v(e,a,t);else if(e.tag===19)_v(e,a,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}i&=1}switch(Y(We,i),s){case"forwards":for(a=t.child,s=null;a!==null;)e=a.alternate,e!==null&&lr(e)===null&&(s=a),a=a.sibling;a=s,a===null?(s=t.child,t.child=null):(s=a.sibling,a.sibling=null),tc(t,!1,s,a,c);break;case"backwards":for(a=null,s=t.child,t.child=null;s!==null;){if(e=s.alternate,e!==null&&lr(e)===null){t.child=s;break}e=s.sibling,s.sibling=a,a=s,s=e}tc(t,!0,a,null,c);break;case"together":tc(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function jn(e,t,a){if(e!==null&&(t.dependencies=e.dependencies),sa|=t.lanes,(a&t.childLanes)===0)if(e!==null){if(gi(e,t,a,!1),(a&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(r(153));if(t.child!==null){for(e=t.child,a=An(e,e.pendingProps),t.child=a,a.return=t;e.sibling!==null;)e=e.sibling,a=a.sibling=An(e,e.pendingProps),a.return=t;a.sibling=null}return t.child}function nc(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&qu(e)))}function c0(e,t,a){switch(t.tag){case 3:je(t,t.stateNode.containerInfo),Fn(t,Pe,e.memoizedState.cache),mi();break;case 27:case 5:No(t);break;case 4:je(t,t.stateNode.containerInfo);break;case 10:Fn(t,t.type,t.memoizedProps.value);break;case 13:var i=t.memoizedState;if(i!==null)return i.dehydrated!==null?(aa(t),t.flags|=128,null):(a&t.child.childLanes)!==0?gv(e,t,a):(aa(t),e=jn(e,t,a),e!==null?e.sibling:null);aa(t);break;case 19:var s=(e.flags&128)!==0;if(i=(a&t.childLanes)!==0,i||(gi(e,t,a,!1),i=(a&t.childLanes)!==0),s){if(i)return yv(e,t,a);t.flags|=128}if(s=t.memoizedState,s!==null&&(s.rendering=null,s.tail=null,s.lastEffect=null),Y(We,We.current),i)break;return null;case 22:case 23:return t.lanes=0,dv(e,t,a);case 24:Fn(t,Pe,e.memoizedState.cache)}return jn(e,t,a)}function bv(e,t,a){if(e!==null)if(e.memoizedProps!==t.pendingProps)et=!0;else{if(!nc(e,a)&&(t.flags&128)===0)return et=!1,c0(e,t,a);et=(e.flags&131072)!==0}else et=!1,ye&&(t.flags&1048576)!==0&&Pd(t,ku,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var i=t.elementType,s=i._init;if(i=s(i._payload),t.type=i,typeof i=="function")fs(i)?(e=La(i,e),t.tag=1,t=mv(null,t,i,e,a)):(t.tag=0,t=Js(null,t,i,e,a));else{if(i!=null){if(s=i.$$typeof,s===W){t.tag=11,t=sv(null,t,i,e,a);break e}else if(s===xe){t.tag=14,t=cv(null,t,i,e,a);break e}}throw t=Kn(i)||i,Error(r(306,t,""))}}return t;case 0:return Js(e,t,t.type,t.pendingProps,a);case 1:return i=t.type,s=La(i,t.pendingProps),mv(e,t,i,s,a);case 3:e:{if(je(t,t.stateNode.containerInfo),e===null)throw Error(r(387));i=t.pendingProps;var c=t.memoizedState;s=c.element,As(e,t),Oi(t,i,null,a);var h=t.memoizedState;if(i=h.cache,Fn(t,Pe,i),i!==c.cache&&ys(t,[Pe],a,!0),Ei(),i=h.element,c.isDehydrated)if(c={element:i,isDehydrated:!1,cache:h.cache},t.updateQueue.baseState=c,t.memoizedState=c,t.flags&256){t=pv(e,t,i,a);break e}else if(i!==s){s=$t(Error(r(424)),t),pi(s),t=pv(e,t,i,a);break e}else{switch(e=t.stateNode.containerInfo,e.nodeType){case 9:e=e.body;break;default:e=e.nodeName==="HTML"?e.ownerDocument.body:e}for(He=It(e.firstChild),ht=t,ye=!0,ja=null,sn=!0,a=Fh(t,null,i,a),t.child=a;a;)a.flags=a.flags&-3|4096,a=a.sibling}else{if(mi(),i===s){t=jn(e,t,a);break e}it(e,t,i,a)}t=t.child}return t;case 26:return rr(e,t),e===null?(a=Om(t.type,null,t.pendingProps,null))?t.memoizedState=a:ye||(a=t.type,e=t.pendingProps,i=xr(re.current).createElement(a),i[ct]=t,i[vt]=e,rt(i,a,e),Ie(i),t.stateNode=i):t.memoizedState=Om(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return No(t),e===null&&ye&&(i=t.stateNode=Sm(t.type,t.pendingProps,re.current),ht=t,sn=!0,s=He,ha(t.type)?(Zc=s,He=It(i.firstChild)):He=s),it(e,t,t.pendingProps.children,a),rr(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&ye&&((s=i=He)&&(i=L0(i,t.type,t.pendingProps,sn),i!==null?(t.stateNode=i,ht=t,He=It(i.firstChild),sn=!1,s=!0):s=!1),s||Ma(t)),No(t),s=t.type,c=t.pendingProps,h=e!==null?e.memoizedProps:null,i=c.children,jc(s,c)?i=null:h!==null&&jc(s,h)&&(t.flags|=32),t.memoizedState!==null&&(s=Ns(e,t,n0,null,null,a),Xi._currentValue=s),rr(e,t),it(e,t,i,a),t.child;case 6:return e===null&&ye&&((e=a=He)&&(a=$0(a,t.pendingProps,sn),a!==null?(t.stateNode=a,ht=t,He=null,e=!0):e=!1),e||Ma(t)),null;case 13:return gv(e,t,a);case 4:return je(t,t.stateNode.containerInfo),i=t.pendingProps,e===null?t.child=Al(t,null,i,a):it(e,t,i,a),t.child;case 11:return sv(e,t,t.type,t.pendingProps,a);case 7:return it(e,t,t.pendingProps,a),t.child;case 8:return it(e,t,t.pendingProps.children,a),t.child;case 12:return it(e,t,t.pendingProps.children,a),t.child;case 10:return i=t.pendingProps,Fn(t,t.type,i.value),it(e,t,i.children,a),t.child;case 9:return s=t.type._context,i=t.pendingProps.children,Ua(t),s=ft(s),i=i(s),t.flags|=1,it(e,t,i,a),t.child;case 14:return cv(e,t,t.type,t.pendingProps,a);case 15:return fv(e,t,t.type,t.pendingProps,a);case 19:return yv(e,t,a);case 31:return i=t.pendingProps,a=t.mode,i={mode:i.mode,children:i.children},e===null?(a=or(i,a),a.ref=t.ref,t.child=a,a.return=t,t=a):(a=An(e.child,i),a.ref=t.ref,t.child=a,a.return=t,t=a),t;case 22:return dv(e,t,a);case 24:return Ua(t),i=ft(Pe),e===null?(s=xs(),s===null&&(s=Re,c=bs(),s.pooledCache=c,c.refCount++,c!==null&&(s.pooledCacheLanes|=a),s=c),t.memoizedState={parent:i,cache:s},Os(t),Fn(t,Pe,s)):((e.lanes&a)!==0&&(As(e,t),Oi(t,null,null,a),Ei()),s=e.memoizedState,c=t.memoizedState,s.parent!==i?(s={parent:i,cache:i},t.memoizedState=s,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=s),Fn(t,Pe,i)):(i=c.cache,Fn(t,Pe,i),i!==s.cache&&ys(t,[Pe],a,!0))),it(e,t,t.pendingProps.children,a),t.child;case 29:throw t.pendingProps}throw Error(r(156,t.tag))}function Mn(e){e.flags|=4}function Sv(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!Rm(t)){if(t=Vt.current,t!==null&&((ve&4194048)===ve?cn!==null:(ve&62914560)!==ve&&(ve&536870912)===0||t!==cn))throw Si=Es,lh;e.flags|=8192}}function sr(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?If():536870912,e.lanes|=t,Rl|=t)}function Ni(e,t){if(!ye)switch(e.tailMode){case"hidden":t=e.tail;for(var a=null;t!==null;)t.alternate!==null&&(a=t),t=t.sibling;a===null?e.tail=null:a.sibling=null;break;case"collapsed":a=e.tail;for(var i=null;a!==null;)a.alternate!==null&&(i=a),a=a.sibling;i===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:i.sibling=null}}function Be(e){var t=e.alternate!==null&&e.alternate.child===e.child,a=0,i=0;if(t)for(var s=e.child;s!==null;)a|=s.lanes|s.childLanes,i|=s.subtreeFlags&65011712,i|=s.flags&65011712,s.return=e,s=s.sibling;else for(s=e.child;s!==null;)a|=s.lanes|s.childLanes,i|=s.subtreeFlags,i|=s.flags,s.return=e,s=s.sibling;return e.subtreeFlags|=i,e.childLanes=a,t}function f0(e,t,a){var i=t.pendingProps;switch(ms(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Be(t),null;case 1:return Be(t),null;case 3:return a=t.stateNode,i=null,e!==null&&(i=e.memoizedState.cache),t.memoizedState.cache!==i&&(t.flags|=2048),Rn(Pe),Qn(),a.pendingContext&&(a.context=a.pendingContext,a.pendingContext=null),(e===null||e.child===null)&&(vi(t)?Mn(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,Id())),Be(t),null;case 26:return a=t.memoizedState,e===null?(Mn(t),a!==null?(Be(t),Sv(t,a)):(Be(t),t.flags&=-16777217)):a?a!==e.memoizedState?(Mn(t),Be(t),Sv(t,a)):(Be(t),t.flags&=-16777217):(e.memoizedProps!==i&&Mn(t),Be(t),t.flags&=-16777217),null;case 27:bu(t),a=re.current;var s=t.type;if(e!==null&&t.stateNode!=null)e.memoizedProps!==i&&Mn(t);else{if(!i){if(t.stateNode===null)throw Error(r(166));return Be(t),null}e=ee.current,vi(t)?Wd(t):(e=Sm(s,i,a),t.stateNode=e,Mn(t))}return Be(t),null;case 5:if(bu(t),a=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==i&&Mn(t);else{if(!i){if(t.stateNode===null)throw Error(r(166));return Be(t),null}if(e=ee.current,vi(t))Wd(t);else{switch(s=xr(re.current),e){case 1:e=s.createElementNS("http://www.w3.org/2000/svg",a);break;case 2:e=s.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;default:switch(a){case"svg":e=s.createElementNS("http://www.w3.org/2000/svg",a);break;case"math":e=s.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;case"script":e=s.createElement("div"),e.innerHTML=" - + +
- \ No newline at end of file + diff --git a/vite-app/docs/runtime-configuration.md b/vite-app/docs/runtime-configuration.md new file mode 100644 index 00000000..72f5d0ba --- /dev/null +++ b/vite-app/docs/runtime-configuration.md @@ -0,0 +1,73 @@ +# Vite App with Runtime Configuration + +This Vite app is designed to work with the Python evaluation protocol server and automatically discovers its configuration at runtime. + +## Runtime Configuration + +The app automatically discovers the server configuration in the following order: + +1. **Server-Injected Configuration** (Recommended): The Python server injects configuration directly into the HTML +2. **Location-Based Discovery**: Falls back to discovering configuration from the current URL +3. **Default Values**: Uses localhost:8000 as a last resort + +## How It Works + +### Server-Side Injection +The Python server (`logs_server.py`) automatically injects configuration into the HTML response: + +```html + +``` + +### Frontend Discovery +The frontend automatically reads this configuration and uses it for WebSocket connections: + +```typescript +// First, check if server injected configuration is available +if (window.SERVER_CONFIG) { + const serverConfig = window.SERVER_CONFIG; + config.websocket.host = serverConfig.host; + config.websocket.port = serverConfig.port; + // ... etc +} +``` + +## Usage + +### Starting the Server +```bash +# Default port 8000 +python -m eval_protocol.utils.logs_server + +# Custom port +python -m eval_protocol.utils.logs_server --port 9000 + +# Custom host and port +python -m eval_protocol.utils.logs_server --host 0.0.0.0 --port 9000 + +# Custom build directory +python -m eval_protocol.utils.logs_server --build-dir /path/to/dist +``` + +### Building the Frontend +```bash +cd vite-app +pnpm install +pnpm build +``` + +The built files will be in the `dist/` directory and automatically served by the Python server. + +## Benefits + +- **No hard-coded ports**: The frontend automatically adapts to whatever port the server runs on +- **Flexible deployment**: Can run on any port without rebuilding the frontend +- **Automatic discovery**: Works whether served from the same origin or different origins +- **Fallback support**: Gracefully handles cases where server injection isn't available diff --git a/vite-app/src/App.tsx b/vite-app/src/App.tsx index a5e86e63..9eeaae0f 100644 --- a/vite-app/src/App.tsx +++ b/vite-app/src/App.tsx @@ -8,6 +8,7 @@ import { EvaluationRowSchema, type EvaluationRow } from "./types/eval-protocol"; import { WebSocketServerMessageSchema } from "./types/websocket"; import { GlobalState } from "./GlobalState"; import logoLight from "./assets/logo-light.png"; +import { getWebSocketUrl, discoverServerConfig } from "./config"; export const state = new GlobalState(); @@ -26,7 +27,7 @@ const App = observer(() => { return; // Already connected } - const ws = new WebSocket("ws://localhost:8000/ws"); + const ws = new WebSocket(getWebSocketUrl()); wsRef.current = ws; ws.onopen = () => { @@ -109,7 +110,13 @@ const App = observer(() => { }; useEffect(() => { - connectWebSocket(); + // Discover server configuration first, then connect + const initializeApp = async () => { + await discoverServerConfig(); + connectWebSocket(); + }; + + initializeApp(); return () => { if (reconnectTimeoutRef.current) { diff --git a/vite-app/src/config.ts b/vite-app/src/config.ts new file mode 100644 index 00000000..96626f06 --- /dev/null +++ b/vite-app/src/config.ts @@ -0,0 +1,63 @@ +// Configuration for the application +export const config = { + // WebSocket connection settings + websocket: { + host: 'localhost', // Will be discovered at runtime + port: '8000', // Will be discovered at runtime + protocol: 'ws', + }, + // API settings + api: { + host: 'localhost', // Will be discovered at runtime + port: '8000', // Will be discovered at runtime + protocol: 'http', + }, +}; + +// Helper function to build WebSocket URL +export const getWebSocketUrl = (): string => { + const { protocol, host, port } = config.websocket; + return `${protocol}://${host}:${port}/ws`; +}; + +// Helper function to build API URL +export const getApiUrl = (): string => { + const { protocol, host, port } = config.api; + return `${protocol}://${host}:${port}`; +}; + +// Runtime configuration discovery +export const discoverServerConfig = async (): Promise => { + try { + // First, check if server injected configuration is available + if (window.SERVER_CONFIG) { + const serverConfig = window.SERVER_CONFIG; + config.websocket.host = serverConfig.host; + config.websocket.port = serverConfig.port; + config.websocket.protocol = serverConfig.protocol; + config.api.host = serverConfig.host; + config.api.port = serverConfig.port; + config.api.protocol = serverConfig.apiProtocol; + console.log('Using server-injected config:', config); + return; + } + + // Fallback: Try to discover server configuration from the current location + const currentHost = window.location.hostname; + const currentPort = window.location.port; + const currentProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + + // Update config with discovered values + config.websocket.host = currentHost; + config.websocket.port = currentPort || (currentProtocol === 'wss:' ? '443' : '80'); + config.websocket.protocol = currentProtocol; + + config.api.host = currentHost; + config.api.port = currentPort || (currentProtocol === 'wss:' ? '443' : '80'); + config.api.protocol = window.location.protocol === 'https:' ? 'https:' : 'http:'; + + console.log('Using discovered config from location:', config); + } catch (error) { + console.warn('Failed to discover server config, using defaults:', error); + } +}; diff --git a/vite-app/src/types/global.d.ts b/vite-app/src/types/global.d.ts new file mode 100644 index 00000000..a8ccce4a --- /dev/null +++ b/vite-app/src/types/global.d.ts @@ -0,0 +1,13 @@ +// Global type declarations +declare global { + interface Window { + SERVER_CONFIG?: { + host: string; + port: string; + protocol: string; + apiProtocol: string; + }; + } +} + +export {}; From 3c36fabc21e258d0439e8c8ec74c6f5afc2cd3e8 Mon Sep 17 00:00:00 2001 From: Dylan Huang Date: Mon, 11 Aug 2025 10:35:13 -0700 Subject: [PATCH 2/9] Fix WebSocketManager to reset broadcast task after cancellation --- eval_protocol/utils/logs_server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/eval_protocol/utils/logs_server.py b/eval_protocol/utils/logs_server.py index 815d33ef..e2a655cd 100644 --- a/eval_protocol/utils/logs_server.py +++ b/eval_protocol/utils/logs_server.py @@ -109,6 +109,7 @@ def stop_broadcast_loop(self): """Stop the broadcast loop.""" if self._broadcast_task and not self._broadcast_task.done(): self._broadcast_task.cancel() + self._broadcast_task = None class EvaluationWatcher: From 2e0be2401066424036a247ba776743d1c9b76830 Mon Sep 17 00:00:00 2001 From: Dylan Huang Date: Mon, 11 Aug 2025 10:35:28 -0700 Subject: [PATCH 3/9] simple tests work --- tests/test_logs_server_simple.py | 88 ++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/test_logs_server_simple.py diff --git a/tests/test_logs_server_simple.py b/tests/test_logs_server_simple.py new file mode 100644 index 00000000..98bc3e47 --- /dev/null +++ b/tests/test_logs_server_simple.py @@ -0,0 +1,88 @@ +import json +import tempfile +from pathlib import Path +from unittest.mock import AsyncMock, Mock, patch + +import pytest + +from eval_protocol.utils.logs_server import EvaluationWatcher, WebSocketManager + + +class TestWebSocketManagerBasic: + """Basic tests for WebSocketManager without starting real loops.""" + + def test_initialization(self): + """Test WebSocketManager initialization.""" + manager = WebSocketManager() + assert len(manager.active_connections) == 0 + assert manager._broadcast_queue is not None + assert manager._broadcast_task is None + + @pytest.mark.asyncio + async def test_connect_disconnect(self): + """Test WebSocket connection and disconnection.""" + manager = WebSocketManager() + mock_websocket = AsyncMock() + + # Test connection + await manager.connect(mock_websocket) + assert len(manager.active_connections) == 1 + assert mock_websocket in manager.active_connections + mock_websocket.accept.assert_called_once() + + # Test disconnection + manager.disconnect(mock_websocket) + assert len(manager.active_connections) == 0 + assert mock_websocket not in manager.active_connections + + def test_broadcast_row_upserted(self): + """Test broadcasting row upsert events.""" + manager = WebSocketManager() + + # Create a simple mock row + mock_row = Mock() + mock_row.model_dump.return_value = {"id": "test-123", "content": "test"} + + # Test that broadcast doesn't fail when no connections + manager.broadcast_row_upserted(mock_row) + + # Test that message is queued + assert not manager._broadcast_queue.empty() + queued_message = manager._broadcast_queue.get_nowait() + assert "type" in queued_message + assert "row" in queued_message + json_message = json.loads(queued_message) + assert json_message["row"]["id"] == "test-123" + assert json_message["row"]["content"] == "test" + + +class TestEvaluationWatcherBasic: + """Basic tests for EvaluationWatcher without starting real threads.""" + + def test_initialization(self): + """Test EvaluationWatcher initialization.""" + mock_manager = Mock() + watcher = EvaluationWatcher(mock_manager) + assert watcher.websocket_manager == mock_manager + assert watcher._thread is None + assert watcher._stop_event is not None + + def test_start_stop(self): + """Test starting and stopping the watcher.""" + mock_manager = Mock() + watcher = EvaluationWatcher(mock_manager) + + # Test start + watcher.start() + assert watcher._thread is not None + assert watcher._thread.is_alive() + + # Test stop + watcher.stop() + assert watcher._stop_event.is_set() + if watcher._thread: + watcher._thread.join(timeout=1.0) + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) From 6cdadf3ce8206628b14377535caff9903fd3d1ec Mon Sep 17 00:00:00 2001 From: Dylan Huang Date: Mon, 11 Aug 2025 10:49:23 -0700 Subject: [PATCH 4/9] TODO: TestLogsServer --- eval_protocol/utils/logs_server.py | 26 +- tests/test_logs_server.py | 496 +++++++++++++++++++++++++++++ 2 files changed, 516 insertions(+), 6 deletions(-) create mode 100644 tests/test_logs_server.py diff --git a/eval_protocol/utils/logs_server.py b/eval_protocol/utils/logs_server.py index e2a655cd..e2a675c7 100644 --- a/eval_protocol/utils/logs_server.py +++ b/eval_protocol/utils/logs_server.py @@ -87,18 +87,32 @@ async def _send_text_to_all_connections(self, text: str): return tasks = [] + failed_connections = [] + for connection in connections: try: tasks.append(connection.send_text(text)) except Exception as e: logger.error(f"Failed to send text to WebSocket: {e}") - with self._lock: - try: - self.active_connections.remove(connection) - except ValueError: - pass + failed_connections.append(connection) + + # Execute all sends in parallel if tasks: - await asyncio.gather(*tasks, return_exceptions=True) + results = await asyncio.gather(*tasks, return_exceptions=True) + + # Check for any exceptions that occurred during execution + for i, result in enumerate(results): + if isinstance(result, Exception): + logger.error(f"Failed to send text to WebSocket: {result}") + failed_connections.append(connections[i]) + + # Remove all failed connections + with self._lock: + for connection in failed_connections: + try: + self.active_connections.remove(connection) + except ValueError: + pass def start_broadcast_loop(self): """Start the broadcast loop in the current event loop.""" diff --git a/tests/test_logs_server.py b/tests/test_logs_server.py new file mode 100644 index 00000000..10e5dd6c --- /dev/null +++ b/tests/test_logs_server.py @@ -0,0 +1,496 @@ +import asyncio +import json +import tempfile +import threading +import time +from pathlib import Path +from unittest.mock import AsyncMock, MagicMock, Mock, patch + +import psutil +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from eval_protocol.dataset_logger import default_logger +from eval_protocol.dataset_logger.dataset_logger import LOG_EVENT_TYPE +from eval_protocol.event_bus import event_bus +from eval_protocol.models import EvalMetadata, EvaluationRow, InputMetadata, Message +from eval_protocol.utils.logs_server import ( + EvaluationWatcher, + LogsServer, + WebSocketManager, + create_app, + serve_logs, +) + + +class TestWebSocketManager: + """Test WebSocketManager class.""" + + def test_initialization(self): + """Test WebSocketManager initialization.""" + manager = WebSocketManager() + assert len(manager.active_connections) == 0 + assert manager._broadcast_queue is not None + assert manager._broadcast_task is None + + @pytest.mark.asyncio + async def test_connect_disconnect(self): + """Test WebSocket connection and disconnection.""" + manager = WebSocketManager() + mock_websocket = AsyncMock() + + # Test connection + with patch.object(default_logger, "read", return_value=[]): + await manager.connect(mock_websocket) + assert len(manager.active_connections) == 1 + assert mock_websocket in manager.active_connections + mock_websocket.accept.assert_called_once() + + # Test disconnection + manager.disconnect(mock_websocket) + assert len(manager.active_connections) == 0 + assert mock_websocket not in manager.active_connections + + @pytest.mark.asyncio + async def test_connect_sends_initial_logs(self): + """Test that connecting sends initial logs.""" + manager = WebSocketManager() + mock_websocket = AsyncMock() + + # Mock default_logger.read() + mock_logs = [ + EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + ) + ] + + with patch.object(default_logger, "read", return_value=mock_logs): + await manager.connect(mock_websocket) + + # Verify that initial logs were sent + mock_websocket.send_text.assert_called_once() + sent_data = json.loads(mock_websocket.send_text.call_args[0][0]) + assert sent_data["type"] == "initialize_logs" + assert len(sent_data["logs"]) == 1 + + def test_broadcast_row_upserted(self): + """Test broadcasting row upsert events.""" + manager = WebSocketManager() + test_row = EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + ) + + # Test that broadcast doesn't fail when no connections + manager.broadcast_row_upserted(test_row) + + # Test that message is queued + assert not manager._broadcast_queue.empty() + queued_message = manager._broadcast_queue.get_nowait() + data = json.loads(queued_message) + assert data["type"] == "log" + assert "row" in data + assert data["row"]["messages"][0]["content"] == "test" + assert data["row"]["input_metadata"]["row_id"] == "test-123" + + @pytest.mark.asyncio + async def test_broadcast_loop(self): + """Test the broadcast loop functionality.""" + manager = WebSocketManager() + mock_websocket = AsyncMock() + await manager.connect(mock_websocket) + + # Test that broadcast loop can be started and stopped + manager.start_broadcast_loop() + assert manager._broadcast_task is not None + + # Stop broadcast loop + manager.stop_broadcast_loop() + assert manager._broadcast_task is None + + @pytest.mark.asyncio + async def test_send_text_to_all_connections(self): + """Test sending text to all connections.""" + manager = WebSocketManager() + mock_websocket1 = AsyncMock() + mock_websocket2 = AsyncMock() + + # Mock default_logger.read() to return empty logs + with patch.object(default_logger, "read", return_value=[]): + await manager.connect(mock_websocket1) + await manager.connect(mock_websocket2) + + test_message = "test message" + await manager._send_text_to_all_connections(test_message) + + # Check that the test message was sent to both websockets + mock_websocket1.send_text.assert_any_call(test_message) + mock_websocket2.send_text.assert_any_call(test_message) + + @pytest.mark.asyncio + async def test_send_text_handles_failed_connections(self): + """Test that failed connections are handled gracefully.""" + manager = WebSocketManager() + mock_websocket1 = AsyncMock() + mock_websocket2 = AsyncMock() + + # Mock default_logger.read() to return empty logs + with patch.object(default_logger, "read", return_value=[]): + await manager.connect(mock_websocket1) + await manager.connect(mock_websocket2) + + # Make the second websocket fail AFTER connection is established + # We need to make send_text raise an exception when awaited + async def failing_send_text(text): + raise Exception("Connection failed") + + mock_websocket2.send_text = failing_send_text + + test_message = "test message" + await manager._send_text_to_all_connections(test_message) + + # First websocket should receive the message + mock_websocket1.send_text.assert_any_call(test_message) + # Second websocket should have been removed due to failure + assert len(manager.active_connections) == 1 + assert mock_websocket1 in manager.active_connections + + +class TestEvaluationWatcher: + """Test EvaluationWatcher class.""" + + def test_initialization(self): + """Test EvaluationWatcher initialization.""" + mock_manager = Mock() + watcher = EvaluationWatcher(mock_manager) + assert watcher.websocket_manager == mock_manager + assert watcher._thread is None + assert watcher._stop_event is not None + + def test_start_stop(self): + """Test starting and stopping the watcher.""" + mock_manager = Mock() + watcher = EvaluationWatcher(mock_manager) + + # Test start + watcher.start() + assert watcher._thread is not None + assert watcher._thread.is_alive() + + # Test stop + watcher.stop() + assert watcher._stop_event.is_set() + if watcher._thread: + watcher._thread.join(timeout=1.0) + + @patch("psutil.Process") + def test_should_update_status_running_process(self, mock_process): + """Test status update for running process.""" + mock_manager = Mock() + watcher = EvaluationWatcher(mock_manager) + + # Mock a running process + mock_process_instance = Mock() + mock_process_instance.is_running.return_value = True + mock_process.return_value = mock_process_instance + + test_row = EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + eval_metadata=EvalMetadata(name="test_eval", num_runs=1, aggregation_method="mean", status="running"), + pid=12345, + ) + + # Process is running, should not update + assert watcher._should_update_status(test_row) is False + + @patch("psutil.Process") + def test_should_update_status_stopped_process(self, mock_process): + """Test status update for stopped process.""" + mock_manager = Mock() + watcher = EvaluationWatcher(mock_manager) + + # Mock a stopped process + mock_process_instance = Mock() + mock_process_instance.is_running.return_value = False + mock_process.return_value = mock_process_instance + + test_row = EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + eval_metadata=EvalMetadata(name="test_eval", num_runs=1, aggregation_method="mean", status="running"), + pid=12345, + ) + + # Process is stopped, should update + assert watcher._should_update_status(test_row) is True + + @patch("psutil.Process") + def test_should_update_status_no_such_process(self, mock_process): + """Test status update for non-existent process.""" + mock_manager = Mock() + watcher = EvaluationWatcher(mock_manager) + + # Mock process not found + mock_process.side_effect = psutil.NoSuchProcess(pid=999) + + test_row = EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + eval_metadata=EvalMetadata(name="test_eval", num_runs=1, aggregation_method="mean", status="running"), + pid=999, + ) + + # Process doesn't exist, should update + assert watcher._should_update_status(test_row) is True + + def test_should_update_status_not_running(self): + """Test status update for non-running evaluation.""" + mock_manager = Mock() + watcher = EvaluationWatcher(mock_manager) + + test_row = EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + eval_metadata=EvalMetadata(name="test_eval", num_runs=1, aggregation_method="mean", status="finished"), + pid=12345, + ) + + # Not running status, should not update + assert watcher._should_update_status(test_row) is False + + def test_should_update_status_no_pid(self): + """Test status update for evaluation without PID.""" + mock_manager = Mock() + watcher = EvaluationWatcher(mock_manager) + + test_row = EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + eval_metadata=EvalMetadata(name="test_eval", num_runs=1, aggregation_method="mean", status="running"), + pid=None, + ) + + # No PID, should not update + assert watcher._should_update_status(test_row) is False + + +class TestLogsServer: + """Test LogsServer class.""" + + @pytest.fixture + def temp_build_dir(self): + """Create a temporary build directory for testing.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + # Create a minimal index.html file + (temp_path / "index.html").write_text("Test") + # Create assets directory (required by ViteServer) + (temp_path / "assets").mkdir(exist_ok=True) + yield temp_path + + def test_initialization(self, temp_build_dir): + """Test LogsServer initialization.""" + server = LogsServer(build_dir=str(temp_build_dir)) + assert server.build_dir == str(temp_build_dir) + assert server.websocket_manager is not None + assert server.evaluation_watcher is not None + + def test_initialization_invalid_build_dir(self): + """Test LogsServer initialization with invalid build directory.""" + with pytest.raises(ValueError, match="Build directory does not exist"): + LogsServer(build_dir="/nonexistent/path") + + def test_websocket_routes(self, temp_build_dir): + """Test that WebSocket routes are properly set up.""" + server = LogsServer(build_dir=str(temp_build_dir)) + + # Check that the WebSocket endpoint exists + websocket_routes = [route for route in server.app.routes if hasattr(route, "endpoint")] + assert len(websocket_routes) > 0 + + @pytest.mark.asyncio + async def test_handle_event(self, temp_build_dir): + """Test event handling.""" + server = LogsServer(build_dir=str(temp_build_dir)) + + # Test handling a log event + test_row = EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + ) + + server._handle_event(LOG_EVENT_TYPE, test_row) + # The event should be queued for broadcasting + assert not server.websocket_manager._broadcast_queue.empty() + + def test_create_app_factory(self, temp_build_dir): + """Test the create_app factory function.""" + app = create_app(build_dir=str(temp_build_dir)) + assert isinstance(app, FastAPI) + + def test_serve_logs_convenience_function(self, temp_build_dir): + """Test the serve_logs convenience function.""" + # This should not raise an error + serve_logs(port=8001) + + @pytest.mark.asyncio + async def test_run_async_lifecycle(self, temp_build_dir): + """Test the async lifecycle of the server.""" + server = LogsServer(build_dir=str(temp_build_dir)) + + # Mock the uvicorn.Server to avoid actually starting a server + with patch("uvicorn.Server") as mock_uvicorn_server: + mock_server = Mock() + mock_server.serve = AsyncMock() + mock_uvicorn_server.return_value = mock_server + + # Start the server + start_task = asyncio.create_task(server.run_async()) + + # Wait a bit for it to start + await asyncio.sleep(0.1) + + # Cancel the task instead of calling non-existent stop method + start_task.cancel() + + # Wait for the task to complete + try: + await start_task + except asyncio.CancelledError: + pass + + +class TestLogsServerIntegration: + """Integration tests for LogsServer.""" + + @pytest.fixture + def temp_build_dir_with_files(self): + """Create a temporary build directory with various test files.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create index.html + (temp_path / "index.html").write_text("Test") + + # Create some static assets + (temp_path / "static").mkdir() + (temp_path / "static" / "app.js").write_text("console.log('test');") + (temp_path / "static" / "style.css").write_text("body { color: black; }") + + # Create a nested directory structure + (temp_path / "nested").mkdir() + (temp_path / "nested" / "file.txt").write_text("nested content") + + yield temp_path + + def test_static_file_serving(self, temp_build_dir_with_files): + """Test that static files are served correctly.""" + server = LogsServer(build_dir=str(temp_build_dir_with_files)) + client = TestClient(server.app) + + # Test serving index.html + response = client.get("/") + assert response.status_code == 200 + assert "Test" in response.text + + # Test serving static files + response = client.get("/static/app.js") + assert response.status_code == 200 + assert "console.log('test')" in response.text + + response = client.get("/static/style.css") + assert response.status_code == 200 + assert "color: black" in response.text + + def test_spa_routing(self, temp_build_dir_with_files): + """Test SPA routing fallback.""" + server = LogsServer(build_dir=str(temp_build_dir_with_files)) + client = TestClient(server.app) + + # Test that non-existent routes fall back to index.html + response = client.get("/some/nonexistent/route") + assert response.status_code == 200 + assert "Test" in response.text + + def test_root_endpoint(self, temp_build_dir_with_files): + """Test the root endpoint.""" + server = LogsServer(build_dir=str(temp_build_dir_with_files)) + client = TestClient(server.app) + + response = client.get("/") + assert response.status_code == 200 + assert "Test" in response.text + + def test_health_endpoint(self, temp_build_dir_with_files): + """Test the health endpoint.""" + server = LogsServer(build_dir=str(temp_build_dir_with_files)) + client = TestClient(server.app) + + response = client.get("/health") + assert response.status_code == 200 + data = response.json() + assert data["status"] == "healthy" + + +@pytest.mark.asyncio +class TestAsyncWebSocketOperations: + """Test async WebSocket operations.""" + + async def test_websocket_connection_lifecycle(self): + """Test complete WebSocket connection lifecycle.""" + manager = WebSocketManager() + + # Create mock WebSocket + mock_websocket = AsyncMock() + + # Test connection + with patch.object(default_logger, "read", return_value=[]): + await manager.connect(mock_websocket) + assert len(manager.active_connections) == 1 + + # Test broadcasting without starting the loop + test_row = EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + ) + manager.broadcast_row_upserted(test_row) + + # Verify message was queued + assert not manager._broadcast_queue.empty() + + # Test disconnection + manager.disconnect(mock_websocket) + assert len(manager.active_connections) == 0 + + async def test_multiple_websocket_connections(self): + """Test handling multiple WebSocket connections.""" + manager = WebSocketManager() + + # Create multiple mock WebSockets + mock_websocket1 = AsyncMock() + mock_websocket2 = AsyncMock() + mock_websocket3 = AsyncMock() + + # Connect all + with patch.object(default_logger, "read", return_value=[]): + await manager.connect(mock_websocket1) + await manager.connect(mock_websocket2) + await manager.connect(mock_websocket3) + assert len(manager.active_connections) == 3 + + # Test broadcasting to all without starting the loop + test_row = EvaluationRow( + messages=[Message(role="user", content="test")], + input_metadata=InputMetadata(row_id="test-123"), + ) + manager.broadcast_row_upserted(test_row) + + # Verify message was queued + assert not manager._broadcast_queue.empty() + + # Disconnect one + manager.disconnect(mock_websocket2) + assert len(manager.active_connections) == 2 From 88f0f3a6507658d29bcc99ab39433fa604c49b8e Mon Sep 17 00:00:00 2001 From: Dylan Huang Date: Mon, 11 Aug 2025 10:56:20 -0700 Subject: [PATCH 5/9] TODO: TestLogsServerIntegration --- tests/test_logs_server.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tests/test_logs_server.py b/tests/test_logs_server.py index 10e5dd6c..5516f070 100644 --- a/tests/test_logs_server.py +++ b/tests/test_logs_server.py @@ -9,6 +9,7 @@ import psutil import pytest from fastapi import FastAPI +from fastapi.routing import APIWebSocketRoute from fastapi.testclient import TestClient from eval_protocol.dataset_logger import default_logger @@ -291,16 +292,16 @@ def temp_build_dir(self): (temp_path / "assets").mkdir(exist_ok=True) yield temp_path - def test_initialization(self, temp_build_dir): + def test_initialization(self, temp_build_dir: Path): """Test LogsServer initialization.""" server = LogsServer(build_dir=str(temp_build_dir)) - assert server.build_dir == str(temp_build_dir) + assert server.build_dir == temp_build_dir assert server.websocket_manager is not None assert server.evaluation_watcher is not None def test_initialization_invalid_build_dir(self): """Test LogsServer initialization with invalid build directory.""" - with pytest.raises(ValueError, match="Build directory does not exist"): + with pytest.raises(FileNotFoundError, match="Build directory '/nonexistent/path' does not exist"): LogsServer(build_dir="/nonexistent/path") def test_websocket_routes(self, temp_build_dir): @@ -308,8 +309,13 @@ def test_websocket_routes(self, temp_build_dir): server = LogsServer(build_dir=str(temp_build_dir)) # Check that the WebSocket endpoint exists - websocket_routes = [route for route in server.app.routes if hasattr(route, "endpoint")] - assert len(websocket_routes) > 0 + if not server.app.routes: + raise ValueError("No routes found") + for route in server.app.routes: + if isinstance(route, APIWebSocketRoute) and route.path == "/ws": + break + else: + raise ValueError("WebSocket route not found") @pytest.mark.asyncio async def test_handle_event(self, temp_build_dir): @@ -317,10 +323,10 @@ async def test_handle_event(self, temp_build_dir): server = LogsServer(build_dir=str(temp_build_dir)) # Test handling a log event - test_row = EvaluationRow( - messages=[Message(role="user", content="test")], - input_metadata=InputMetadata(row_id="test-123"), - ) + test_row = { + "messages": [{"role": "user", "content": "test"}], + "input_metadata": {"row_id": "test-123"}, + } server._handle_event(LOG_EVENT_TYPE, test_row) # The event should be queued for broadcasting @@ -333,8 +339,12 @@ def test_create_app_factory(self, temp_build_dir): def test_serve_logs_convenience_function(self, temp_build_dir): """Test the serve_logs convenience function.""" - # This should not raise an error - serve_logs(port=8001) + # Mock the LogsServer.run method to avoid actually starting a server + with patch("eval_protocol.utils.logs_server.LogsServer.run") as mock_run: + # This should not raise an error + serve_logs(port=8001) + # Verify that the run method was called + mock_run.assert_called_once() @pytest.mark.asyncio async def test_run_async_lifecycle(self, temp_build_dir): From 1b421799e6fed80095268217c583cd6499842088 Mon Sep 17 00:00:00 2001 From: Dylan Huang Date: Mon, 11 Aug 2025 11:06:04 -0700 Subject: [PATCH 6/9] TODO: test HTML injection - also test TestAsyncWebSocketOperations --- eval_protocol/utils/vite_server.py | 24 ++++++++++++------------ tests/test_logs_server.py | 23 ++++++++++++----------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/eval_protocol/utils/vite_server.py b/eval_protocol/utils/vite_server.py index 4c3143e2..02eef31d 100644 --- a/eval_protocol/utils/vite_server.py +++ b/eval_protocol/utils/vite_server.py @@ -97,7 +97,17 @@ def _setup_routes(self): # Mount static files self.app.mount("/assets", StaticFiles(directory=self.build_dir / "assets"), name="assets") - # Serve other static files from build directory + @self.app.get("/") + async def root(): + """Serve the main index.html file with injected configuration.""" + return self._serve_index_with_config() + + @self.app.get("/health") + async def health(): + """Health check endpoint.""" + return {"status": "ok", "build_dir": str(self.build_dir)} + + # Serve other static files from build directory - this must be last @self.app.get("/{path:path}") async def serve_spa(path: str): """ @@ -114,22 +124,12 @@ async def serve_spa(path: str): # For SPA routing, serve index.html for non-existent routes # but exclude API routes and asset requests - if not path.startswith(("api/", "assets/")): + if not path.startswith(("api/", "assets/", "health")): return self._serve_index_with_config() # If we get here, the file doesn't exist and it's not a SPA route raise HTTPException(status_code=404, detail="File not found") - @self.app.get("/") - async def root(): - """Serve the main index.html file with injected configuration.""" - return self._serve_index_with_config() - - @self.app.get("/health") - async def health(): - """Health check endpoint.""" - return {"status": "ok", "build_dir": str(self.build_dir)} - def run(self): """ Run the Vite server. diff --git a/tests/test_logs_server.py b/tests/test_logs_server.py index 5516f070..48db912b 100644 --- a/tests/test_logs_server.py +++ b/tests/test_logs_server.py @@ -378,21 +378,22 @@ class TestLogsServerIntegration: @pytest.fixture def temp_build_dir_with_files(self): - """Create a temporary build directory with various test files.""" + """Create a temporary build directory with index.html and assets/ directory.""" with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Create index.html (temp_path / "index.html").write_text("Test") - # Create some static assets - (temp_path / "static").mkdir() - (temp_path / "static" / "app.js").write_text("console.log('test');") - (temp_path / "static" / "style.css").write_text("body { color: black; }") + # Create assets directory and some files inside it + assets_dir = temp_path / "assets" + assets_dir.mkdir() + (assets_dir / "app.js").write_text("console.log('test');") + (assets_dir / "style.css").write_text("body { color: black; }") - # Create a nested directory structure - (temp_path / "nested").mkdir() - (temp_path / "nested" / "file.txt").write_text("nested content") + # Optionally, create a nested directory inside assets + (assets_dir / "nested").mkdir() + (assets_dir / "nested" / "file.txt").write_text("nested content") yield temp_path @@ -407,11 +408,11 @@ def test_static_file_serving(self, temp_build_dir_with_files): assert "Test" in response.text # Test serving static files - response = client.get("/static/app.js") + response = client.get("/assets/app.js") assert response.status_code == 200 assert "console.log('test')" in response.text - response = client.get("/static/style.css") + response = client.get("/assets/style.css") assert response.status_code == 200 assert "color: black" in response.text @@ -442,7 +443,7 @@ def test_health_endpoint(self, temp_build_dir_with_files): response = client.get("/health") assert response.status_code == 200 data = response.json() - assert data["status"] == "healthy" + assert data["status"] == "ok" @pytest.mark.asyncio From b10d403dcb2a60765778270db54173b0d6a36b45 Mon Sep 17 00:00:00 2001 From: Dylan Huang Date: Mon, 11 Aug 2025 11:15:51 -0700 Subject: [PATCH 7/9] add logs server tests --- tests/test_vite_server.py | 224 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 tests/test_vite_server.py diff --git a/tests/test_vite_server.py b/tests/test_vite_server.py new file mode 100644 index 00000000..eb4a59df --- /dev/null +++ b/tests/test_vite_server.py @@ -0,0 +1,224 @@ +import tempfile +from pathlib import Path + +import pytest +from fastapi.testclient import TestClient + +from eval_protocol.utils.vite_server import ViteServer + + +class TestViteServer: + """Test ViteServer class.""" + + @pytest.fixture + def temp_build_dir_with_files(self): + """Create a temporary build directory with index.html and assets/ directory.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create index.html + (temp_path / "index.html").write_text("Test") + + # Create assets directory and some files inside it + assets_dir = temp_path / "assets" + assets_dir.mkdir() + (assets_dir / "app.js").write_text("console.log('test');") + (assets_dir / "style.css").write_text("body { color: black; }") + + # Optionally, create a nested directory inside assets + (assets_dir / "nested").mkdir() + (assets_dir / "nested" / "file.txt").write_text("nested content") + + yield temp_path + + def test_initialization(self, temp_build_dir_with_files): + """Test ViteServer initialization.""" + vite_server = ViteServer(build_dir=str(temp_build_dir_with_files), host="localhost", port=8000) + + assert vite_server.build_dir == temp_build_dir_with_files + assert vite_server.host == "localhost" + assert vite_server.port == 8000 + assert vite_server.index_file == "index.html" + assert vite_server.app is not None + + def test_initialization_invalid_build_dir(self): + """Test ViteServer initialization with invalid build directory.""" + with pytest.raises(FileNotFoundError): + ViteServer(build_dir="nonexistent_dir") + + def test_initialization_invalid_index_file(self, temp_build_dir_with_files): + """Test ViteServer initialization with invalid index file.""" + # Remove the index.html file + (temp_build_dir_with_files / "index.html").unlink() + + with pytest.raises(FileNotFoundError): + ViteServer(build_dir=str(temp_build_dir_with_files)) + + def test_html_injection_in_vite_server(self, temp_build_dir_with_files): + """Test that ViteServer injects server configuration into HTML.""" + # Create a more complex HTML file for testing injection + index_html = """ + + + + + Test App + + + +
Test Application
+ + +""" + + # Write the test HTML + (temp_build_dir_with_files / "index.html").write_text(index_html) + + # Create ViteServer instance + vite_server = ViteServer(build_dir=str(temp_build_dir_with_files), host="localhost", port=8000) + + # Test the HTML injection method directly + injected_html = vite_server._inject_config_into_html(index_html) + + # Verify server configuration is injected + assert "window.SERVER_CONFIG" in injected_html + assert 'host: "localhost"' in injected_html + assert 'port: "8000"' in injected_html + assert 'protocol: "ws"' in injected_html + assert 'apiProtocol: "http"' in injected_html + + # Verify injection happens before closing tag + head_end_index = injected_html.find("") + config_script_index = injected_html.find("window.SERVER_CONFIG") + assert config_script_index < head_end_index + + # Verify the original HTML structure is preserved + assert '
Test Application
' in injected_html + assert '' in injected_html + + # Test that the injected config is valid JavaScript + assert "window.SERVER_CONFIG = {" in injected_html + assert "};" in injected_html + + def test_html_injection_without_head_tag(self, temp_build_dir_with_files): + """Test HTML injection when no tag is present.""" + # Create HTML without tag + simple_html = """ + + +

Simple App

+

No head tag

+ +""" + + (temp_build_dir_with_files / "index.html").write_text(simple_html) + + vite_server = ViteServer(build_dir=str(temp_build_dir_with_files), host="127.0.0.1", port=9000) + + injected_html = vite_server._inject_config_into_html(simple_html) + + # Verify config is injected at the beginnin + assert injected_html.strip().startswith("