diff --git a/.env b/.env deleted file mode 100644 index ba7cc18..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -GENERATE_SOURCEMAP=false diff --git a/.gitignore b/.gitignore index 5339fb1..2430dc2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,9 +10,11 @@ # production /build +/dist # misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/.whitesource b/.whitesource deleted file mode 100644 index 9c7ae90..0000000 --- a/.whitesource +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scanSettings": { - "baseBranches": [] - }, - "checkRunSettings": { - "vulnerableCheckRunConclusionLevel": "failure", - "displayMode": "diff", - "useMendCheckNames": true - }, - "issueSettings": { - "minSeverityLevel": "LOW", - "issueType": "DEPENDENCY" - } -} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4728c40 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,85 @@ +# EDDI Chat UI — AI Agent Guidelines + +> **This file is loaded by AI coding assistants. Follow ALL rules below.** + +## 1. Project Context + +**eddi-chat-ui** is a standalone React 19 chat widget for [EDDI](https://github.com/labsai/EDDI) agents. Built with Vite + TypeScript 5.7, vanilla CSS with CSS custom properties, and `react-markdown` for rich message rendering. + +### Tech Stack + +| Technology | Version | Purpose | +| -------------- | ------- | ------------------------------ | +| React | 19 | UI framework | +| TypeScript | 5.7 | Type safety | +| Vite | 6 | Build tool + dev server | +| Vitest | 3.x | Unit testing (jsdom) | +| react-markdown | 9.x | Markdown rendering in messages | + +### Ecosystem + +- **EDDI backend** — Quarkus REST API at `/agents/{env}/{agentId}`, serves chat UI from `META-INF/resources/` +- **EDDI Manager** — Admin UI with embedded chat panel, shares API patterns +- All repos at `c:\dev\git\` + +--- + +## 2. Project Structure + +``` +src/ +├── api/ # API layer +│ ├── chat-api.ts # Pure fetch functions (start, read, send, stream, undo, redo) +│ └── demo-api.ts # Mock API for demo mode +├── components/ # UI components +│ ├── ChatWidget.tsx # Main orchestrator (lifecycle, SSE, query params) +│ ├── ChatHeader.tsx # Logo/title, undo/redo, theme toggle, new conversation +│ ├── MessageBubble.tsx # User/agent messages with markdown +│ ├── ChatInput.tsx # Auto-grow textarea, Enter/Shift+Enter +│ ├── QuickReplies.tsx # Pill buttons for suggested replies +│ ├── Indicators.tsx # Typing (dots) + Thinking (brain) indicators +│ └── ScrollToAgenttom.tsx # Floating scroll button +├── hooks/ +│ └── useTheme.ts # Dark/light/system theme with localStorage +├── store/ +│ └── chat-store.tsx # Context + useReducer state management +├── styles/ +│ ├── variables.css # CSS custom properties (dark/light tokens) +│ └── chat.css # All component styles (BEM naming) +└── types.ts # Shared TypeScript types +``` + +--- + +## 3. Key Conventions + +1. **CSS** — BEM naming: `.chat-header__logo`, `.message--user`, `.quick-replies__btn`. No Tailwind. +2. **State** — `ChatProvider` → `useChatState()` / `useChatDispatch()`. No prop drilling. +3. **API** — Pure `fetch` in `chat-api.ts`. SSE streaming uses `AsyncGenerator`. +4. **Testing** — Wrap components in ``. Mock `window.matchMedia` in `test-setup.ts`. +5. **Demo mode** — `/chat/demo/showcase` uses `demo-api.ts`. Check with `isDemoMode()`. +6. **Query params** — `hideUndo`, `hideRedo`, `hideNewConversation`, `hideLogo`, `theme`, `title`. + +--- + +## 4. Build & Deploy + +Production build goes to **EDDI backend** at `src/main/resources/META-INF/resources/`: + +```bash +npm run build # Outputs to EDDI backend resources +``` + +--- + +## 5. Mandatory Workflow + +1. **Before work**: `git status`, read this file + any `changelog.md` +2. **During work**: Commit with `feat(chat-ui):` / `fix(chat-ui):`. Each commit must build. +3. **After work**: Update `changelog.md` + +### DO NOT + +- Add external state libraries (zustand, redux) — use Context + useReducer +- Add CSS frameworks — use CSS custom properties +- Duplicate logic from `demo-api.ts` into components diff --git a/README.md b/README.md index b1fb14d..aa26b85 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,100 @@ -# Getting Started with EDDI Chat UI - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.\ -Open `http://localhost:3000/chat/managedbots/:intent/:userId` or `http://localhost:3000/chat/:environment/:botId?userId=:userId` to view it in your browser. - -The page will reload when you make changes.\ -You may also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can't go back!** - -If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. - -You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) - -### Analyzing the Bundle Size - -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) - -### Making a Progressive Web App - -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) - -### Advanced Configuration - -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) - -### Deployment - -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) - -### `npm run build` fails to minify - -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) +# EDDI Chat UI + +A standalone, configurable chat widget for [EDDI](https://github.com/labsai/EDDI) conversational AI agents. Built with React 19, TypeScript, and Vite. + +## Features + +- 💬 **Rich markdown** — Tables, code blocks, bold/italic, links, lists +- 🌊 **SSE streaming** — Real-time token-by-token agent responses with thinking indicator +- 🎨 **Dark/Light themes** — Toggle via UI or query parameters +- ⚡ **Quick replies** — Pill buttons for suggested responses +- ↩↪ **Undo/Redo** — Step through conversation history +- 🔧 **Configurable** — All features togglable via URL query parameters +- 📱 **Responsive** — Mobile-first design with adaptive breakpoints +- 🎭 **Demo mode** — Full showcase without a running backend + +## Quick Start + +```bash +npm install +npm run dev # http://localhost:5174 +``` + +### URL Patterns + +| URL | Description | +| ------------------------------------- | --------------------------- | +| `/chat/:environment/:agentId` | Connect to a specific agent | +| `/chat/demo/showcase` | Demo mode with mock data | +| `/chat/managedagents/:intent/:userId` | Managed agent mode | + +### Query Parameters + +| Parameter | Example | Effect | +| --------------------- | --------------------------- | ---------------------------------- | +| `hideUndo` | `?hideUndo=true` | Hide undo button | +| `hideRedo` | `?hideRedo=true` | Hide redo button | +| `hideNewConversation` | `?hideNewConversation=true` | Hide restart button | +| `hideLogo` | `?hideLogo=true` | Show text title instead of logo | +| `hideQuickReplies` | `?hideQuickReplies=true` | Hide quick reply buttons | +| `theme` | `?theme=light` | Set initial theme (`dark`/`light`) | +| `title` | `?title=My%20Agent` | Override header title | + +## Development + +```bash +npm run dev # Dev server (port 5174) with proxy to EDDI backend +npm run build # Production build +npx vitest run # Run tests (42 tests) +npx tsc --noEmit # Type check +``` + +### Project Structure + +``` +src/ +├── api/ # API layer (fetch + SSE streaming) +│ ├── chat-api.ts # Real EDDI backend API +│ └── demo-api.ts # Mock API for demo mode +├── components/ # React components +│ ├── ChatWidget.tsx # Main orchestrator +│ ├── ChatHeader.tsx # Logo, actions, theme toggle +│ ├── MessageBubble.tsx # Message rendering with Markdown +│ ├── ChatInput.tsx # Auto-grow textarea + send +│ ├── QuickReplies.tsx # Suggested reply pills +│ ├── Indicators.tsx # Typing + Thinking indicators +│ └── ScrollToAgenttom.tsx +├── hooks/ +│ └── useTheme.ts # Theme management +├── store/ +│ └── chat-store.tsx # Context + useReducer +├── styles/ +│ ├── variables.css # CSS custom properties (design tokens) +│ └── chat.css # Component styles (BEM naming) +└── types.ts # Shared types +``` + +## Embedding + +The chat UI can be embedded in any HTML page via iframe: + +```html + +``` + +## Backend Integration + +The production build is deployed into the EDDI Quarkus backend at: + +``` +EDDI/src/main/resources/META-INF/resources/ +``` + +This makes the chat UI available at `http://your-eddi-server/chat.html`. + +## License + +Part of the EDDI project — see [EDDI License](https://github.com/labsai/EDDI/blob/master/LICENSE). diff --git a/chat.html b/chat.html new file mode 100644 index 0000000..518f21e --- /dev/null +++ b/chat.html @@ -0,0 +1,44 @@ + + + + + + + + EDDI Chat + + + + + + + +
+ + + diff --git a/dist/assets/index-BCEfKpWy.css b/dist/assets/index-BCEfKpWy.css new file mode 100644 index 0000000..83e21e0 --- /dev/null +++ b/dist/assets/index-BCEfKpWy.css @@ -0,0 +1 @@ +:root,[data-theme=dark]{--chat-bg: #0a0a0a;--chat-surface: #141414;--chat-surface-raised: #1e1e1e;--chat-text: #f1f1f1;--chat-text-muted: #999;--chat-text-accent: #cba135;--chat-accent: #113B92;--chat-accent-hover: #1a50b5;--chat-accent-soft: rgba(17, 59, 146, .15);--chat-bot-bg: #1e1e1e;--chat-bot-border: #2a2a2a;--chat-bot-text: #f1f1f1;--chat-user-bg: #113B92;--chat-user-text: #ffffff;--chat-input-bg: #1a1a1a;--chat-input-border: #333;--chat-input-focus: #113B92;--chat-input-text: #f1f1f1;--chat-input-placeholder: #666;--chat-qr-bg: rgba(17, 59, 146, .18);--chat-qr-border: rgba(17, 59, 146, .4);--chat-qr-text: #7eaaff;--chat-qr-hover: rgba(17, 59, 146, .35);--chat-border: #222;--chat-radius: 16px;--chat-radius-sm: 8px;--chat-font: "Noto Sans", system-ui, -apple-system, sans-serif;--chat-shadow: 0 2px 12px rgba(0, 0, 0, .4);--chat-scrollbar-thumb: #444;--chat-scrollbar-track: transparent}[data-theme=light]{--chat-bg: #f5f5f5;--chat-surface: #ffffff;--chat-surface-raised: #fafafa;--chat-text: #1a1a1a;--chat-text-muted: #666;--chat-text-accent: #8b6914;--chat-accent: #113B92;--chat-accent-hover: #0d2d6b;--chat-accent-soft: rgba(17, 59, 146, .08);--chat-bot-bg: #ffffff;--chat-bot-border: #e0e0e0;--chat-bot-text: #1a1a1a;--chat-user-bg: #113B92;--chat-user-text: #ffffff;--chat-input-bg: #ffffff;--chat-input-border: #d0d0d0;--chat-input-focus: #113B92;--chat-input-text: #1a1a1a;--chat-input-placeholder: #999;--chat-qr-bg: rgba(17, 59, 146, .06);--chat-qr-border: rgba(17, 59, 146, .25);--chat-qr-text: #113B92;--chat-qr-hover: rgba(17, 59, 146, .14);--chat-border: #e0e0e0;--chat-shadow: 0 2px 12px rgba(0, 0, 0, .08);--chat-scrollbar-thumb: #ccc;--chat-scrollbar-track: transparent}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html,body,#root{height:100%;overflow:hidden}body{background-color:var(--chat-bg);color:var(--chat-text);font-family:var(--chat-font);font-weight:400;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{color:var(--chat-text-accent)}a:hover{opacity:.85}.chat-root{display:flex;flex-direction:column;height:100%;max-width:860px;margin:0 auto;background-color:var(--chat-bg)}.chat-header{display:flex;align-items:center;gap:12px;padding:12px 20px;border-bottom:1px solid var(--chat-border);background:var(--chat-surface);flex-shrink:0}.chat-header__logo{height:36px;object-fit:contain}.chat-header__title{flex:1;font-size:1rem;font-weight:500;color:var(--chat-text);letter-spacing:.02em}.chat-header__actions{display:flex;align-items:center;gap:6px}.chat-header__btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--chat-radius-sm);background:transparent;color:var(--chat-text-muted);cursor:pointer;transition:background .15s,color .15s;font-size:1.1rem}.chat-header__btn:hover{background:var(--chat-accent-soft);color:var(--chat-text)}.chat-messages{flex:1;overflow-y:auto;padding:16px 12px;display:flex;flex-direction:column;gap:4px}.chat-messages::-webkit-scrollbar{width:5px}.chat-messages::-webkit-scrollbar-thumb{background:var(--chat-scrollbar-thumb);border-radius:4px}.chat-messages::-webkit-scrollbar-track{background:var(--chat-scrollbar-track)}.message{display:flex;gap:10px;padding:4px 8px;max-width:85%;animation:msgIn .25s ease-out}@keyframes msgIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.message--user{align-self:flex-end;flex-direction:row-reverse}.message--bot{align-self:flex-start}.message__avatar{flex-shrink:0;width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:600}.message--user .message__avatar{background:var(--chat-accent);color:var(--chat-user-text)}.message--bot .message__avatar{background:var(--chat-surface-raised);color:var(--chat-text-accent);border:1px solid var(--chat-bot-border)}.message__bubble{border-radius:var(--chat-radius);padding:10px 16px;font-size:.925rem;line-height:1.6;word-break:break-word}.message--user .message__bubble{background:var(--chat-user-bg);color:var(--chat-user-text);border-bottom-right-radius:4px}.message--bot .message__bubble{background:var(--chat-bot-bg);color:var(--chat-bot-text);border:1px solid var(--chat-bot-border);border-bottom-left-radius:4px}.message__bubble .markdown-body{font-size:inherit;line-height:inherit;color:inherit}.message__bubble .markdown-body p{margin:.4em 0}.message__bubble .markdown-body p:first-child{margin-top:0}.message__bubble .markdown-body p:last-child{margin-bottom:0}.message__bubble .markdown-body pre{background:var(--chat-surface);border:1px solid var(--chat-border);border-radius:var(--chat-radius-sm);padding:12px;overflow-x:auto;margin:8px 0;font-size:.85rem}.message__bubble .markdown-body code{background:var(--chat-surface);border-radius:3px;padding:1px 5px;font-size:.85em;font-family:Noto Sans Mono,ui-monospace,monospace}.message__bubble .markdown-body pre code{background:none;padding:0}.message__bubble .markdown-body ul,.message__bubble .markdown-body ol{padding-left:1.4em;margin:.4em 0}.message__bubble .markdown-body table{border-collapse:collapse;margin:8px 0;width:100%}.message__bubble .markdown-body th,.message__bubble .markdown-body td{border:1px solid var(--chat-border);padding:6px 10px;text-align:left;font-size:.85rem}.message__bubble .markdown-body th{background:var(--chat-surface-raised);font-weight:500}.message__bubble .markdown-body blockquote{border-left:3px solid var(--chat-accent);padding-left:12px;margin:8px 0;color:var(--chat-text-muted)}.indicator{display:flex;gap:10px;padding:4px 8px;align-self:flex-start;animation:msgIn .25s ease-out}.indicator__bubble{background:var(--chat-bot-bg);border:1px solid var(--chat-bot-border);border-radius:var(--chat-radius);border-bottom-left-radius:4px;padding:12px 18px;display:flex;align-items:center;gap:8px}.indicator__dots{display:flex;align-items:center;gap:4px}.indicator__dot{width:7px;height:7px;border-radius:50%;background:var(--chat-text-muted);animation:bounce 1.2s infinite}.indicator__dot:nth-child(2){animation-delay:.15s}.indicator__dot:nth-child(3){animation-delay:.3s}@keyframes bounce{0%,60%,to{transform:translateY(0)}30%{transform:translateY(-6px)}}.indicator__thinking{display:flex;align-items:center;gap:8px;color:var(--chat-text-muted);font-size:.85rem;font-style:italic}.indicator__brain{font-size:1rem;animation:pulse 1.5s ease-in-out infinite}@keyframes pulse{0%,to{opacity:.4}50%{opacity:1}}.quick-replies{display:flex;flex-wrap:wrap;gap:8px;padding:8px 20px 12px;animation:msgIn .2s ease-out}.quick-replies__btn{padding:8px 16px;font-size:.875rem;font-family:var(--chat-font);font-weight:400;border:1px solid var(--chat-qr-border);border-radius:20px;background:var(--chat-qr-bg);color:var(--chat-qr-text);cursor:pointer;transition:background .15s,border-color .15s,transform .1s;white-space:nowrap}.quick-replies__btn:hover{background:var(--chat-qr-hover);border-color:var(--chat-qr-text)}.quick-replies__btn:active{transform:scale(.97)}.chat-input{display:flex;align-items:flex-end;gap:10px;padding:12px 16px 16px;border-top:1px solid var(--chat-border);background:var(--chat-surface)}.chat-input__textarea{flex:1;resize:none;min-height:44px;max-height:150px;padding:10px 14px;font-size:.925rem;font-family:var(--chat-font);line-height:1.5;color:var(--chat-input-text);background:var(--chat-input-bg);border:1px solid var(--chat-input-border);border-radius:14px;outline:none;transition:border-color .15s,box-shadow .15s}.chat-input__textarea::placeholder{color:var(--chat-input-placeholder)}.chat-input__textarea:focus{border-color:var(--chat-input-focus);box-shadow:0 0 0 2px var(--chat-accent-soft)}.chat-input__send{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:44px;height:44px;border:none;border-radius:14px;font-size:1.2rem;cursor:pointer;transition:background .15s,transform .1s}.chat-input__send--active{background:var(--chat-accent);color:var(--chat-user-text)}.chat-input__send--active:hover{background:var(--chat-accent-hover)}.chat-input__send--active:active{transform:scale(.95)}.chat-input__send--disabled{background:var(--chat-surface-raised);color:var(--chat-text-muted);cursor:not-allowed}.chat-input__spinner{width:18px;height:18px;border:2px solid transparent;border-top-color:currentColor;border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.scroll-to-bottom{position:absolute;bottom:90px;left:50%;transform:translate(-50%);width:38px;height:38px;border:1px solid var(--chat-border);border-radius:50%;background:var(--chat-surface);color:var(--chat-text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:1.1rem;box-shadow:var(--chat-shadow);transition:background .15s,color .15s;z-index:10;animation:fadeIn .2s ease-out}.scroll-to-bottom:hover{background:var(--chat-surface-raised);color:var(--chat-text)}@keyframes fadeIn{0%{opacity:0;transform:translate(-50%) translateY(8px)}to{opacity:1;transform:translate(-50%) translateY(0)}}.chat-ended{display:flex;flex-direction:column;align-items:center;gap:12px;padding:20px;border-top:1px solid var(--chat-border);background:var(--chat-surface);text-align:center}.chat-ended__label{font-size:.9rem;color:var(--chat-text-muted);font-weight:500}.chat-ended__restart{padding:8px 24px;font-size:.875rem;font-family:var(--chat-font);font-weight:500;border:1px solid var(--chat-accent);border-radius:20px;background:var(--chat-accent-soft);color:var(--chat-qr-text);cursor:pointer;transition:background .15s,transform .1s}.chat-ended__restart:hover{background:var(--chat-accent);color:var(--chat-user-text)}.chat-ended__restart:active{transform:scale(.97)}.chat-empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;color:var(--chat-text-muted);text-align:center;padding:40px 20px}.chat-empty__icon{font-size:3rem;opacity:.3}.chat-empty__text{font-size:.95rem}@media(max-width:768px){.chat-root{max-width:100%}.chat-header{padding:10px 14px}.chat-header__logo{height:28px}.chat-messages{padding:12px 8px}.message{max-width:90%}.chat-input{padding:10px 12px 14px}.quick-replies{padding:8px 12px 10px}}@media(max-width:480px){.message__avatar{width:26px;height:26px;font-size:.65rem}.message__bubble{padding:8px 12px;font-size:.875rem}.chat-input__textarea{font-size:.875rem;padding:8px 12px}.chat-input__send{width:40px;height:40px}.quick-replies__btn{font-size:.8rem;padding:6px 12px}} diff --git a/dist/assets/index-CSxR_bJz.js b/dist/assets/index-CSxR_bJz.js new file mode 100644 index 0000000..7d09532 --- /dev/null +++ b/dist/assets/index-CSxR_bJz.js @@ -0,0 +1,92 @@ +(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const c of s)if(c.type==="childList")for(const o of c.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function a(s){const c={};return s.integrity&&(c.integrity=s.integrity),s.referrerPolicy&&(c.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?c.credentials="include":s.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function r(s){if(s.ep)return;s.ep=!0;const c=a(s);fetch(s.href,c)}})();function Pp(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Qo={exports:{}},ur={};/** + * @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 m1;function f2(){if(m1)return ur;m1=1;var e=Symbol.for("react.transitional.element"),n=Symbol.for("react.fragment");function a(r,s,c){var o=null;if(c!==void 0&&(o=""+c),s.key!==void 0&&(o=""+s.key),"key"in s){c={};for(var m in s)m!=="key"&&(c[m]=s[m])}else c=s;return s=c.ref,{$$typeof:e,type:r,key:o,ref:s!==void 0?s:null,props:c}}return ur.Fragment=n,ur.jsx=a,ur.jsxs=a,ur}var p1;function h2(){return p1||(p1=1,Qo.exports=f2()),Qo.exports}var fe=h2(),Vo={exports:{}},Ne={};/** + * @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 E1;function d2(){if(E1)return Ne;E1=1;var e=Symbol.for("react.transitional.element"),n=Symbol.for("react.portal"),a=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),c=Symbol.for("react.consumer"),o=Symbol.for("react.context"),m=Symbol.for("react.forward_ref"),E=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),T=Symbol.for("react.lazy"),g=Symbol.for("react.activity"),A=Symbol.iterator;function y(L){return L===null||typeof L!="object"?null:(L=A&&L[A]||L["@@iterator"],typeof L=="function"?L:null)}var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},I=Object.assign,w={};function k(L,K,C){this.props=L,this.context=K,this.refs=w,this.updater=C||x}k.prototype.isReactComponent={},k.prototype.setState=function(L,K){if(typeof L!="object"&&typeof L!="function"&&L!=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,L,K,"setState")},k.prototype.forceUpdate=function(L){this.updater.enqueueForceUpdate(this,L,"forceUpdate")};function V(){}V.prototype=k.prototype;function Q(L,K,C){this.props=L,this.context=K,this.refs=w,this.updater=C||x}var se=Q.prototype=new V;se.constructor=Q,I(se,k.prototype),se.isPureReactComponent=!0;var he=Array.isArray;function j(){}var ue={H:null,A:null,T:null,S:null},ye=Object.prototype.hasOwnProperty;function ie(L,K,C){var oe=C.ref;return{$$typeof:e,type:L,key:K,ref:oe!==void 0?oe:null,props:C}}function P(L,K){return ie(L.type,K,L.props)}function re(L){return typeof L=="object"&&L!==null&&L.$$typeof===e}function ae(L){var K={"=":"=0",":":"=2"};return"$"+L.replace(/[=:]/g,function(C){return K[C]})}var de=/\/+/g;function ce(L,K){return typeof L=="object"&&L!==null&&L.key!=null?ae(""+L.key):K.toString(36)}function me(L){switch(L.status){case"fulfilled":return L.value;case"rejected":throw L.reason;default:switch(typeof L.status=="string"?L.then(j,j):(L.status="pending",L.then(function(K){L.status==="pending"&&(L.status="fulfilled",L.value=K)},function(K){L.status==="pending"&&(L.status="rejected",L.reason=K)})),L.status){case"fulfilled":return L.value;case"rejected":throw L.reason}}throw L}function Y(L,K,C,oe,Se){var Te=typeof L;(Te==="undefined"||Te==="boolean")&&(L=null);var we=!1;if(L===null)we=!0;else switch(Te){case"bigint":case"string":case"number":we=!0;break;case"object":switch(L.$$typeof){case e:case n:we=!0;break;case T:return we=L._init,Y(we(L._payload),K,C,oe,Se)}}if(we)return Se=Se(L),we=oe===""?"."+ce(L,0):oe,he(Se)?(C="",we!=null&&(C=we.replace(de,"$&/")+"/"),Y(Se,K,C,"",function(Gt){return Gt})):Se!=null&&(re(Se)&&(Se=P(Se,C+(Se.key==null||L&&L.key===Se.key?"":(""+Se.key).replace(de,"$&/")+"/")+we)),K.push(Se)),1;we=0;var tt=oe===""?".":oe+":";if(he(L))for(var Qe=0;Qe>>1,R=Y[Le];if(0>>1;Les(C,ge))oes(Se,C)?(Y[Le]=Se,Y[oe]=ge,Le=oe):(Y[Le]=C,Y[K]=ge,Le=K);else if(oes(Se,ge))Y[Le]=Se,Y[oe]=ge,Le=oe;else break e}}return te}function s(Y,te){var ge=Y.sortIndex-te.sortIndex;return ge!==0?ge:Y.id-te.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var c=performance;e.unstable_now=function(){return c.now()}}else{var o=Date,m=o.now();e.unstable_now=function(){return o.now()-m}}var E=[],p=[],T=1,g=null,A=3,y=!1,x=!1,I=!1,w=!1,k=typeof setTimeout=="function"?setTimeout:null,V=typeof clearTimeout=="function"?clearTimeout:null,Q=typeof setImmediate<"u"?setImmediate:null;function se(Y){for(var te=a(p);te!==null;){if(te.callback===null)r(p);else if(te.startTime<=Y)r(p),te.sortIndex=te.expirationTime,n(E,te);else break;te=a(p)}}function he(Y){if(I=!1,se(Y),!x)if(a(E)!==null)x=!0,j||(j=!0,ae());else{var te=a(p);te!==null&&me(he,te.startTime-Y)}}var j=!1,ue=-1,ye=5,ie=-1;function P(){return w?!0:!(e.unstable_now()-ieY&&P());){var Le=g.callback;if(typeof Le=="function"){g.callback=null,A=g.priorityLevel;var R=Le(g.expirationTime<=Y);if(Y=e.unstable_now(),typeof R=="function"){g.callback=R,se(Y),te=!0;break t}g===a(E)&&r(E),se(Y)}else r(E);g=a(E)}if(g!==null)te=!0;else{var L=a(p);L!==null&&me(he,L.startTime-Y),te=!1}}break e}finally{g=null,A=ge,y=!1}te=void 0}}finally{te?ae():j=!1}}}var ae;if(typeof Q=="function")ae=function(){Q(re)};else if(typeof MessageChannel<"u"){var de=new MessageChannel,ce=de.port2;de.port1.onmessage=re,ae=function(){ce.postMessage(null)}}else ae=function(){k(re,0)};function me(Y,te){ue=k(function(){Y(e.unstable_now())},te)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(Y){Y.callback=null},e.unstable_forceFrameRate=function(Y){0>Y||125Le?(Y.sortIndex=ge,n(p,Y),a(E)===null&&Y===a(p)&&(I?(V(ue),ue=-1):I=!0,me(he,ge-Le))):(Y.sortIndex=R,n(E,Y),x||y||(x=!0,j||(j=!0,ae()))),Y},e.unstable_shouldYield=P,e.unstable_wrapCallback=function(Y){var te=A;return function(){var ge=A;A=te;try{return Y.apply(this,arguments)}finally{A=ge}}}})(Zo)),Zo}var b1;function p2(){return b1||(b1=1,Ko.exports=m2()),Ko.exports}var Jo={exports:{}},Rt={};/** + * @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 y1;function E2(){if(y1)return Rt;y1=1;var e=qf();function n(E){var p="https://react.dev/errors/"+E;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(n){console.error(n)}}return e(),Jo.exports=E2(),Jo.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 A1;function T2(){if(A1)return ar;A1=1;var e=p2(),n=qf(),a=g2();function r(t){var u="https://react.dev/errors/"+t;if(1R||(t.current=Le[R],Le[R]=null,R--)}function C(t,u){R++,Le[R]=t.current,t.current=u}var oe=L(null),Se=L(null),Te=L(null),we=L(null);function tt(t,u){switch(C(Te,u),C(Se,t),C(oe,null),u.nodeType){case 9:case 11:t=(t=u.documentElement)&&(t=t.namespaceURI)?Um(t):0;break;default:if(t=u.tagName,u=u.namespaceURI)u=Um(u),t=Hm(u,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}K(oe),C(oe,t)}function Qe(){K(oe),K(Se),K(Te)}function Gt(t){t.memoizedState!==null&&C(we,t);var u=oe.current,i=Hm(u,t.type);u!==i&&(C(Se,t),C(oe,i))}function iu(t){Se.current===t&&(K(oe),K(Se)),we.current===t&&(K(we),$i._currentValue=ge)}var si,kr;function xn(t){if(si===void 0)try{throw Error()}catch(i){var u=i.stack.trim().match(/\n( *(at )?)/);si=u&&u[1]||"",kr=-1)":-1f||D[l]!==H[f]){var X=` +`+D[l].replace(" at new "," at ");return t.displayName&&X.includes("")&&(X=X.replace("",t.displayName)),X}while(1<=l&&0<=f);break}}}finally{ra=!1,Error.prepareStackTrace=i}return(i=t?t.displayName||t.name:"")?xn(i):""}function Ls(t,u){switch(t.tag){case 26:case 27:case 5:return xn(t.type);case 16:return xn("Lazy");case 13:return t.child!==u&&u!==null?xn("Suspense Fallback"):xn("Suspense");case 19:return xn("SuspenseList");case 0:case 15:return la(t.type,!1);case 11:return la(t.type.render,!1);case 1:return la(t.type,!0);case 31:return xn("Activity");default:return""}}function ci(t){try{var u="",i=null;do u+=Ls(t,i),i=t,t=t.return;while(t);return u}catch(l){return` +Error generating stack: `+l.message+` +`+l.stack}}var sa=Object.prototype.hasOwnProperty,oi=e.unstable_scheduleCallback,fi=e.unstable_cancelCallback,vs=e.unstable_shouldYield,ks=e.unstable_requestPaint,kt=e.unstable_now,J=e.unstable_getCurrentPriorityLevel,le=e.unstable_ImmediatePriority,Ce=e.unstable_UserBlockingPriority,De=e.unstable_NormalPriority,qe=e.unstable_LowPriority,wt=e.unstable_IdlePriority,Un=e.log,gn=e.unstable_setDisableYieldValue,Tn=null,gt=null;function it(t){if(typeof Un=="function"&&gn(t),gt&&typeof gt.setStrictMode=="function")try{gt.setStrictMode(Tn,t)}catch{}}var ft=Math.clz32?Math.clz32:Jg,bn=Math.log,Zg=Math.LN2;function Jg(t){return t>>>=0,t===0?32:31-(bn(t)/Zg|0)|0}var Ir=256,Mr=262144,wr=4194304;function Bu(t){var u=t&42;if(u!==0)return u;switch(t&-t){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:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function Br(t,u,i){var l=t.pendingLanes;if(l===0)return 0;var f=0,h=t.suspendedLanes,b=t.pingedLanes;t=t.warmLanes;var _=l&134217727;return _!==0?(l=_&~h,l!==0?f=Bu(l):(b&=_,b!==0?f=Bu(b):i||(i=_&~t,i!==0&&(f=Bu(i))))):(_=l&~h,_!==0?f=Bu(_):b!==0?f=Bu(b):i||(i=l&~t,i!==0&&(f=Bu(i)))),f===0?0:u!==0&&u!==f&&(u&h)===0&&(h=f&-f,i=u&-u,h>=i||h===32&&(i&4194048)!==0)?u:f}function hi(t,u){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&u)===0}function Wg(t,u){switch(t){case 1:case 2:case 4:case 8:case 64:return u+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 u+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 T0(){var t=wr;return wr<<=1,(wr&62914560)===0&&(wr=4194304),t}function Is(t){for(var u=[],i=0;31>i;i++)u.push(t);return u}function di(t,u){t.pendingLanes|=u,u!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function $g(t,u,i,l,f,h){var b=t.pendingLanes;t.pendingLanes=i,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=i,t.entangledLanes&=i,t.errorRecoveryDisabledLanes&=i,t.shellSuspendCounter=0;var _=t.entanglements,D=t.expirationTimes,H=t.hiddenUpdates;for(i=b&~i;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var iT=/[\n"\\]/g;function nn(t){return t.replace(iT,function(u){return"\\"+u.charCodeAt(0).toString(16)+" "})}function Ps(t,u,i,l,f,h,b,_){t.name="",b!=null&&typeof b!="function"&&typeof b!="symbol"&&typeof b!="boolean"?t.type=b:t.removeAttribute("type"),u!=null?b==="number"?(u===0&&t.value===""||t.value!=u)&&(t.value=""+tn(u)):t.value!==""+tn(u)&&(t.value=""+tn(u)):b!=="submit"&&b!=="reset"||t.removeAttribute("value"),u!=null?zs(t,b,tn(u)):i!=null?zs(t,b,tn(i)):l!=null&&t.removeAttribute("value"),f==null&&h!=null&&(t.defaultChecked=!!h),f!=null&&(t.checked=f&&typeof f!="function"&&typeof f!="symbol"),_!=null&&typeof _!="function"&&typeof _!="symbol"&&typeof _!="boolean"?t.name=""+tn(_):t.removeAttribute("name")}function v0(t,u,i,l,f,h,b,_){if(h!=null&&typeof h!="function"&&typeof h!="symbol"&&typeof h!="boolean"&&(t.type=h),u!=null||i!=null){if(!(h!=="submit"&&h!=="reset"||u!=null)){Hs(t);return}i=i!=null?""+tn(i):"",u=u!=null?""+tn(u):i,_||u===t.value||(t.value=u),t.defaultValue=u}l=l??f,l=typeof l!="function"&&typeof l!="symbol"&&!!l,t.checked=_?t.checked:!!l,t.defaultChecked=!!l,b!=null&&typeof b!="function"&&typeof b!="symbol"&&typeof b!="boolean"&&(t.name=b),Hs(t)}function zs(t,u,i){u==="number"&&Pr(t.ownerDocument)===t||t.defaultValue===""+i||(t.defaultValue=""+i)}function ma(t,u,i,l){if(t=t.options,u){u={};for(var f=0;f"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Gs=!1;if(zn)try{var gi={};Object.defineProperty(gi,"passive",{get:function(){Gs=!0}}),window.addEventListener("test",gi,gi),window.removeEventListener("test",gi,gi)}catch{Gs=!1}var lu=null,Qs=null,Fr=null;function H0(){if(Fr)return Fr;var t,u=Qs,i=u.length,l,f="value"in lu?lu.value:lu.textContent,h=f.length;for(t=0;t=yi),j0=" ",G0=!1;function Q0(t,u){switch(t){case"keyup":return IT.indexOf(u.keyCode)!==-1;case"keydown":return u.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function V0(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Ta=!1;function wT(t,u){switch(t){case"compositionend":return V0(u);case"keypress":return u.which!==32?null:(G0=!0,j0);case"textInput":return t=u.data,t===j0&&G0?null:t;default:return null}}function BT(t,u){if(Ta)return t==="compositionend"||!Js&&Q0(t,u)?(t=H0(),Fr=Qs=lu=null,Ta=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(u.ctrlKey||u.altKey||u.metaKey)||u.ctrlKey&&u.altKey){if(u.char&&1=u)return{node:i,offset:u-t};t=l}e:{for(;i;){if(i.nextSibling){i=i.nextSibling;break e}i=i.parentNode}i=void 0}i=th(i)}}function uh(t,u){return t&&u?t===u?!0:t&&t.nodeType===3?!1:u&&u.nodeType===3?uh(t,u.parentNode):"contains"in t?t.contains(u):t.compareDocumentPosition?!!(t.compareDocumentPosition(u)&16):!1:!1}function ah(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var u=Pr(t.document);u instanceof t.HTMLIFrameElement;){try{var i=typeof u.contentWindow.location.href=="string"}catch{i=!1}if(i)t=u.contentWindow;else break;u=Pr(t.document)}return u}function ec(t){var u=t&&t.nodeName&&t.nodeName.toLowerCase();return u&&(u==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||u==="textarea"||t.contentEditable==="true")}var jT=zn&&"documentMode"in document&&11>=document.documentMode,ba=null,tc=null,Ci=null,nc=!1;function ih(t,u,i){var l=i.window===i?i.document:i.nodeType===9?i:i.ownerDocument;nc||ba==null||ba!==Pr(l)||(l=ba,"selectionStart"in l&&ec(l)?l={start:l.selectionStart,end:l.selectionEnd}:(l=(l.ownerDocument&&l.ownerDocument.defaultView||window).getSelection(),l={anchorNode:l.anchorNode,anchorOffset:l.anchorOffset,focusNode:l.focusNode,focusOffset:l.focusOffset}),Ci&&Si(Ci,l)||(Ci=l,l=Ml(tc,"onSelect"),0>=b,f-=b,On=1<<32-ft(u)+f|i<Re?(Ue=Ee,Ee=null):Ue=Ee.sibling;var Fe=z(M,Ee,U[Re],Z);if(Fe===null){Ee===null&&(Ee=Ue);break}t&&Ee&&Fe.alternate===null&&u(M,Ee),v=h(Fe,v,Re),ze===null?be=Fe:ze.sibling=Fe,ze=Fe,Ee=Ue}if(Re===U.length)return i(M,Ee),He&&Yn(M,Re),be;if(Ee===null){for(;ReRe?(Ue=Ee,Ee=null):Ue=Ee.sibling;var Du=z(M,Ee,Fe.value,Z);if(Du===null){Ee===null&&(Ee=Ue);break}t&&Ee&&Du.alternate===null&&u(M,Ee),v=h(Du,v,Re),ze===null?be=Du:ze.sibling=Du,ze=Du,Ee=Ue}if(Fe.done)return i(M,Ee),He&&Yn(M,Re),be;if(Ee===null){for(;!Fe.done;Re++,Fe=U.next())Fe=W(M,Fe.value,Z),Fe!==null&&(v=h(Fe,v,Re),ze===null?be=Fe:ze.sibling=Fe,ze=Fe);return He&&Yn(M,Re),be}for(Ee=l(Ee);!Fe.done;Re++,Fe=U.next())Fe=q(Ee,M,Re,Fe.value,Z),Fe!==null&&(t&&Fe.alternate!==null&&Ee.delete(Fe.key===null?Re:Fe.key),v=h(Fe,v,Re),ze===null?be=Fe:ze.sibling=Fe,ze=Fe);return t&&Ee.forEach(function(o2){return u(M,o2)}),He&&Yn(M,Re),be}function Ke(M,v,U,Z){if(typeof U=="object"&&U!==null&&U.type===I&&U.key===null&&(U=U.props.children),typeof U=="object"&&U!==null){switch(U.$$typeof){case y:e:{for(var be=U.key;v!==null;){if(v.key===be){if(be=U.type,be===I){if(v.tag===7){i(M,v.sibling),Z=f(v,U.props.children),Z.return=M,M=Z;break e}}else if(v.elementType===be||typeof be=="object"&&be!==null&&be.$$typeof===ye&&Vu(be)===v.type){i(M,v.sibling),Z=f(v,U.props),Li(Z,U),Z.return=M,M=Z;break e}i(M,v);break}else u(M,v);v=v.sibling}U.type===I?(Z=Yu(U.props.children,M.mode,Z,U.key),Z.return=M,M=Z):(Z=Jr(U.type,U.key,U.props,null,M.mode,Z),Li(Z,U),Z.return=M,M=Z)}return b(M);case x:e:{for(be=U.key;v!==null;){if(v.key===be)if(v.tag===4&&v.stateNode.containerInfo===U.containerInfo&&v.stateNode.implementation===U.implementation){i(M,v.sibling),Z=f(v,U.children||[]),Z.return=M,M=Z;break e}else{i(M,v);break}else u(M,v);v=v.sibling}Z=cc(U,M.mode,Z),Z.return=M,M=Z}return b(M);case ye:return U=Vu(U),Ke(M,v,U,Z)}if(me(U))return pe(M,v,U,Z);if(ae(U)){if(be=ae(U),typeof be!="function")throw Error(r(150));return U=be.call(U),Ae(M,v,U,Z)}if(typeof U.then=="function")return Ke(M,v,al(U),Z);if(U.$$typeof===Q)return Ke(M,v,el(M,U),Z);il(M,U)}return typeof U=="string"&&U!==""||typeof U=="number"||typeof U=="bigint"?(U=""+U,v!==null&&v.tag===6?(i(M,v.sibling),Z=f(v,U),Z.return=M,M=Z):(i(M,v),Z=sc(U,M.mode,Z),Z.return=M,M=Z),b(M)):i(M,v)}return function(M,v,U,Z){try{Di=0;var be=Ke(M,v,U,Z);return La=null,be}catch(Ee){if(Ee===Da||Ee===nl)throw Ee;var ze=Vt(29,Ee,null,M.mode);return ze.lanes=Z,ze.return=M,ze}finally{}}}var Ku=Rh(!0),Dh=Rh(!1),hu=!1;function _c(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ac(t,u){t=t.updateQueue,u.updateQueue===t&&(u.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function du(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function mu(t,u,i){var l=t.updateQueue;if(l===null)return null;if(l=l.shared,(Ye&2)!==0){var f=l.pending;return f===null?u.next=u:(u.next=f.next,f.next=u),l.pending=u,u=Zr(t),hh(t,null,i),u}return Kr(t,l,u,i),Zr(t)}function vi(t,u,i){if(u=u.updateQueue,u!==null&&(u=u.shared,(i&4194048)!==0)){var l=u.lanes;l&=t.pendingLanes,i|=l,u.lanes=i,y0(t,i)}}function Sc(t,u){var i=t.updateQueue,l=t.alternate;if(l!==null&&(l=l.updateQueue,i===l)){var f=null,h=null;if(i=i.firstBaseUpdate,i!==null){do{var b={lane:i.lane,tag:i.tag,payload:i.payload,callback:null,next:null};h===null?f=h=b:h=h.next=b,i=i.next}while(i!==null);h===null?f=h=u:h=h.next=u}else f=h=u;i={baseState:l.baseState,firstBaseUpdate:f,lastBaseUpdate:h,shared:l.shared,callbacks:l.callbacks},t.updateQueue=i;return}t=i.lastBaseUpdate,t===null?i.firstBaseUpdate=u:t.next=u,i.lastBaseUpdate=u}var Cc=!1;function ki(){if(Cc){var t=Ra;if(t!==null)throw t}}function Ii(t,u,i,l){Cc=!1;var f=t.updateQueue;hu=!1;var h=f.firstBaseUpdate,b=f.lastBaseUpdate,_=f.shared.pending;if(_!==null){f.shared.pending=null;var D=_,H=D.next;D.next=null,b===null?h=H:b.next=H,b=D;var X=t.alternate;X!==null&&(X=X.updateQueue,_=X.lastBaseUpdate,_!==b&&(_===null?X.firstBaseUpdate=H:_.next=H,X.lastBaseUpdate=D))}if(h!==null){var W=f.baseState;b=0,X=H=D=null,_=h;do{var z=_.lane&-536870913,q=z!==_.lane;if(q?(Be&z)===z:(l&z)===z){z!==0&&z===Oa&&(Cc=!0),X!==null&&(X=X.next={lane:0,tag:_.tag,payload:_.payload,callback:null,next:null});e:{var pe=t,Ae=_;z=u;var Ke=i;switch(Ae.tag){case 1:if(pe=Ae.payload,typeof pe=="function"){W=pe.call(Ke,W,z);break e}W=pe;break e;case 3:pe.flags=pe.flags&-65537|128;case 0:if(pe=Ae.payload,z=typeof pe=="function"?pe.call(Ke,W,z):pe,z==null)break e;W=g({},W,z);break e;case 2:hu=!0}}z=_.callback,z!==null&&(t.flags|=64,q&&(t.flags|=8192),q=f.callbacks,q===null?f.callbacks=[z]:q.push(z))}else q={lane:z,tag:_.tag,payload:_.payload,callback:_.callback,next:null},X===null?(H=X=q,D=W):X=X.next=q,b|=z;if(_=_.next,_===null){if(_=f.shared.pending,_===null)break;q=_,_=q.next,q.next=null,f.lastBaseUpdate=q,f.shared.pending=null}}while(!0);X===null&&(D=W),f.baseState=D,f.firstBaseUpdate=H,f.lastBaseUpdate=X,h===null&&(f.shared.lanes=0),bu|=b,t.lanes=b,t.memoizedState=W}}function Lh(t,u){if(typeof t!="function")throw Error(r(191,t));t.call(u)}function vh(t,u){var i=t.callbacks;if(i!==null)for(t.callbacks=null,t=0;th?h:8;var b=Y.T,_={};Y.T=_,qc(t,!1,u,i);try{var D=f(),H=Y.S;if(H!==null&&H(_,D),D!==null&&typeof D=="object"&&typeof D.then=="function"){var X=$T(D,l);Bi(t,u,X,Wt(t))}else Bi(t,u,l,Wt(t))}catch(W){Bi(t,u,{then:function(){},status:"rejected",reason:W},Wt())}finally{te.p=h,b!==null&&_.types!==null&&(b.types=_.types),Y.T=b}}function ib(){}function Fc(t,u,i,l){if(t.tag!==5)throw Error(r(476));var f=cd(t).queue;sd(t,f,u,ge,i===null?ib:function(){return od(t),i(l)})}function cd(t){var u=t.memoizedState;if(u!==null)return u;u={memoizedState:ge,baseState:ge,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Qn,lastRenderedState:ge},next:null};var i={};return u.next={memoizedState:i,baseState:i,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Qn,lastRenderedState:i},next:null},t.memoizedState=u,t=t.alternate,t!==null&&(t.memoizedState=u),u}function od(t){var u=cd(t);u.next===null&&(u=t.alternate.memoizedState),Bi(t,u.next.queue,{},Wt())}function Yc(){return Nt($i)}function fd(){return ct().memoizedState}function hd(){return ct().memoizedState}function rb(t){for(var u=t.return;u!==null;){switch(u.tag){case 24:case 3:var i=Wt();t=du(i);var l=mu(u,t,i);l!==null&&(Yt(l,u,i),vi(l,u,i)),u={cache:gc()},t.payload=u;return}u=u.return}}function lb(t,u,i){var l=Wt();i={lane:l,revertLane:0,gesture:null,action:i,hasEagerState:!1,eagerState:null,next:null},pl(t)?md(u,i):(i=rc(t,u,i,l),i!==null&&(Yt(i,t,l),pd(i,u,l)))}function dd(t,u,i){var l=Wt();Bi(t,u,i,l)}function Bi(t,u,i,l){var f={lane:l,revertLane:0,gesture:null,action:i,hasEagerState:!1,eagerState:null,next:null};if(pl(t))md(u,f);else{var h=t.alternate;if(t.lanes===0&&(h===null||h.lanes===0)&&(h=u.lastRenderedReducer,h!==null))try{var b=u.lastRenderedState,_=h(b,i);if(f.hasEagerState=!0,f.eagerState=_,Qt(_,b))return Kr(t,u,f,0),Ze===null&&Xr(),!1}catch{}finally{}if(i=rc(t,u,f,l),i!==null)return Yt(i,t,l),pd(i,u,l),!0}return!1}function qc(t,u,i,l){if(l={lane:2,revertLane:Ao(),gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},pl(t)){if(u)throw Error(r(479))}else u=rc(t,i,l,2),u!==null&&Yt(u,t,2)}function pl(t){var u=t.alternate;return t===xe||u!==null&&u===xe}function md(t,u){ka=sl=!0;var i=t.pending;i===null?u.next=u:(u.next=i.next,i.next=u),t.pending=u}function pd(t,u,i){if((i&4194048)!==0){var l=u.lanes;l&=t.pendingLanes,i|=l,u.lanes=i,y0(t,i)}}var Ui={readContext:Nt,use:fl,useCallback:rt,useContext:rt,useEffect:rt,useImperativeHandle:rt,useLayoutEffect:rt,useInsertionEffect:rt,useMemo:rt,useReducer:rt,useRef:rt,useState:rt,useDebugValue:rt,useDeferredValue:rt,useTransition:rt,useSyncExternalStore:rt,useId:rt,useHostTransitionStatus:rt,useFormState:rt,useActionState:rt,useOptimistic:rt,useMemoCache:rt,useCacheRefresh:rt};Ui.useEffectEvent=rt;var Ed={readContext:Nt,use:fl,useCallback:function(t,u){return It().memoizedState=[t,u===void 0?null:u],t},useContext:Nt,useEffect:$h,useImperativeHandle:function(t,u,i){i=i!=null?i.concat([t]):null,dl(4194308,4,ud.bind(null,u,t),i)},useLayoutEffect:function(t,u){return dl(4194308,4,t,u)},useInsertionEffect:function(t,u){dl(4,2,t,u)},useMemo:function(t,u){var i=It();u=u===void 0?null:u;var l=t();if(Zu){it(!0);try{t()}finally{it(!1)}}return i.memoizedState=[l,u],l},useReducer:function(t,u,i){var l=It();if(i!==void 0){var f=i(u);if(Zu){it(!0);try{i(u)}finally{it(!1)}}}else f=u;return l.memoizedState=l.baseState=f,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:f},l.queue=t,t=t.dispatch=lb.bind(null,xe,t),[l.memoizedState,t]},useRef:function(t){var u=It();return t={current:t},u.memoizedState=t},useState:function(t){t=Bc(t);var u=t.queue,i=dd.bind(null,xe,u);return u.dispatch=i,[t.memoizedState,i]},useDebugValue:Pc,useDeferredValue:function(t,u){var i=It();return zc(i,t,u)},useTransition:function(){var t=Bc(!1);return t=sd.bind(null,xe,t.queue,!0,!1),It().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,u,i){var l=xe,f=It();if(He){if(i===void 0)throw Error(r(407));i=i()}else{if(i=u(),Ze===null)throw Error(r(349));(Be&127)!==0||Uh(l,u,i)}f.memoizedState=i;var h={value:i,getSnapshot:u};return f.queue=h,$h(Ph.bind(null,l,h,t),[t]),l.flags|=2048,Ma(9,{destroy:void 0},Hh.bind(null,l,h,i,u),null),i},useId:function(){var t=It(),u=Ze.identifierPrefix;if(He){var i=Rn,l=On;i=(l&~(1<<32-ft(l)-1)).toString(32)+i,u="_"+u+"R_"+i,i=cl++,0<\/script>",h=h.removeChild(h.firstChild);break;case"select":h=typeof l.is=="string"?b.createElement("select",{is:l.is}):b.createElement("select"),l.multiple?h.multiple=!0:l.size&&(h.size=l.size);break;default:h=typeof l.is=="string"?b.createElement(f,{is:l.is}):b.createElement(f)}}h[St]=u,h[Bt]=l;e:for(b=u.child;b!==null;){if(b.tag===5||b.tag===6)h.appendChild(b.stateNode);else if(b.tag!==4&&b.tag!==27&&b.child!==null){b.child.return=b,b=b.child;continue}if(b===u)break e;for(;b.sibling===null;){if(b.return===null||b.return===u)break e;b=b.return}b.sibling.return=b.return,b=b.sibling}u.stateNode=h;e:switch(Ot(h,f,l),f){case"button":case"input":case"select":case"textarea":l=!!l.autoFocus;break e;case"img":l=!0;break e;default:l=!1}l&&Xn(u)}}return et(u),uo(u,u.type,t===null?null:t.memoizedProps,u.pendingProps,i),null;case 6:if(t&&u.stateNode!=null)t.memoizedProps!==l&&Xn(u);else{if(typeof l!="string"&&u.stateNode===null)throw Error(r(166));if(t=Te.current,Na(u)){if(t=u.stateNode,i=u.memoizedProps,l=null,f=Ct,f!==null)switch(f.tag){case 27:case 5:l=f.memoizedProps}t[St]=u,t=!!(t.nodeValue===i||l!==null&&l.suppressHydrationWarning===!0||wm(t.nodeValue,i)),t||ou(u,!0)}else t=wl(t).createTextNode(l),t[St]=u,u.stateNode=t}return et(u),null;case 31:if(i=u.memoizedState,t===null||t.memoizedState!==null){if(l=Na(u),i!==null){if(t===null){if(!l)throw Error(r(318));if(t=u.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[St]=u}else qu(),(u.flags&128)===0&&(u.memoizedState=null),u.flags|=4;et(u),t=!1}else i=dc(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=i),t=!0;if(!t)return u.flags&256?(Kt(u),u):(Kt(u),null);if((u.flags&128)!==0)throw Error(r(558))}return et(u),null;case 13:if(l=u.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(f=Na(u),l!==null&&l.dehydrated!==null){if(t===null){if(!f)throw Error(r(318));if(f=u.memoizedState,f=f!==null?f.dehydrated:null,!f)throw Error(r(317));f[St]=u}else qu(),(u.flags&128)===0&&(u.memoizedState=null),u.flags|=4;et(u),f=!1}else f=dc(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=f),f=!0;if(!f)return u.flags&256?(Kt(u),u):(Kt(u),null)}return Kt(u),(u.flags&128)!==0?(u.lanes=i,u):(i=l!==null,t=t!==null&&t.memoizedState!==null,i&&(l=u.child,f=null,l.alternate!==null&&l.alternate.memoizedState!==null&&l.alternate.memoizedState.cachePool!==null&&(f=l.alternate.memoizedState.cachePool.pool),h=null,l.memoizedState!==null&&l.memoizedState.cachePool!==null&&(h=l.memoizedState.cachePool.pool),h!==f&&(l.flags|=2048)),i!==t&&i&&(u.child.flags|=8192),yl(u,u.updateQueue),et(u),null);case 4:return Qe(),t===null&&xo(u.stateNode.containerInfo),et(u),null;case 10:return jn(u.type),et(u),null;case 19:if(K(st),l=u.memoizedState,l===null)return et(u),null;if(f=(u.flags&128)!==0,h=l.rendering,h===null)if(f)Pi(l,!1);else{if(lt!==0||t!==null&&(t.flags&128)!==0)for(t=u.child;t!==null;){if(h=ll(t),h!==null){for(u.flags|=128,Pi(l,!1),t=h.updateQueue,u.updateQueue=t,yl(u,t),u.subtreeFlags=0,t=i,i=u.child;i!==null;)dh(i,t),i=i.sibling;return C(st,st.current&1|2),He&&Yn(u,l.treeForkCount),u.child}t=t.sibling}l.tail!==null&&kt()>Nl&&(u.flags|=128,f=!0,Pi(l,!1),u.lanes=4194304)}else{if(!f)if(t=ll(h),t!==null){if(u.flags|=128,f=!0,t=t.updateQueue,u.updateQueue=t,yl(u,t),Pi(l,!0),l.tail===null&&l.tailMode==="hidden"&&!h.alternate&&!He)return et(u),null}else 2*kt()-l.renderingStartTime>Nl&&i!==536870912&&(u.flags|=128,f=!0,Pi(l,!1),u.lanes=4194304);l.isBackwards?(h.sibling=u.child,u.child=h):(t=l.last,t!==null?t.sibling=h:u.child=h,l.last=h)}return l.tail!==null?(t=l.tail,l.rendering=t,l.tail=t.sibling,l.renderingStartTime=kt(),t.sibling=null,i=st.current,C(st,f?i&1|2:i&1),He&&Yn(u,l.treeForkCount),t):(et(u),null);case 22:case 23:return Kt(u),xc(),l=u.memoizedState!==null,t!==null?t.memoizedState!==null!==l&&(u.flags|=8192):l&&(u.flags|=8192),l?(i&536870912)!==0&&(u.flags&128)===0&&(et(u),u.subtreeFlags&6&&(u.flags|=8192)):et(u),i=u.updateQueue,i!==null&&yl(u,i.retryQueue),i=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(i=t.memoizedState.cachePool.pool),l=null,u.memoizedState!==null&&u.memoizedState.cachePool!==null&&(l=u.memoizedState.cachePool.pool),l!==i&&(u.flags|=2048),t!==null&&K(Qu),null;case 24:return i=null,t!==null&&(i=t.memoizedState.cache),u.memoizedState.cache!==i&&(u.flags|=2048),jn(ht),et(u),null;case 25:return null;case 30:return null}throw Error(r(156,u.tag))}function hb(t,u){switch(fc(u),u.tag){case 1:return t=u.flags,t&65536?(u.flags=t&-65537|128,u):null;case 3:return jn(ht),Qe(),t=u.flags,(t&65536)!==0&&(t&128)===0?(u.flags=t&-65537|128,u):null;case 26:case 27:case 5:return iu(u),null;case 31:if(u.memoizedState!==null){if(Kt(u),u.alternate===null)throw Error(r(340));qu()}return t=u.flags,t&65536?(u.flags=t&-65537|128,u):null;case 13:if(Kt(u),t=u.memoizedState,t!==null&&t.dehydrated!==null){if(u.alternate===null)throw Error(r(340));qu()}return t=u.flags,t&65536?(u.flags=t&-65537|128,u):null;case 19:return K(st),null;case 4:return Qe(),null;case 10:return jn(u.type),null;case 22:case 23:return Kt(u),xc(),t!==null&&K(Qu),t=u.flags,t&65536?(u.flags=t&-65537|128,u):null;case 24:return jn(ht),null;case 25:return null;default:return null}}function zd(t,u){switch(fc(u),u.tag){case 3:jn(ht),Qe();break;case 26:case 27:case 5:iu(u);break;case 4:Qe();break;case 31:u.memoizedState!==null&&Kt(u);break;case 13:Kt(u);break;case 19:K(st);break;case 10:jn(u.type);break;case 22:case 23:Kt(u),xc(),t!==null&&K(Qu);break;case 24:jn(ht)}}function zi(t,u){try{var i=u.updateQueue,l=i!==null?i.lastEffect:null;if(l!==null){var f=l.next;i=f;do{if((i.tag&t)===t){l=void 0;var h=i.create,b=i.inst;l=h(),b.destroy=l}i=i.next}while(i!==f)}}catch(_){Ge(u,u.return,_)}}function gu(t,u,i){try{var l=u.updateQueue,f=l!==null?l.lastEffect:null;if(f!==null){var h=f.next;l=h;do{if((l.tag&t)===t){var b=l.inst,_=b.destroy;if(_!==void 0){b.destroy=void 0,f=u;var D=i,H=_;try{H()}catch(X){Ge(f,D,X)}}}l=l.next}while(l!==h)}}catch(X){Ge(u,u.return,X)}}function Fd(t){var u=t.updateQueue;if(u!==null){var i=t.stateNode;try{vh(u,i)}catch(l){Ge(t,t.return,l)}}}function Yd(t,u,i){i.props=Ju(t.type,t.memoizedProps),i.state=t.memoizedState;try{i.componentWillUnmount()}catch(l){Ge(t,u,l)}}function Fi(t,u){try{var i=t.ref;if(i!==null){switch(t.tag){case 26:case 27:case 5:var l=t.stateNode;break;case 30:l=t.stateNode;break;default:l=t.stateNode}typeof i=="function"?t.refCleanup=i(l):i.current=l}}catch(f){Ge(t,u,f)}}function Dn(t,u){var i=t.ref,l=t.refCleanup;if(i!==null)if(typeof l=="function")try{l()}catch(f){Ge(t,u,f)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof i=="function")try{i(null)}catch(f){Ge(t,u,f)}else i.current=null}function qd(t){var u=t.type,i=t.memoizedProps,l=t.stateNode;try{e:switch(u){case"button":case"input":case"select":case"textarea":i.autoFocus&&l.focus();break e;case"img":i.src?l.src=i.src:i.srcSet&&(l.srcset=i.srcSet)}}catch(f){Ge(t,t.return,f)}}function ao(t,u,i){try{var l=t.stateNode;Mb(l,t.type,i,u),l[Bt]=u}catch(f){Ge(t,t.return,f)}}function jd(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&Cu(t.type)||t.tag===4}function io(t){e:for(;;){for(;t.sibling===null;){if(t.return===null||jd(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&Cu(t.type)||t.flags&2||t.child===null||t.tag===4)continue e;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function ro(t,u,i){var l=t.tag;if(l===5||l===6)t=t.stateNode,u?(i.nodeType===9?i.body:i.nodeName==="HTML"?i.ownerDocument.body:i).insertBefore(t,u):(u=i.nodeType===9?i.body:i.nodeName==="HTML"?i.ownerDocument.body:i,u.appendChild(t),i=i._reactRootContainer,i!=null||u.onclick!==null||(u.onclick=Pn));else if(l!==4&&(l===27&&Cu(t.type)&&(i=t.stateNode,u=null),t=t.child,t!==null))for(ro(t,u,i),t=t.sibling;t!==null;)ro(t,u,i),t=t.sibling}function _l(t,u,i){var l=t.tag;if(l===5||l===6)t=t.stateNode,u?i.insertBefore(t,u):i.appendChild(t);else if(l!==4&&(l===27&&Cu(t.type)&&(i=t.stateNode),t=t.child,t!==null))for(_l(t,u,i),t=t.sibling;t!==null;)_l(t,u,i),t=t.sibling}function Gd(t){var u=t.stateNode,i=t.memoizedProps;try{for(var l=t.type,f=u.attributes;f.length;)u.removeAttributeNode(f[0]);Ot(u,l,i),u[St]=t,u[Bt]=i}catch(h){Ge(t,t.return,h)}}var Kn=!1,pt=!1,lo=!1,Qd=typeof WeakSet=="function"?WeakSet:Set,yt=null;function db(t,u){if(t=t.containerInfo,Do=Yl,t=ah(t),ec(t)){if("selectionStart"in t)var i={start:t.selectionStart,end:t.selectionEnd};else e:{i=(i=t.ownerDocument)&&i.defaultView||window;var l=i.getSelection&&i.getSelection();if(l&&l.rangeCount!==0){i=l.anchorNode;var f=l.anchorOffset,h=l.focusNode;l=l.focusOffset;try{i.nodeType,h.nodeType}catch{i=null;break e}var b=0,_=-1,D=-1,H=0,X=0,W=t,z=null;t:for(;;){for(var q;W!==i||f!==0&&W.nodeType!==3||(_=b+f),W!==h||l!==0&&W.nodeType!==3||(D=b+l),W.nodeType===3&&(b+=W.nodeValue.length),(q=W.firstChild)!==null;)z=W,W=q;for(;;){if(W===t)break t;if(z===i&&++H===f&&(_=b),z===h&&++X===l&&(D=b),(q=W.nextSibling)!==null)break;W=z,z=W.parentNode}W=q}i=_===-1||D===-1?null:{start:_,end:D}}else i=null}i=i||{start:0,end:0}}else i=null;for(Lo={focusedElem:t,selectionRange:i},Yl=!1,yt=u;yt!==null;)if(u=yt,t=u.child,(u.subtreeFlags&1028)!==0&&t!==null)t.return=u,yt=t;else for(;yt!==null;){switch(u=yt,h=u.alternate,t=u.flags,u.tag){case 0:if((t&4)!==0&&(t=u.updateQueue,t=t!==null?t.events:null,t!==null))for(i=0;i title"))),Ot(h,l,i),h[St]=t,bt(h),l=h;break e;case"link":var b=Wm("link","href",f).get(l+(i.href||""));if(b){for(var _=0;_Ke&&(b=Ke,Ke=Ae,Ae=b);var M=nh(_,Ae),v=nh(_,Ke);if(M&&v&&(q.rangeCount!==1||q.anchorNode!==M.node||q.anchorOffset!==M.offset||q.focusNode!==v.node||q.focusOffset!==v.offset)){var U=W.createRange();U.setStart(M.node,M.offset),q.removeAllRanges(),Ae>Ke?(q.addRange(U),q.extend(v.node,v.offset)):(U.setEnd(v.node,v.offset),q.addRange(U))}}}}for(W=[],q=_;q=q.parentNode;)q.nodeType===1&&W.push({element:q,left:q.scrollLeft,top:q.scrollTop});for(typeof _.focus=="function"&&_.focus(),_=0;_i?32:i,Y.T=null,i=po,po=null;var h=_u,b=eu;if(Tt=0,Pa=_u=null,eu=0,(Ye&6)!==0)throw Error(r(331));var _=Ye;if(Ye|=4,um(h.current),em(h,h.current,b,i),Ye=_,Vi(0,!1),gt&&typeof gt.onPostCommitFiberRoot=="function")try{gt.onPostCommitFiberRoot(Tn,h)}catch{}return!0}finally{te.p=f,Y.T=l,_m(t,u)}}function Sm(t,u,i){u=an(i,u),u=Vc(t.stateNode,u,2),t=mu(t,u,2),t!==null&&(di(t,2),Ln(t))}function Ge(t,u,i){if(t.tag===3)Sm(t,t,i);else for(;u!==null;){if(u.tag===3){Sm(u,t,i);break}else if(u.tag===1){var l=u.stateNode;if(typeof u.type.getDerivedStateFromError=="function"||typeof l.componentDidCatch=="function"&&(yu===null||!yu.has(l))){t=an(i,t),i=Cd(2),l=mu(u,i,2),l!==null&&(Nd(i,l,u,t),di(l,2),Ln(l));break}}u=u.return}}function bo(t,u,i){var l=t.pingCache;if(l===null){l=t.pingCache=new Eb;var f=new Set;l.set(u,f)}else f=l.get(u),f===void 0&&(f=new Set,l.set(u,f));f.has(i)||(oo=!0,f.add(i),t=_b.bind(null,t,u,i),u.then(t,t))}function _b(t,u,i){var l=t.pingCache;l!==null&&l.delete(u),t.pingedLanes|=t.suspendedLanes&i,t.warmLanes&=~i,Ze===t&&(Be&i)===i&&(lt===4||lt===3&&(Be&62914560)===Be&&300>kt()-Cl?(Ye&2)===0&&za(t,0):fo|=i,Ha===Be&&(Ha=0)),Ln(t)}function Cm(t,u){u===0&&(u=T0()),t=Fu(t,u),t!==null&&(di(t,u),Ln(t))}function Ab(t){var u=t.memoizedState,i=0;u!==null&&(i=u.retryLane),Cm(t,i)}function Sb(t,u){var i=0;switch(t.tag){case 31:case 13:var l=t.stateNode,f=t.memoizedState;f!==null&&(i=f.retryLane);break;case 19:l=t.stateNode;break;case 22:l=t.stateNode._retryCache;break;default:throw Error(r(314))}l!==null&&l.delete(u),Cm(t,i)}function Cb(t,u){return oi(t,u)}var vl=null,Ya=null,yo=!1,kl=!1,_o=!1,Su=0;function Ln(t){t!==Ya&&t.next===null&&(Ya===null?vl=Ya=t:Ya=Ya.next=t),kl=!0,yo||(yo=!0,xb())}function Vi(t,u){if(!_o&&kl){_o=!0;do for(var i=!1,l=vl;l!==null;){if(t!==0){var f=l.pendingLanes;if(f===0)var h=0;else{var b=l.suspendedLanes,_=l.pingedLanes;h=(1<<31-ft(42|t)+1)-1,h&=f&~(b&~_),h=h&201326741?h&201326741|1:h?h|2:0}h!==0&&(i=!0,Rm(l,h))}else h=Be,h=Br(l,l===Ze?h:0,l.cancelPendingCommit!==null||l.timeoutHandle!==-1),(h&3)===0||hi(l,h)||(i=!0,Rm(l,h));l=l.next}while(i);_o=!1}}function Nb(){Nm()}function Nm(){kl=yo=!1;var t=0;Su!==0&&Bb()&&(t=Su);for(var u=kt(),i=null,l=vl;l!==null;){var f=l.next,h=xm(l,u);h===0?(l.next=null,i===null?vl=f:i.next=f,f===null&&(Ya=i)):(i=l,(t!==0||(h&3)!==0)&&(kl=!0)),l=f}Tt!==0&&Tt!==5||Vi(t),Su!==0&&(Su=0)}function xm(t,u){for(var i=t.suspendedLanes,l=t.pingedLanes,f=t.expirationTimes,h=t.pendingLanes&-62914561;0_)break;var X=D.transferSize,W=D.initiatorType;X&&Bm(W)&&(D=D.responseEnd,b+=X*(D<_?1:(_-H)/(D-H)))}if(--l,u+=8*(h+b)/(f.duration/1e3),t++,10"u"?null:document;function Xm(t,u,i){var l=qa;if(l&&typeof u=="string"&&u){var f=nn(u);f='link[rel="'+t+'"][href="'+f+'"]',typeof i=="string"&&(f+='[crossorigin="'+i+'"]'),Vm.has(f)||(Vm.add(f),t={rel:t,crossOrigin:i,href:u},l.querySelector(f)===null&&(u=l.createElement("link"),Ot(u,"link",t),bt(u),l.head.appendChild(u)))}}function Gb(t){tu.D(t),Xm("dns-prefetch",t,null)}function Qb(t,u){tu.C(t,u),Xm("preconnect",t,u)}function Vb(t,u,i){tu.L(t,u,i);var l=qa;if(l&&t&&u){var f='link[rel="preload"][as="'+nn(u)+'"]';u==="image"&&i&&i.imageSrcSet?(f+='[imagesrcset="'+nn(i.imageSrcSet)+'"]',typeof i.imageSizes=="string"&&(f+='[imagesizes="'+nn(i.imageSizes)+'"]')):f+='[href="'+nn(t)+'"]';var h=f;switch(u){case"style":h=ja(t);break;case"script":h=Ga(t)}fn.has(h)||(t=g({rel:"preload",href:u==="image"&&i&&i.imageSrcSet?void 0:t,as:u},i),fn.set(h,t),l.querySelector(f)!==null||u==="style"&&l.querySelector(Ji(h))||u==="script"&&l.querySelector(Wi(h))||(u=l.createElement("link"),Ot(u,"link",t),bt(u),l.head.appendChild(u)))}}function Xb(t,u){tu.m(t,u);var i=qa;if(i&&t){var l=u&&typeof u.as=="string"?u.as:"script",f='link[rel="modulepreload"][as="'+nn(l)+'"][href="'+nn(t)+'"]',h=f;switch(l){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":h=Ga(t)}if(!fn.has(h)&&(t=g({rel:"modulepreload",href:t},u),fn.set(h,t),i.querySelector(f)===null)){switch(l){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(i.querySelector(Wi(h)))return}l=i.createElement("link"),Ot(l,"link",t),bt(l),i.head.appendChild(l)}}}function Kb(t,u,i){tu.S(t,u,i);var l=qa;if(l&&t){var f=ha(l).hoistableStyles,h=ja(t);u=u||"default";var b=f.get(h);if(!b){var _={loading:0,preload:null};if(b=l.querySelector(Ji(h)))_.loading=5;else{t=g({rel:"stylesheet",href:t,"data-precedence":u},i),(i=fn.get(h))&&Uo(t,i);var D=b=l.createElement("link");bt(D),Ot(D,"link",t),D._p=new Promise(function(H,X){D.onload=H,D.onerror=X}),D.addEventListener("load",function(){_.loading|=1}),D.addEventListener("error",function(){_.loading|=2}),_.loading|=4,Ul(b,u,l)}b={type:"stylesheet",instance:b,count:1,state:_},f.set(h,b)}}}function Zb(t,u){tu.X(t,u);var i=qa;if(i&&t){var l=ha(i).hoistableScripts,f=Ga(t),h=l.get(f);h||(h=i.querySelector(Wi(f)),h||(t=g({src:t,async:!0},u),(u=fn.get(f))&&Ho(t,u),h=i.createElement("script"),bt(h),Ot(h,"link",t),i.head.appendChild(h)),h={type:"script",instance:h,count:1,state:null},l.set(f,h))}}function Jb(t,u){tu.M(t,u);var i=qa;if(i&&t){var l=ha(i).hoistableScripts,f=Ga(t),h=l.get(f);h||(h=i.querySelector(Wi(f)),h||(t=g({src:t,async:!0,type:"module"},u),(u=fn.get(f))&&Ho(t,u),h=i.createElement("script"),bt(h),Ot(h,"link",t),i.head.appendChild(h)),h={type:"script",instance:h,count:1,state:null},l.set(f,h))}}function Km(t,u,i,l){var f=(f=Te.current)?Bl(f):null;if(!f)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof i.precedence=="string"&&typeof i.href=="string"?(u=ja(i.href),i=ha(f).hoistableStyles,l=i.get(u),l||(l={type:"style",instance:null,count:0,state:null},i.set(u,l)),l):{type:"void",instance:null,count:0,state:null};case"link":if(i.rel==="stylesheet"&&typeof i.href=="string"&&typeof i.precedence=="string"){t=ja(i.href);var h=ha(f).hoistableStyles,b=h.get(t);if(b||(f=f.ownerDocument||f,b={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},h.set(t,b),(h=f.querySelector(Ji(t)))&&!h._p&&(b.instance=h,b.state.loading=5),fn.has(t)||(i={rel:"preload",as:"style",href:i.href,crossOrigin:i.crossOrigin,integrity:i.integrity,media:i.media,hrefLang:i.hrefLang,referrerPolicy:i.referrerPolicy},fn.set(t,i),h||Wb(f,t,i,b.state))),u&&l===null)throw Error(r(528,""));return b}if(u&&l!==null)throw Error(r(529,""));return null;case"script":return u=i.async,i=i.src,typeof i=="string"&&u&&typeof u!="function"&&typeof u!="symbol"?(u=Ga(i),i=ha(f).hoistableScripts,l=i.get(u),l||(l={type:"script",instance:null,count:0,state:null},i.set(u,l)),l):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function ja(t){return'href="'+nn(t)+'"'}function Ji(t){return'link[rel="stylesheet"]['+t+"]"}function Zm(t){return g({},t,{"data-precedence":t.precedence,precedence:null})}function Wb(t,u,i,l){t.querySelector('link[rel="preload"][as="style"]['+u+"]")?l.loading=1:(u=t.createElement("link"),l.preload=u,u.addEventListener("load",function(){return l.loading|=1}),u.addEventListener("error",function(){return l.loading|=2}),Ot(u,"link",i),bt(u),t.head.appendChild(u))}function Ga(t){return'[src="'+nn(t)+'"]'}function Wi(t){return"script[async]"+t}function Jm(t,u,i){if(u.count++,u.instance===null)switch(u.type){case"style":var l=t.querySelector('style[data-href~="'+nn(i.href)+'"]');if(l)return u.instance=l,bt(l),l;var f=g({},i,{"data-href":i.href,"data-precedence":i.precedence,href:null,precedence:null});return l=(t.ownerDocument||t).createElement("style"),bt(l),Ot(l,"style",f),Ul(l,i.precedence,t),u.instance=l;case"stylesheet":f=ja(i.href);var h=t.querySelector(Ji(f));if(h)return u.state.loading|=4,u.instance=h,bt(h),h;l=Zm(i),(f=fn.get(f))&&Uo(l,f),h=(t.ownerDocument||t).createElement("link"),bt(h);var b=h;return b._p=new Promise(function(_,D){b.onload=_,b.onerror=D}),Ot(h,"link",l),u.state.loading|=4,Ul(h,i.precedence,t),u.instance=h;case"script":return h=Ga(i.src),(f=t.querySelector(Wi(h)))?(u.instance=f,bt(f),f):(l=i,(f=fn.get(h))&&(l=g({},i),Ho(l,f)),t=t.ownerDocument||t,f=t.createElement("script"),bt(f),Ot(f,"link",l),t.head.appendChild(f),u.instance=f);case"void":return null;default:throw Error(r(443,u.type))}else u.type==="stylesheet"&&(u.state.loading&4)===0&&(l=u.instance,u.state.loading|=4,Ul(l,i.precedence,t));return u.instance}function Ul(t,u,i){for(var l=i.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),f=l.length?l[l.length-1]:null,h=f,b=0;b title"):null)}function $b(t,u,i){if(i===1||u.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof u.precedence!="string"||typeof u.href!="string"||u.href==="")break;return!0;case"link":if(typeof u.rel!="string"||typeof u.href!="string"||u.href===""||u.onLoad||u.onError)break;switch(u.rel){case"stylesheet":return t=u.disabled,typeof u.precedence=="string"&&t==null;default:return!0}case"script":if(u.async&&typeof u.async!="function"&&typeof u.async!="symbol"&&!u.onLoad&&!u.onError&&u.src&&typeof u.src=="string")return!0}return!1}function e1(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function e2(t,u,i,l){if(i.type==="stylesheet"&&(typeof l.media!="string"||matchMedia(l.media).matches!==!1)&&(i.state.loading&4)===0){if(i.instance===null){var f=ja(l.href),h=u.querySelector(Ji(f));if(h){u=h._p,u!==null&&typeof u=="object"&&typeof u.then=="function"&&(t.count++,t=Pl.bind(t),u.then(t,t)),i.state.loading|=4,i.instance=h,bt(h);return}h=u.ownerDocument||u,l=Zm(l),(f=fn.get(f))&&Uo(l,f),h=h.createElement("link"),bt(h);var b=h;b._p=new Promise(function(_,D){b.onload=_,b.onerror=D}),Ot(h,"link",l),i.instance=h}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(i,u),(u=i.state.preload)&&(i.state.loading&3)===0&&(t.count++,i=Pl.bind(t),u.addEventListener("load",i),u.addEventListener("error",i))}}var Po=0;function t2(t,u){return t.stylesheets&&t.count===0&&Fl(t,t.stylesheets),0Po?50:800)+u);return t.unsuspend=i,function(){t.unsuspend=null,clearTimeout(l),clearTimeout(f)}}:null}function Pl(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Fl(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var zl=null;function Fl(t,u){t.stylesheets=null,t.unsuspend!==null&&(t.count++,zl=new Map,u.forEach(n2,t),zl=null,Pl.call(t))}function n2(t,u){if(!(u.state.loading&4)){var i=zl.get(t);if(i)var l=i.get(null);else{i=new Map,zl.set(t,i);for(var f=t.querySelectorAll("link[data-precedence],style[data-precedence]"),h=0;h"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(n){console.error(n)}}return e(),Xo.exports=T2(),Xo.exports}var y2=b2();/** + * react-router v7.13.1 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */var C1="popstate";function N1(e){return typeof e=="object"&&e!=null&&"pathname"in e&&"search"in e&&"hash"in e&&"state"in e&&"key"in e}function _2(e={}){function n(r,s){var p;let c=(p=s.state)==null?void 0:p.masked,{pathname:o,search:m,hash:E}=c||r.location;return Nf("",{pathname:o,search:m,hash:E},s.state&&s.state.usr||null,s.state&&s.state.key||"default",c?{pathname:r.location.pathname,search:r.location.search,hash:r.location.hash}:void 0)}function a(r,s){return typeof s=="string"?s:br(s)}return S2(n,a,null,e)}function at(e,n){if(e===!1||e===null||typeof e>"u")throw new Error(n)}function pn(e,n){if(!e){typeof console<"u"&&console.warn(n);try{throw new Error(n)}catch{}}}function A2(){return Math.random().toString(36).substring(2,10)}function x1(e,n){return{usr:e.state,key:e.key,idx:n,masked:e.unstable_mask?{pathname:e.pathname,search:e.search,hash:e.hash}:void 0}}function Nf(e,n,a=null,r,s){return{pathname:typeof e=="string"?e:e.pathname,search:"",hash:"",...typeof n=="string"?ei(n):n,state:a,key:n&&n.key||r||A2(),unstable_mask:s}}function br({pathname:e="/",search:n="",hash:a=""}){return n&&n!=="?"&&(e+=n.charAt(0)==="?"?n:"?"+n),a&&a!=="#"&&(e+=a.charAt(0)==="#"?a:"#"+a),e}function ei(e){let n={};if(e){let a=e.indexOf("#");a>=0&&(n.hash=e.substring(a),e=e.substring(0,a));let r=e.indexOf("?");r>=0&&(n.search=e.substring(r),e=e.substring(0,r)),e&&(n.pathname=e)}return n}function S2(e,n,a,r={}){let{window:s=document.defaultView,v5Compat:c=!1}=r,o=s.history,m="POP",E=null,p=T();p==null&&(p=0,o.replaceState({...o.state,idx:p},""));function T(){return(o.state||{idx:null}).idx}function g(){m="POP";let w=T(),k=w==null?null:w-p;p=w,E&&E({action:m,location:I.location,delta:k})}function A(w,k){m="PUSH";let V=N1(w)?w:Nf(I.location,w,k);p=T()+1;let Q=x1(V,p),se=I.createHref(V.unstable_mask||V);try{o.pushState(Q,"",se)}catch(he){if(he instanceof DOMException&&he.name==="DataCloneError")throw he;s.location.assign(se)}c&&E&&E({action:m,location:I.location,delta:1})}function y(w,k){m="REPLACE";let V=N1(w)?w:Nf(I.location,w,k);p=T();let Q=x1(V,p),se=I.createHref(V.unstable_mask||V);o.replaceState(Q,"",se),c&&E&&E({action:m,location:I.location,delta:0})}function x(w){return C2(w)}let I={get action(){return m},get location(){return e(s,o)},listen(w){if(E)throw new Error("A history only accepts one active listener");return s.addEventListener(C1,g),E=w,()=>{s.removeEventListener(C1,g),E=null}},createHref(w){return n(s,w)},createURL:x,encodeLocation(w){let k=x(w);return{pathname:k.pathname,search:k.search,hash:k.hash}},push:A,replace:y,go(w){return o.go(w)}};return I}function C2(e,n=!1){let a="http://localhost";typeof window<"u"&&(a=window.location.origin!=="null"?window.location.origin:window.location.href),at(a,"No window.location.(origin|href) available to create URL");let r=typeof e=="string"?e:br(e);return r=r.replace(/ $/,"%20"),!n&&r.startsWith("//")&&(r=a+r),new URL(r,a)}function zp(e,n,a="/"){return N2(e,n,a,!1)}function N2(e,n,a,r){let s=typeof n=="string"?ei(n):n,c=uu(s.pathname||"/",a);if(c==null)return null;let o=Fp(e);x2(o);let m=null;for(let E=0;m==null&&E{let T={relativePath:p===void 0?o.path||"":p,caseSensitive:o.caseSensitive===!0,childrenIndex:m,route:o};if(T.relativePath.startsWith("/")){if(!T.relativePath.startsWith(r)&&E)return;at(T.relativePath.startsWith(r),`Absolute route path "${T.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),T.relativePath=T.relativePath.slice(r.length)}let g=In([r,T.relativePath]),A=a.concat(T);o.children&&o.children.length>0&&(at(o.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${g}".`),Fp(o.children,n,A,g,E)),!(o.path==null&&!o.index)&&n.push({path:g,score:I2(g,o.index),routesMeta:A})};return e.forEach((o,m)=>{var E;if(o.path===""||!((E=o.path)!=null&&E.includes("?")))c(o,m);else for(let p of Yp(o.path))c(o,m,!0,p)}),n}function Yp(e){let n=e.split("/");if(n.length===0)return[];let[a,...r]=n,s=a.endsWith("?"),c=a.replace(/\?$/,"");if(r.length===0)return s?[c,""]:[c];let o=Yp(r.join("/")),m=[];return m.push(...o.map(E=>E===""?c:[c,E].join("/"))),s&&m.push(...o),m.map(E=>e.startsWith("/")&&E===""?"/":E)}function x2(e){e.sort((n,a)=>n.score!==a.score?a.score-n.score:M2(n.routesMeta.map(r=>r.childrenIndex),a.routesMeta.map(r=>r.childrenIndex)))}var O2=/^:[\w-]+$/,R2=3,D2=2,L2=1,v2=10,k2=-2,O1=e=>e==="*";function I2(e,n){let a=e.split("/"),r=a.length;return a.some(O1)&&(r+=k2),n&&(r+=D2),a.filter(s=>!O1(s)).reduce((s,c)=>s+(O2.test(c)?R2:c===""?L2:v2),r)}function M2(e,n){return e.length===n.length&&e.slice(0,-1).every((r,s)=>r===n[s])?e[e.length-1]-n[n.length-1]:0}function w2(e,n,a=!1){let{routesMeta:r}=e,s={},c="/",o=[];for(let m=0;m{if(T==="*"){let x=m[A]||"";o=c.slice(0,c.length-x.length).replace(/(.)\/+$/,"$1")}const y=m[A];return g&&!y?p[T]=void 0:p[T]=(y||"").replace(/%2F/g,"/"),p},{}),pathname:c,pathnameBase:o,pattern:e}}function B2(e,n=!1,a=!0){pn(e==="*"||!e.endsWith("*")||e.endsWith("/*"),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,"/*")}".`);let r=[],s="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(o,m,E,p,T)=>{if(r.push({paramName:m,isOptional:E!=null}),E){let g=T.charAt(p+o.length);return g&&g!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return e.endsWith("*")?(r.push({paramName:"*"}),s+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):a?s+="\\/*$":e!==""&&e!=="/"&&(s+="(?:(?=\\/|$))"),[new RegExp(s,n?void 0:"i"),r]}function U2(e){try{return e.split("/").map(n=>decodeURIComponent(n).replace(/\//g,"%2F")).join("/")}catch(n){return pn(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${n}).`),e}}function uu(e,n){if(n==="/")return e;if(!e.toLowerCase().startsWith(n.toLowerCase()))return null;let a=n.endsWith("/")?n.length-1:n.length,r=e.charAt(a);return r&&r!=="/"?null:e.slice(a)||"/"}var H2=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function P2(e,n="/"){let{pathname:a,search:r="",hash:s=""}=typeof e=="string"?ei(e):e,c;return a?(a=a.replace(/\/\/+/g,"/"),a.startsWith("/")?c=R1(a.substring(1),"/"):c=R1(a,n)):c=n,{pathname:c,search:Y2(r),hash:q2(s)}}function R1(e,n){let a=n.replace(/\/+$/,"").split("/");return e.split("/").forEach(s=>{s===".."?a.length>1&&a.pop():s!=="."&&a.push(s)}),a.length>1?a.join("/"):"/"}function Wo(e,n,a,r){return`Cannot include a '${e}' character in a manually specified \`to.${n}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${a}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function z2(e){return e.filter((n,a)=>a===0||n.route.path&&n.route.path.length>0)}function jf(e){let n=z2(e);return n.map((a,r)=>r===n.length-1?a.pathname:a.pathnameBase)}function ms(e,n,a,r=!1){let s;typeof e=="string"?s=ei(e):(s={...e},at(!s.pathname||!s.pathname.includes("?"),Wo("?","pathname","search",s)),at(!s.pathname||!s.pathname.includes("#"),Wo("#","pathname","hash",s)),at(!s.search||!s.search.includes("#"),Wo("#","search","hash",s)));let c=e===""||s.pathname==="",o=c?"/":s.pathname,m;if(o==null)m=a;else{let g=n.length-1;if(!r&&o.startsWith("..")){let A=o.split("/");for(;A[0]==="..";)A.shift(),g-=1;s.pathname=A.join("/")}m=g>=0?n[g]:"/"}let E=P2(s,m),p=o&&o!=="/"&&o.endsWith("/"),T=(c||o===".")&&a.endsWith("/");return!E.pathname.endsWith("/")&&(p||T)&&(E.pathname+="/"),E}var In=e=>e.join("/").replace(/\/\/+/g,"/"),F2=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),Y2=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,q2=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e,j2=class{constructor(e,n,a,r=!1){this.status=e,this.statusText=n||"",this.internal=r,a instanceof Error?(this.data=a.toString(),this.error=a):this.data=a}};function G2(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}function Q2(e){return e.map(n=>n.route.path).filter(Boolean).join("/").replace(/\/\/*/g,"/")||"/"}var qp=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function jp(e,n){let a=e;if(typeof a!="string"||!H2.test(a))return{absoluteURL:void 0,isExternal:!1,to:a};let r=a,s=!1;if(qp)try{let c=new URL(window.location.href),o=a.startsWith("//")?new URL(c.protocol+a):new URL(a),m=uu(o.pathname,n);o.origin===c.origin&&m!=null?a=m+o.search+o.hash:s=!0}catch{pn(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:s,to:a}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Gp=["POST","PUT","PATCH","DELETE"];new Set(Gp);var V2=["GET",...Gp];new Set(V2);var ti=F.createContext(null);ti.displayName="DataRouter";var ps=F.createContext(null);ps.displayName="DataRouterState";var X2=F.createContext(!1),Qp=F.createContext({isTransitioning:!1});Qp.displayName="ViewTransition";var K2=F.createContext(new Map);K2.displayName="Fetchers";var Z2=F.createContext(null);Z2.displayName="Await";var en=F.createContext(null);en.displayName="Navigation";var Sr=F.createContext(null);Sr.displayName="Location";var Cn=F.createContext({outlet:null,matches:[],isDataRoute:!1});Cn.displayName="Route";var Gf=F.createContext(null);Gf.displayName="RouteError";var Vp="REACT_ROUTER_ERROR",J2="REDIRECT",W2="ROUTE_ERROR_RESPONSE";function $2(e){if(e.startsWith(`${Vp}:${J2}:{`))try{let n=JSON.parse(e.slice(28));if(typeof n=="object"&&n&&typeof n.status=="number"&&typeof n.statusText=="string"&&typeof n.location=="string"&&typeof n.reloadDocument=="boolean"&&typeof n.replace=="boolean")return n}catch{}}function ey(e){if(e.startsWith(`${Vp}:${W2}:{`))try{let n=JSON.parse(e.slice(40));if(typeof n=="object"&&n&&typeof n.status=="number"&&typeof n.statusText=="string")return new j2(n.status,n.statusText,n.data)}catch{}}function ty(e,{relative:n}={}){at(ni(),"useHref() may be used only in the context of a component.");let{basename:a,navigator:r}=F.useContext(en),{hash:s,pathname:c,search:o}=Cr(e,{relative:n}),m=c;return a!=="/"&&(m=c==="/"?a:In([a,c])),r.createHref({pathname:m,search:o,hash:s})}function ni(){return F.useContext(Sr)!=null}function Mn(){return at(ni(),"useLocation() may be used only in the context of a component."),F.useContext(Sr).location}var Xp="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Kp(e){F.useContext(en).static||F.useLayoutEffect(e)}function Qf(){let{isDataRoute:e}=F.useContext(Cn);return e?py():ny()}function ny(){at(ni(),"useNavigate() may be used only in the context of a component.");let e=F.useContext(ti),{basename:n,navigator:a}=F.useContext(en),{matches:r}=F.useContext(Cn),{pathname:s}=Mn(),c=JSON.stringify(jf(r)),o=F.useRef(!1);return Kp(()=>{o.current=!0}),F.useCallback((E,p={})=>{if(pn(o.current,Xp),!o.current)return;if(typeof E=="number"){a.go(E);return}let T=ms(E,JSON.parse(c),s,p.relative==="path");e==null&&n!=="/"&&(T.pathname=T.pathname==="/"?n:In([n,T.pathname])),(p.replace?a.replace:a.push)(T,p.state,p)},[n,a,c,s,e])}F.createContext(null);function uy(){let{matches:e}=F.useContext(Cn),n=e[e.length-1];return n?n.params:{}}function Cr(e,{relative:n}={}){let{matches:a}=F.useContext(Cn),{pathname:r}=Mn(),s=JSON.stringify(jf(a));return F.useMemo(()=>ms(e,JSON.parse(s),r,n==="path"),[e,s,r,n])}function ay(e,n){return Zp(e,n)}function Zp(e,n,a){var w;at(ni(),"useRoutes() may be used only in the context of a component.");let{navigator:r}=F.useContext(en),{matches:s}=F.useContext(Cn),c=s[s.length-1],o=c?c.params:{},m=c?c.pathname:"/",E=c?c.pathnameBase:"/",p=c&&c.route;{let k=p&&p.path||"";Wp(m,!p||k.endsWith("*")||k.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${m}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let T=Mn(),g;if(n){let k=typeof n=="string"?ei(n):n;at(E==="/"||((w=k.pathname)==null?void 0:w.startsWith(E)),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${E}" but pathname "${k.pathname}" was given in the \`location\` prop.`),g=k}else g=T;let A=g.pathname||"/",y=A;if(E!=="/"){let k=E.replace(/^\//,"").split("/");y="/"+A.replace(/^\//,"").split("/").slice(k.length).join("/")}let x=zp(e,{pathname:y});pn(p||x!=null,`No routes matched location "${g.pathname}${g.search}${g.hash}" `),pn(x==null||x[x.length-1].route.element!==void 0||x[x.length-1].route.Component!==void 0||x[x.length-1].route.lazy!==void 0,`Matched leaf route at location "${g.pathname}${g.search}${g.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let I=cy(x&&x.map(k=>Object.assign({},k,{params:Object.assign({},o,k.params),pathname:In([E,r.encodeLocation?r.encodeLocation(k.pathname.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:k.pathname]),pathnameBase:k.pathnameBase==="/"?E:In([E,r.encodeLocation?r.encodeLocation(k.pathnameBase.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:k.pathnameBase])})),s,a);return n&&I?F.createElement(Sr.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",unstable_mask:void 0,...g},navigationType:"POP"}},I):I}function iy(){let e=my(),n=G2(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),a=e instanceof Error?e.stack:null,r="rgba(200,200,200, 0.5)",s={padding:"0.5rem",backgroundColor:r},c={padding:"2px 4px",backgroundColor:r},o=null;return console.error("Error handled by React Router default ErrorBoundary:",e),o=F.createElement(F.Fragment,null,F.createElement("p",null,"💿 Hey developer 👋"),F.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",F.createElement("code",{style:c},"ErrorBoundary")," or"," ",F.createElement("code",{style:c},"errorElement")," prop on your route.")),F.createElement(F.Fragment,null,F.createElement("h2",null,"Unexpected Application Error!"),F.createElement("h3",{style:{fontStyle:"italic"}},n),a?F.createElement("pre",{style:s},a):null,o)}var ry=F.createElement(iy,null),Jp=class extends F.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,n){return n.location!==e.location||n.revalidation!=="idle"&&e.revalidation==="idle"?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error!==void 0?e.error:n.error,location:n.location,revalidation:e.revalidation||n.revalidation}}componentDidCatch(e,n){this.props.onError?this.props.onError(e,n):console.error("React Router caught the following error during render",e)}render(){let e=this.state.error;if(this.context&&typeof e=="object"&&e&&"digest"in e&&typeof e.digest=="string"){const a=ey(e.digest);a&&(e=a)}let n=e!==void 0?F.createElement(Cn.Provider,{value:this.props.routeContext},F.createElement(Gf.Provider,{value:e,children:this.props.component})):this.props.children;return this.context?F.createElement(ly,{error:e},n):n}};Jp.contextType=X2;var $o=new WeakMap;function ly({children:e,error:n}){let{basename:a}=F.useContext(en);if(typeof n=="object"&&n&&"digest"in n&&typeof n.digest=="string"){let r=$2(n.digest);if(r){let s=$o.get(n);if(s)throw s;let c=jp(r.location,a);if(qp&&!$o.get(n))if(c.isExternal||r.reloadDocument)window.location.href=c.absoluteURL||c.to;else{const o=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(c.to,{replace:r.replace}));throw $o.set(n,o),o}return F.createElement("meta",{httpEquiv:"refresh",content:`0;url=${c.absoluteURL||c.to}`})}}return e}function sy({routeContext:e,match:n,children:a}){let r=F.useContext(ti);return r&&r.static&&r.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=n.route.id),F.createElement(Cn.Provider,{value:e},a)}function cy(e,n=[],a){let r=a==null?void 0:a.state;if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(n.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let s=e,c=r==null?void 0:r.errors;if(c!=null){let T=s.findIndex(g=>g.route.id&&(c==null?void 0:c[g.route.id])!==void 0);at(T>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(c).join(",")}`),s=s.slice(0,Math.min(s.length,T+1))}let o=!1,m=-1;if(a&&r){o=r.renderFallback;for(let T=0;T=0?s=s.slice(0,m+1):s=[s[0]];break}}}}let E=a==null?void 0:a.onError,p=r&&E?(T,g)=>{var A,y;E(T,{location:r.location,params:((y=(A=r.matches)==null?void 0:A[0])==null?void 0:y.params)??{},unstable_pattern:Q2(r.matches),errorInfo:g})}:void 0;return s.reduceRight((T,g,A)=>{let y,x=!1,I=null,w=null;r&&(y=c&&g.route.id?c[g.route.id]:void 0,I=g.route.errorElement||ry,o&&(m<0&&A===0?(Wp("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),x=!0,w=null):m===A&&(x=!0,w=g.route.hydrateFallbackElement||null)));let k=n.concat(s.slice(0,A+1)),V=()=>{let Q;return y?Q=I:x?Q=w:g.route.Component?Q=F.createElement(g.route.Component,null):g.route.element?Q=g.route.element:Q=T,F.createElement(sy,{match:g,routeContext:{outlet:T,matches:k,isDataRoute:r!=null},children:Q})};return r&&(g.route.ErrorBoundary||g.route.errorElement||A===0)?F.createElement(Jp,{location:r.location,revalidation:r.revalidation,component:I,error:y,children:V(),routeContext:{outlet:null,matches:k,isDataRoute:!0},onError:p}):V()},null)}function Vf(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function oy(e){let n=F.useContext(ti);return at(n,Vf(e)),n}function fy(e){let n=F.useContext(ps);return at(n,Vf(e)),n}function hy(e){let n=F.useContext(Cn);return at(n,Vf(e)),n}function Xf(e){let n=hy(e),a=n.matches[n.matches.length-1];return at(a.route.id,`${e} can only be used on routes that contain a unique "id"`),a.route.id}function dy(){return Xf("useRouteId")}function my(){var r;let e=F.useContext(Gf),n=fy("useRouteError"),a=Xf("useRouteError");return e!==void 0?e:(r=n.errors)==null?void 0:r[a]}function py(){let{router:e}=oy("useNavigate"),n=Xf("useNavigate"),a=F.useRef(!1);return Kp(()=>{a.current=!0}),F.useCallback(async(s,c={})=>{pn(a.current,Xp),a.current&&(typeof s=="number"?await e.navigate(s):await e.navigate(s,{fromRouteId:n,...c}))},[e,n])}var D1={};function Wp(e,n,a){!n&&!D1[e]&&(D1[e]=!0,pn(!1,a))}F.memo(Ey);function Ey({routes:e,future:n,state:a,isStatic:r,onError:s}){return Zp(e,void 0,{state:a,isStatic:r,onError:s})}function gy({to:e,replace:n,state:a,relative:r}){at(ni()," may be used only in the context of a component.");let{static:s}=F.useContext(en);pn(!s," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:c}=F.useContext(Cn),{pathname:o}=Mn(),m=Qf(),E=ms(e,jf(c),o,r==="path"),p=JSON.stringify(E);return F.useEffect(()=>{m(JSON.parse(p),{replace:n,state:a,relative:r})},[m,p,r,n,a]),null}function or(e){at(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Ty({basename:e="/",children:n=null,location:a,navigationType:r="POP",navigator:s,static:c=!1,unstable_useTransitions:o}){at(!ni(),"You cannot render a inside another . You should never have more than one in your app.");let m=e.replace(/^\/*/,"/"),E=F.useMemo(()=>({basename:m,navigator:s,static:c,unstable_useTransitions:o,future:{}}),[m,s,c,o]);typeof a=="string"&&(a=ei(a));let{pathname:p="/",search:T="",hash:g="",state:A=null,key:y="default",unstable_mask:x}=a,I=F.useMemo(()=>{let w=uu(p,m);return w==null?null:{location:{pathname:w,search:T,hash:g,state:A,key:y,unstable_mask:x},navigationType:r}},[m,p,T,g,A,y,r,x]);return pn(I!=null,` is not able to match the URL "${p}${T}${g}" because it does not start with the basename, so the won't render anything.`),I==null?null:F.createElement(en.Provider,{value:E},F.createElement(Sr.Provider,{children:n,value:I}))}function by({children:e,location:n}){return ay(xf(e),n)}function xf(e,n=[]){let a=[];return F.Children.forEach(e,(r,s)=>{if(!F.isValidElement(r))return;let c=[...n,s];if(r.type===F.Fragment){a.push.apply(a,xf(r.props.children,c));return}at(r.type===or,`[${typeof r.type=="string"?r.type:r.type.name}] is not a component. All component children of must be a or `),at(!r.props.index||!r.props.children,"An index route cannot have child routes.");let o={id:r.props.id||c.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,middleware:r.props.middleware,loader:r.props.loader,action:r.props.action,hydrateFallbackElement:r.props.hydrateFallbackElement,HydrateFallback:r.props.HydrateFallback,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.hasErrorBoundary===!0||r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(o.children=xf(r.props.children,c)),a.push(o)}),a}var ts="get",ns="application/x-www-form-urlencoded";function Es(e){return typeof HTMLElement<"u"&&e instanceof HTMLElement}function yy(e){return Es(e)&&e.tagName.toLowerCase()==="button"}function _y(e){return Es(e)&&e.tagName.toLowerCase()==="form"}function Ay(e){return Es(e)&&e.tagName.toLowerCase()==="input"}function Sy(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Cy(e,n){return e.button===0&&(!n||n==="_self")&&!Sy(e)}function Of(e=""){return new URLSearchParams(typeof e=="string"||Array.isArray(e)||e instanceof URLSearchParams?e:Object.keys(e).reduce((n,a)=>{let r=e[a];return n.concat(Array.isArray(r)?r.map(s=>[a,s]):[[a,r]])},[]))}function Ny(e,n){let a=Of(e);return n&&n.forEach((r,s)=>{a.has(s)||n.getAll(s).forEach(c=>{a.append(s,c)})}),a}var Kl=null;function xy(){if(Kl===null)try{new FormData(document.createElement("form"),0),Kl=!1}catch{Kl=!0}return Kl}var Oy=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function ef(e){return e!=null&&!Oy.has(e)?(pn(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${ns}"`),null):e}function Ry(e,n){let a,r,s,c,o;if(_y(e)){let m=e.getAttribute("action");r=m?uu(m,n):null,a=e.getAttribute("method")||ts,s=ef(e.getAttribute("enctype"))||ns,c=new FormData(e)}else if(yy(e)||Ay(e)&&(e.type==="submit"||e.type==="image")){let m=e.form;if(m==null)throw new Error('Cannot submit a - )} - {!conversationEnded && quickReplies.length > 0 && ( -
- {quickReplies.map((quickReply, index) => ( - - ))} -
- )} - {isLoading ? null : (quickReplies.length === 0 || forceInput) && !conversationEnded && - ( - - - - )} - - {conversationEnded &&
-
Conversation Ended
- -
} - - - - ); -}; - -export default Chat; diff --git a/src/api/chat-api.ts b/src/api/chat-api.ts new file mode 100644 index 0000000..f92b232 --- /dev/null +++ b/src/api/chat-api.ts @@ -0,0 +1,273 @@ +/* ────────────────────────────────────────────── + EDDI Chat — API Layer + Pure fetch-based, zero external dependencies. + v6: simplified paths — all conversation-scoped ops use only conversationId. + ────────────────────────────────────────────── */ + +import type { + ConversationSnapshot, + SSEEvent, + SSEEventType, +} from "@/types"; + +let _baseUrl = ""; + +/** Set the API base URL (e.g. from ChatConfig). Call once at startup. */ +export function setBaseUrl(url: string): void { + _baseUrl = url.replace(/\/$/, ""); +} + +function buildUrl(path: string): string { + return `${_baseUrl}${path}`; +} + +/* ─── Conversation lifecycle ─────────────────── */ + +/** + * Start a new conversation. + * Returns the conversation ID extracted from the Location header. + */ +export async function startConversation( + _environment: string, + agentId: string, + userId?: string, +): Promise { + const params = userId ? `?userId=${encodeURIComponent(userId)}` : ""; + const res = await fetch( + buildUrl(`/agents/${agentId}/start${params}`), + { method: "POST" }, + ); + if (!res.ok) throw new Error(`Failed to start conversation: ${res.statusText}`); + + const location = res.headers.get("Location") ?? ""; + const segments = location.split("/"); + const last = segments[segments.length - 1] || location; + return last.split("?")[0]; +} + +/** + * Read an existing conversation (GET). + * Used after start (to pick up welcome messages) and to resume. + */ +export async function readConversation( + _environment: string, + _agentId: string, + conversationId: string, + currentStepOnly = false, +): Promise { + const params = new URLSearchParams({ + returnDetailed: "false", + returnCurrentStepOnly: String(currentStepOnly), + }); + const res = await fetch( + buildUrl(`/agents/${conversationId}?${params}`), + ); + if (!res.ok) throw new Error(`Failed to read conversation: ${res.statusText}`); + return res.json(); +} + +/** + * Send a message (non-streaming) to a direct agent. + * Returns the conversation snapshot with the agent's reply in `conversationOutputs`. + * When `context` is provided, sends as JSON `InputData` instead of plain text. + */ +export async function sendMessage( + _environment: string, + _agentId: string, + conversationId: string, + message: string, + userId?: string, + context?: Record, +): Promise { + const params = new URLSearchParams({ + returnDetailed: "false", + returnCurrentStepOnly: "true", + }); + if (userId) params.set("userId", userId); + + const hasContext = context && Object.keys(context).length > 0; + + const res = await fetch( + buildUrl(`/agents/${conversationId}?${params}`), + { + method: "POST", + headers: { + "Content-Type": hasContext ? "application/json" : "text/plain", + }, + body: hasContext + ? JSON.stringify({ input: message, context }) + : message, + }, + ); + if (!res.ok) throw new Error(`Failed to send message: ${res.statusText}`); + return res.json(); +} + +/** + * Send a message via SSE streaming. + * Yields parsed SSE events as they arrive. + */ +export async function* sendMessageStreaming( + _environment: string, + _agentId: string, + conversationId: string, + message: string, + context?: Record, +): AsyncGenerator { + const body: Record = { input: message }; + if (context && Object.keys(context).length > 0) { + body.context = context; + } + + const res = await fetch( + buildUrl(`/agents/${conversationId}/stream`), + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }, + ); + + if (!res.ok) throw new Error(`Streaming failed: ${res.statusText}`); + + const reader = res.body?.getReader(); + if (!reader) throw new Error("No readable stream"); + + const decoder = new TextDecoder(); + let buffer = ""; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + const parts = buffer.split("\n\n"); + buffer = parts.pop() ?? ""; + + for (const part of parts) { + if (!part.trim()) continue; + let eventType: SSEEventType = "token"; + let eventData = ""; + + for (const line of part.split("\n")) { + if (line.startsWith("event:")) { + eventType = line.slice(6).trim() as SSEEventType; + } else if (line.startsWith("data:")) { + eventData = line.slice(5).trim(); + } + } + + if (eventData || eventType) { + yield { type: eventType, data: eventData }; + } + } + } + } finally { + reader.releaseLock(); + } +} + +/* ─── Managed agent endpoints ──────────────────── */ + +/** + * Send a message to a managed agent (intent-based routing). + * v6: path changed from /managedagents to /agents/managed + */ +export async function sendManagedAgentMessage( + intent: string, + userId: string, + message?: string, +): Promise { + const params = new URLSearchParams({ + returnDetailed: "false", + returnCurrentStepOnly: "true", + }); + const url = buildUrl(`/agents/managed/${intent}/${userId}?${params}`); + + if (message) { + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ input: message }), + }); + if (!res.ok) throw new Error(`Failed to send message: ${res.statusText}`); + return res.json(); + } else { + const res = await fetch(url, { method: "GET" }); + if (!res.ok) throw new Error(`Failed to load conversation: ${res.statusText}`); + return res.json(); + } +} + +/** + * End a conversation. + */ +export async function endConversation( + conversationId: string, +): Promise { + const res = await fetch( + buildUrl(`/agents/${conversationId}/endConversation`), + { method: "POST" }, + ); + if (!res.ok) throw new Error(`Failed to end conversation: ${res.statusText}`); +} + +/* ─── Undo / Redo ────────────────────────────── */ + +/** + * Undo the last conversation step. + */ +export async function undoConversation( + _environment: string, + _agentId: string, + conversationId: string, +): Promise { + const res = await fetch( + buildUrl(`/agents/${conversationId}/undo`), + { method: "POST" }, + ); + if (!res.ok) throw new Error(`Failed to undo: ${res.statusText}`); + return res.json(); +} + +/** + * Redo a previously undone conversation step. + */ +export async function redoConversation( + _environment: string, + _agentId: string, + conversationId: string, +): Promise { + const res = await fetch( + buildUrl(`/agents/${conversationId}/redo`), + { method: "POST" }, + ); + if (!res.ok) throw new Error(`Failed to redo: ${res.statusText}`); + return res.json(); +} + +/* ─── Agent descriptor ─────────────────────────── */ + +/** + * Fetch the agent document descriptor to get the agent's display name. + * Uses the GET /agentstore/agents/:agentId endpoint. + */ +export async function fetchAgentDescriptor( + agentId: string, +): Promise<{ name?: string; description?: string }> { + const res = await fetch( + buildUrl(`/agentstore/agents/${agentId}`), + ); + if (!res.ok) return {}; + try { + const data = await res.json(); + return { + name: data?.resource?.name ?? data?.name, + description: data?.resource?.description ?? data?.description, + }; + } catch { + return {}; + } +} diff --git a/src/api/demo-api.test.ts b/src/api/demo-api.test.ts new file mode 100644 index 0000000..22f1e66 --- /dev/null +++ b/src/api/demo-api.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from "vitest"; +import { demoStartConversation, demoSendMessageStreaming, demoGetQuickReplies } from "./demo-api"; + +describe("demo-api", () => { + describe("demoStartConversation", () => { + it("returns a conversationId, welcomeMessage, and quickReplies", async () => { + const result = await demoStartConversation(); + expect(result.conversationId).toBeTruthy(); + expect(result.welcomeMessage).toBeDefined(); + expect(result.welcomeMessage.role).toBe("agent"); + expect(result.welcomeMessage.content).toBeTruthy(); + expect(result.quickReplies).toBeDefined(); + expect(result.quickReplies.length).toBeGreaterThan(0); + }); + }); + + describe("demoSendMessageStreaming", () => { + it("yields SSE events for a known message", async () => { + const events: Array<{ type: string; data: string }> = []; + for await (const event of demoSendMessageStreaming("Tell me about EDDI")) { + events.push(event); + } + // Should have at least a thinking event, some tokens, and a done event + expect(events.length).toBeGreaterThan(0); + expect(events.some((e) => e.type === "done")).toBe(true); + }, 15000); + + it("yields events for an unknown message with a default response", async () => { + const events: Array<{ type: string; data: string }> = []; + for await (const event of demoSendMessageStreaming("random gibberish")) { + events.push(event); + } + expect(events.length).toBeGreaterThan(0); + expect(events.some((e) => e.type === "done")).toBe(true); + }); + }); + + describe("demoGetQuickReplies", () => { + it("returns an array of quick replies", () => { + const replies = demoGetQuickReplies("any-id"); + expect(Array.isArray(replies)).toBe(true); + expect(replies.length).toBeGreaterThan(0); + expect(replies[0]).toHaveProperty("value"); + }); + }); +}); diff --git a/src/api/demo-api.ts b/src/api/demo-api.ts new file mode 100644 index 0000000..d2f8a5b --- /dev/null +++ b/src/api/demo-api.ts @@ -0,0 +1,210 @@ +/* ────────────────────────────────────────────── + Demo Mode — Mock API for testing all UI features + without a running EDDI backend. + + Activate by visiting: /chat/demo/showcase + ────────────────────────────────────────────── */ + +import type { ChatMessage, QuickReply, SSEEvent } from "@/types"; + +/** Simulated delay */ +const delay = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +/* ─── Welcome message ─────────────────────────── */ + +const WELCOME_MESSAGE = `👋 **Welcome to EDDI!** + +I'm your AI assistant. Here's what I can do: + +- Answer questions using **AI-powered reasoning** +- Render \`inline code\` and code blocks +- Display math: $E = mc^2$ +- Format **bold**, *italic*, and ~~strikethrough~~ text +- Show [links](https://labs.ai) and lists + +Try typing a message or use the quick replies below!`; + +/* ─── Mock responses ──────────────────────────── */ + +interface MockResponse { + text: string; + quickReplies?: QuickReply[]; + thinkFirst?: boolean; +} + +const RESPONSES: Record = { + "Tell me about EDDI": { + text: `**EDDI** (Enhanced Dialog Driven Intelligence) is an open-source conversational AI platform. + +### Key Features +| Feature | Description | +|---------|-------------| +| Multi-agent orchestration | Run multiple agents in parallel | +| Plugin architecture | Extensible with custom behaviors | +| SSE Streaming | Real-time token-by-token responses | +| REST API | Full conversation management | + +EDDI supports **LLM integration**, behavior rules, HTTP callouts, and much more.`, + quickReplies: [ + { value: "Show me code" }, + { value: "What about streaming?" }, + ], + }, + + "Show me code": { + text: `Here's a simple example of using the EDDI API: + +\`\`\`typescript +// Start a conversation +const response = await fetch('/agents/myAgent/start', { + method: 'POST' +}); +const conversationId = response.headers.get('Location'); + +// Send a message +const result = await fetch( + \`/agents/\${conversationId}\`, + { + method: 'POST', + headers: { 'Content-Type': 'text/plain' }, + body: 'Hello, EDDI!' + } +); +\`\`\` + +The API returns a \`ConversationMemorySnapshot\` with the agent's response.`, + quickReplies: [ + { value: "What about streaming?" }, + { value: "Tell me more" }, + ], + }, + + "What about streaming?": { + text: `EDDI supports **Server-Sent Events (SSE)** for real-time streaming responses. + +When streaming is enabled, the agent sends tokens one at a time: + +1. 🧠 **Thinking phase** — the agent reasons about your input +2. ⚡ **Streaming phase** — tokens arrive incrementally +3. ✅ **Done** — the complete response is ready + +This gives users immediate feedback instead of waiting for the entire response.`, + thinkFirst: true, + quickReplies: [ + { value: "Show me a list" }, + { value: "Tell me about EDDI" }, + ], + }, + + "Show me a list": { + text: `Here's what EDDI v6 brings: + +### New in v6 +- ✅ Keycloak authentication integration +- ✅ PostgreSQL support (alongside MongoDB) +- ✅ MCP (Model Context Protocol) integration +- ✅ Improved chat UI with streaming +- ✅ Dark/light theme support +- 🔄 Multi-agent orchestration (coming soon) + +### Architecture Improvements +1. **DB-agnostic storage** — choose PostgreSQL or MongoDB +2. **Message queue infrastructure** — NATS JetStream +3. **Decomposed engine** — modular pipeline stages +4. **Enhanced REST API** — consistent JSON responses`, + quickReplies: [ + { value: "Tell me about EDDI" }, + { value: "Thanks!" }, + ], + }, + + "Thanks!": { + text: `You're welcome! 😊 + +Feel free to ask me anything else, or try these:`, + quickReplies: [ + { value: "Tell me about EDDI" }, + { value: "Show me code" }, + { value: "Show me a list" }, + ], + }, +}; + +function getResponse(input: string): MockResponse { + // Case-insensitive match + const key = Object.keys(RESPONSES).find( + (k) => k.toLowerCase() === input.toLowerCase(), + ); + if (key) return RESPONSES[key]; + + // Default response with thinking + return { + text: `I received your message: *"${input}"* + +This is a **demo mode** response. In production, this would be processed by the EDDI backend AI engine. + +Try one of the suggested quick replies to see more features!`, + thinkFirst: true, + quickReplies: [ + { value: "Tell me about EDDI" }, + { value: "Show me code" }, + { value: "What about streaming?" }, + { value: "Show me a list" }, + ], + }; +} + +/* ─── Demo API functions ──────────────────────── */ + +export function isDemoMode(environment?: string, agentId?: string): boolean { + return environment === "demo" && agentId === "showcase"; +} + +export async function demoStartConversation(): Promise<{ + conversationId: string; + welcomeMessage: ChatMessage; + quickReplies: QuickReply[]; +}> { + await delay(400); + return { + conversationId: `demo-conv-${Date.now()}`, + welcomeMessage: { + id: `agent-welcome-${Date.now()}`, + role: "agent", + content: WELCOME_MESSAGE, + timestamp: Date.now(), + }, + quickReplies: [ + { value: "Tell me about EDDI" }, + { value: "Show me code" }, + { value: "What about streaming?" }, + { value: "Show me a list" }, + ], + }; +} + +export async function* demoSendMessageStreaming( + message: string, +): AsyncGenerator { + const response = getResponse(message); + + // Simulate thinking phase + if (response.thinkFirst) { + yield { type: "thinking", data: "" }; + await delay(1500); + } + + // Stream tokens word by word + const words = response.text.split(/(\s+)/); + for (const word of words) { + yield { type: "token", data: word }; + await delay(25 + Math.random() * 35); + } + + yield { type: "done", data: "" }; +} + +export function demoGetQuickReplies(message: string): QuickReply[] { + const response = getResponse(message); + return response.quickReplies ?? []; +} diff --git a/src/components/ChatHeader.test.tsx b/src/components/ChatHeader.test.tsx new file mode 100644 index 0000000..e69d35d --- /dev/null +++ b/src/components/ChatHeader.test.tsx @@ -0,0 +1,55 @@ +/* ────────────────────────────────────────────── + ChatHeader Tests + ────────────────────────────────────────────── */ + +import { describe, it, expect } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { ChatProvider } from "@/store/chat-store"; +import { ChatHeader } from "./ChatHeader"; + +function renderHeader(config = {}) { + return render( + + + , + ); +} + +describe("ChatHeader", () => { + it("renders the logo image by default", () => { + renderHeader(); + const logo = screen.getByRole("img", { name: "EDDI" }); + expect(logo).toBeInTheDocument(); + }); + + it("renders the theme toggle button", () => { + renderHeader(); + expect(screen.getByTestId("theme-toggle")).toBeInTheDocument(); + }); + + it("hides the logo when showLogo is false (no branding text either)", () => { + renderHeader({ showLogo: false }); + expect(screen.queryByRole("img", { name: "EDDI" })).not.toBeInTheDocument(); + // No fallback "EDDI" text either + expect(screen.queryByText("EDDI")).not.toBeInTheDocument(); + }); + + it("does not show agent name when no agent name is set in state", () => { + const { container } = renderHeader(); + expect(container.querySelector(".chat-header__agent-name")).not.toBeInTheDocument(); + }); + + it("renders custom logo URL", () => { + renderHeader({ logoUrl: "/custom-logo.svg" }); + const logo = screen.getByRole("img", { name: "EDDI" }); + expect(logo).toHaveAttribute("src", "/custom-logo.svg"); + }); + + it("clicking theme toggle calls setTheme", () => { + renderHeader(); + const toggle = screen.getByTestId("theme-toggle"); + fireEvent.click(toggle); + // Theme changes are reflected on the document element + expect(toggle).toBeInTheDocument(); + }); +}); diff --git a/src/components/ChatHeader.tsx b/src/components/ChatHeader.tsx new file mode 100644 index 0000000..4c47112 --- /dev/null +++ b/src/components/ChatHeader.tsx @@ -0,0 +1,51 @@ +/* ────────────────────────────────────────────── + ChatHeader — Branding (logo/title/agent name) + + theme toggle. Undo/redo/restart moved to input area. + ────────────────────────────────────────────── */ + +import { useChatState } from "@/store/chat-store"; +import { useTheme, type ThemeMode } from "@/hooks/useTheme"; + +export function ChatHeader() { + const { config, agentName } = useChatState(); + const { setTheme } = useTheme(config.theme ?? "dark"); + + const cycleTheme = () => { + const current = document.documentElement.getAttribute("data-theme"); + const next: ThemeMode = current === "dark" ? "light" : "dark"; + setTheme(next); + }; + + // Determine what to show in the branding area + const showLogo = config.showLogo !== false; + const showAgentName = config.showAgentName !== false && !!agentName; + + return ( +
+
+ {showLogo && ( + {config.title + )} + {showAgentName && ( + {agentName} + )} +
+ +
+ {/* Theme toggle */} + +
+
+ ); +} diff --git a/src/components/ChatInput.test.tsx b/src/components/ChatInput.test.tsx new file mode 100644 index 0000000..98817e2 --- /dev/null +++ b/src/components/ChatInput.test.tsx @@ -0,0 +1,61 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { ChatInput } from "./ChatInput"; +import { ChatProvider } from "@/store/chat-store"; + +function renderInput(props = {}) { + const onSend = vi.fn(); + const result = render( + + + , + ); + return { ...result, onSend }; +} + +describe("ChatInput", () => { + it("renders a textarea and send button", () => { + renderInput(); + expect(screen.getByTestId("chat-input")).toBeInTheDocument(); + expect(screen.getByTestId("chat-send")).toBeInTheDocument(); + }); + + it("calls onSend on Enter key", () => { + const { onSend } = renderInput(); + const textarea = screen.getByTestId("chat-input"); + fireEvent.change(textarea, { target: { value: "Hello" } }); + fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false }); + expect(onSend).toHaveBeenCalledWith("Hello", false); + }); + + it("does NOT send on Shift+Enter", () => { + const { onSend } = renderInput(); + const textarea = screen.getByTestId("chat-input"); + fireEvent.change(textarea, { target: { value: "Hello" } }); + fireEvent.keyDown(textarea, { key: "Enter", shiftKey: true }); + expect(onSend).not.toHaveBeenCalled(); + }); + + it("does NOT send when input is empty", () => { + const { onSend } = renderInput(); + const textarea = screen.getByTestId("chat-input"); + fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false }); + expect(onSend).not.toHaveBeenCalled(); + }); + + it("does NOT send when disabled", () => { + const { onSend } = renderInput({ disabled: true }); + const textarea = screen.getByTestId("chat-input"); + fireEvent.change(textarea, { target: { value: "Hello" } }); + fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false }); + expect(onSend).not.toHaveBeenCalled(); + }); + + it("clears input after send", () => { + renderInput(); + const textarea = screen.getByTestId("chat-input") as HTMLTextAreaElement; + fireEvent.change(textarea, { target: { value: "Hello" } }); + fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false }); + expect(textarea.value).toBe(""); + }); +}); diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx new file mode 100644 index 0000000..32ab90f --- /dev/null +++ b/src/components/ChatInput.tsx @@ -0,0 +1,130 @@ +/* ────────────────────────────────────────────── + ChatInput — Auto-growing textarea + send button + With 🔒 secret mode toggle for client-initiated secret input. + ────────────────────────────────────────────── */ + +import { useState, useRef, useCallback, type KeyboardEvent } from "react"; +import { useChatState, useChatDispatch } from "@/store/chat-store"; + +interface ChatInputProps { + onSend: (message: string, isSecret?: boolean) => void; + disabled?: boolean; +} + +export function ChatInput({ onSend, disabled }: ChatInputProps) { + const { isProcessing, config, isSecretMode } = useChatState(); + const dispatch = useChatDispatch(); + const [value, setValue] = useState(""); + const [secretVisible, setSecretVisible] = useState(false); + const textareaRef = useRef(null); + + const handleSend = useCallback(() => { + const trimmed = value.trim(); + if (!trimmed || disabled || isProcessing) return; + onSend(trimmed, isSecretMode); + setValue(""); + if (isSecretMode) { + dispatch({ type: "TOGGLE_SECRET_MODE" }); + setSecretVisible(false); + } + if (textareaRef.current) { + textareaRef.current.style.height = "auto"; + } + }, [value, disabled, isProcessing, isSecretMode, onSend, dispatch]); + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }, + [handleSend], + ); + + const handleInput = useCallback(() => { + const el = textareaRef.current; + if (!el) return; + el.style.height = "auto"; + el.style.height = `${Math.min(el.scrollHeight, 150)}px`; + }, []); + + const toggleSecretMode = useCallback(() => { + dispatch({ type: "TOGGLE_SECRET_MODE" }); + setSecretVisible(false); + }, [dispatch]); + + const canSend = value.trim().length > 0 && !disabled && !isProcessing; + + return ( +
+ {/* 🔒 Secret mode toggle */} + + + {isSecretMode ? ( + /* Secret mode: password input with eye toggle */ +
+ setValue(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Enter secret value..." + disabled={disabled} + className="chat-input__secret-field" + autoComplete="off" + /> + +
+ ) : ( + /* Normal mode: auto-growing textarea */ +