diff --git a/.gitattributes b/.gitattributes index 0fc54157abd7d0b1fa93f5c4a7d4bbf7a0585a24..e336abed579bf132789e11d5832590687d9efbcd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -46,3 +46,4 @@ docs/usage/llms/screenshots/2_select_model.png filter=lfs diff=lfs merge=lfs -te docs/usage/llms/screenshots/4_set_context_window.png filter=lfs diff=lfs merge=lfs -text docs/usage/llms/screenshots/5_copy_url.png filter=lfs diff=lfs merge=lfs -text evaluation/static/example_task_1.png filter=lfs diff=lfs merge=lfs -text +frontend/src/assets/logo.png filter=lfs diff=lfs merge=lfs -text diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a6ebbd1a6c98a045ffbb4e74a448bb932f727047 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,254 @@ +# Getting Started with the OpenHands Frontend + +## Overview + +This is the frontend of the OpenHands project. It is a React application that provides a web interface for the OpenHands project. + +## Tech Stack + +- Remix SPA Mode (React + Vite + React Router) +- TypeScript +- Redux +- TanStack Query +- Tailwind CSS +- i18next +- React Testing Library +- Vitest +- Mock Service Worker + +## Getting Started + +### Prerequisites + +- Node.js 20.x or later +- `npm`, `bun`, or any other package manager that supports the `package.json` file + +### Installation + +```sh +# Clone the repository +git clone https://github.com/All-Hands-AI/OpenHands.git + +# Change the directory to the frontend +cd OpenHands/frontend + +# Install the dependencies +npm install +``` + +### Running the Application in Development Mode + +We use `msw` to mock the backend API. To start the application with the mocked backend, run the following command: + +```sh +npm run dev +``` + +This will start the application in development mode. Open [http://localhost:3001](http://localhost:3001) to view it in the browser. + +**NOTE: The backend is _partially_ mocked using `msw`. Therefore, some features may not work as they would with the actual backend.** + +See the [Development.md](../Development.md) for extra tips on how to run in development mode. + +### Running the Application with the Actual Backend (Production Mode) + +To run the application with the actual backend: + +```sh +# Build the application from the root directory +make build + +# Start the application +make run +``` +Or to run backend and frontend separately. + +```sh +# Start the backend from the root directory +make start-backend + +# Serve the frontend +make start-frontend or +cd frontend && npm start -- --port 3001 +``` + +Start frontend with Mock Service Worker (MSW), see testing for more info. +```sh +npm run dev:mock or npm run dev:mock:saas +``` + +### Environment Variables + +The frontend application uses the following environment variables: + +| Variable | Description | Default Value | +| --------------------------- | ---------------------------------------------------------------------- | ---------------- | +| `VITE_BACKEND_BASE_URL` | The backend hostname without protocol (used for WebSocket connections) | `localhost:3000` | +| `VITE_BACKEND_HOST` | The backend host with port for API connections | `127.0.0.1:3000` | +| `VITE_MOCK_API` | Enable/disable API mocking with MSW | `false` | +| `VITE_MOCK_SAAS` | Simulate SaaS mode in development | `false` | +| `VITE_USE_TLS` | Use HTTPS/WSS for backend connections | `false` | +| `VITE_FRONTEND_PORT` | Port to run the frontend application | `3001` | +| `VITE_INSECURE_SKIP_VERIFY` | Skip TLS certificate verification | `false` | +| `VITE_GITHUB_TOKEN` | GitHub token for repository access (used in some tests) | - | + +You can create a `.env` file in the frontend directory with these variables based on the `.env.sample` file. + +### Project Structure + +```sh +frontend +├── __tests__ # Tests +├── public +├── src +│ ├── api # API calls +│ ├── assets +│ ├── components +│ ├── context # Local state management +│ ├── hooks # Custom hooks +│ ├── i18n # Internationalization +│ ├── mocks # MSW mocks for development +│ ├── routes # React Router file-based routes +│ ├── services +│ ├── state # Redux state management +│ ├── types +│ ├── utils # Utility/helper functions +│ └── root.tsx # Entry point +└── .env.sample # Sample environment variables +``` + +#### Components + +Components are organized into folders based on their **domain**, **feature**, or **shared functionality**. + +```sh +components +├── features # Domain-specific components +├── layout +├── modals +└── ui # Shared UI components +``` + +### Features + +- Real-time updates with WebSockets +- Internationalization +- Router data loading with Remix +- User authentication with GitHub OAuth (if saas mode is enabled) + +## Testing + +### Testing Framework and Tools + +We use the following testing tools: +- **Test Runner**: Vitest +- **Rendering**: React Testing Library +- **User Interactions**: @testing-library/user-event +- **API Mocking**: [Mock Service Worker (MSW)](https://mswjs.io/) +- **Code Coverage**: Vitest with V8 coverage + +### Running Tests + +To run all tests: +```sh +npm run test +``` + +To run tests with coverage: +```sh +npm run test:coverage +``` + +### Testing Best Practices + +1. **Component Testing** + - Test components in isolation + - Use our custom [`renderWithProviders()`](https://github.com/All-Hands-AI/OpenHands/blob/ce26f1c6d3feec3eedf36f823dee732b5a61e517/frontend/test-utils.tsx#L56-L85) that wraps the components we want to test in our providers. It is especially useful for components that use Redux + - Use `render()` from React Testing Library to render components + - Prefer querying elements by role, label, or test ID over CSS selectors + - Test both rendering and interaction scenarios + +2. **User Event Simulation** + - Use `userEvent` for simulating realistic user interactions + - Test keyboard events, clicks, typing, and other user actions + - Handle edge cases like disabled states, empty inputs, etc. + +3. **Mocking** + - We test components that make network requests by mocking those requests with Mock Service Worker (MSW) + - Use `vi.fn()` to create mock functions for callbacks and event handlers + - Mock external dependencies and API calls (more info)[https://mswjs.io/docs/getting-started] + - Verify mock function calls using `.toHaveBeenCalledWith()`, `.toHaveBeenCalledTimes()` + +4. **Accessibility Testing** + - Use `toBeInTheDocument()` to check element presence + - Test keyboard navigation and screen reader compatibility + - Verify correct ARIA attributes and roles + +5. **State and Prop Testing** + - Test component behavior with different prop combinations + - Verify state changes and conditional rendering + - Test error states and loading scenarios + +6. **Internationalization (i18n) Testing** + - Test translation keys and placeholders + - Verify text rendering across different languages + +Example Test Structure: +```typescript +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, it, expect, vi } from "vitest"; + +describe("ComponentName", () => { + it("should render correctly", () => { + render(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + + it("should handle user interactions", async () => { + const mockCallback = vi.fn(); + const user = userEvent.setup(); + + render(); + const button = screen.getByRole("button"); + + await user.click(button); + expect(mockCallback).toHaveBeenCalledOnce(); + }); +}); +``` + +### Example Tests in the Codebase + +For real-world examples of testing, check out these test files: + +1. **Chat Input Component Test**: + [`__tests__/components/chat/chat-input.test.tsx`](https://github.com/All-Hands-AI/OpenHands/blob/main/frontend/__tests__/components/chat/chat-input.test.tsx) + - Demonstrates comprehensive testing of a complex input component + - Covers various scenarios like submission, disabled states, and user interactions + +2. **File Explorer Component Test**: + [`__tests__/components/file-explorer/file-explorer.test.tsx`](https://github.com/All-Hands-AI/OpenHands/blob/main/frontend/__tests__/components/file-explorer/file-explorer.test.tsx) + - Shows testing of a more complex component with multiple interactions + - Illustrates testing of nested components and state management + +### Test Coverage + +- Aim for high test coverage, especially for critical components +- Focus on testing different scenarios and edge cases +- Use code coverage reports to identify untested code paths + +### Continuous Integration + +Tests are automatically run during: +- Pre-commit hooks +- Pull request checks +- CI/CD pipeline + +## Contributing + +Please read the [CONTRIBUTING.md](../CONTRIBUTING.md) file for details on our code of conduct, and the process for submitting pull requests to us. + +## Troubleshooting + +TODO diff --git a/frontend/__tests__/api/file-service/file-service.api.test.ts b/frontend/__tests__/api/file-service/file-service.api.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..12c0cd9a2e13fb1e2cf231143fde34700b3768e8 --- /dev/null +++ b/frontend/__tests__/api/file-service/file-service.api.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { FileService } from "#/api/file-service/file-service.api"; +import { + FILE_VARIANTS_1, + FILE_VARIANTS_2, +} from "#/mocks/file-service-handlers"; + +/** + * File service API tests. The actual API calls are mocked using MSW. + * You can find the mock handlers in `frontend/src/mocks/file-service-handlers.ts`. + */ + +describe("FileService", () => { + it("should get a list of files", async () => { + await expect(FileService.getFiles("test-conversation-id")).resolves.toEqual( + FILE_VARIANTS_1, + ); + + await expect( + FileService.getFiles("test-conversation-id-2"), + ).resolves.toEqual(FILE_VARIANTS_2); + }); + + it("should get content of a file", async () => { + await expect( + FileService.getFile("test-conversation-id", "file1.txt"), + ).resolves.toEqual("Content of file1.txt"); + }); +}); diff --git a/frontend/__tests__/components/browser.test.tsx b/frontend/__tests__/components/browser.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1f768535dacba40981ddd2fe09c00ce5ce6f5ddb --- /dev/null +++ b/frontend/__tests__/components/browser.test.tsx @@ -0,0 +1,86 @@ +import { describe, it, expect, afterEach, vi } from "vitest"; +import { screen, render } from "@testing-library/react"; +import React from "react"; + +// Mock modules before importing the component +vi.mock("react-router", async () => { + const actual = await vi.importActual("react-router"); + return { + ...(actual as object), + useParams: () => ({ conversationId: "test-conversation-id" }), + }; +}); + +vi.mock("#/context/conversation-context", () => ({ + useConversation: () => ({ conversationId: "test-conversation-id" }), + ConversationProvider: ({ children }: { children: React.ReactNode }) => children, +})); + +vi.mock("react-i18next", async () => { + const actual = await vi.importActual("react-i18next"); + return { + ...(actual as object), + useTranslation: () => ({ + t: (key: string) => key, + i18n: { + changeLanguage: () => new Promise(() => {}), + }, + }), + }; +}); + +// Mock redux +const mockDispatch = vi.fn(); +let mockBrowserState = { + url: "https://example.com", + screenshotSrc: "", +}; + +vi.mock("react-redux", async () => { + const actual = await vi.importActual("react-redux"); + return { + ...actual, + useDispatch: () => mockDispatch, + useSelector: () => mockBrowserState, + }; +}); + +// Import the component after all mocks are set up +import { BrowserPanel } from "#/components/features/browser/browser"; + +describe("Browser", () => { + afterEach(() => { + vi.clearAllMocks(); + // Reset the mock state + mockBrowserState = { + url: "https://example.com", + screenshotSrc: "", + }; + }); + + it("renders a message if no screenshotSrc is provided", () => { + // Set the mock state for this test + mockBrowserState = { + url: "https://example.com", + screenshotSrc: "", + }; + + render(); + + // i18n empty message key + expect(screen.getByText("BROWSER$NO_PAGE_LOADED")).toBeInTheDocument(); + }); + + it("renders the url and a screenshot", () => { + // Set the mock state for this test + mockBrowserState = { + url: "https://example.com", + screenshotSrc: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN0uGvyHwAFCAJS091fQwAAAABJRU5ErkJggg==", + }; + + render(); + + expect(screen.getByText("https://example.com")).toBeInTheDocument(); + expect(screen.getByAltText("BROWSER$SCREENSHOT_ALT")).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/components/buttons/copy-to-clipboard.test.tsx b/frontend/__tests__/components/buttons/copy-to-clipboard.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5777f584de7531627dec84d701302a787ea8e2d5 --- /dev/null +++ b/frontend/__tests__/components/buttons/copy-to-clipboard.test.tsx @@ -0,0 +1,40 @@ +import { render, screen } from "@testing-library/react"; +import { test, expect, describe, vi } from "vitest"; +import { CopyToClipboardButton } from "#/components/shared/buttons/copy-to-clipboard-button"; + +// Mock react-i18next +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +describe("CopyToClipboardButton", () => { + test("should have localized aria-label", () => { + render( + {}} + mode="copy" + /> + ); + + const button = screen.getByTestId("copy-to-clipboard"); + expect(button).toHaveAttribute("aria-label", "BUTTON$COPY"); + }); + + test("should have localized aria-label when copied", () => { + render( + {}} + mode="copied" + /> + ); + + const button = screen.getByTestId("copy-to-clipboard"); + expect(button).toHaveAttribute("aria-label", "BUTTON$COPIED"); + }); +}); diff --git a/frontend/__tests__/components/chat-message.test.tsx b/frontend/__tests__/components/chat-message.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c11b2df1b69f5afb4e15af82c7c4bfa9e0b92a48 --- /dev/null +++ b/frontend/__tests__/components/chat-message.test.tsx @@ -0,0 +1,72 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, it, expect } from "vitest"; +import { ChatMessage } from "#/components/features/chat/chat-message"; + +describe("ChatMessage", () => { + it("should render a user message", () => { + render(); + expect(screen.getByTestId("user-message")).toBeInTheDocument(); + expect(screen.getByText("Hello, World!")).toBeInTheDocument(); + }); + + it.todo("should render an assistant message"); + + it.skip("should support code syntax highlighting", () => { + const code = "```js\nconsole.log('Hello, World!')\n```"; + render(); + + // SyntaxHighlighter breaks the code blocks into "tokens" + expect(screen.getByText("console")).toBeInTheDocument(); + expect(screen.getByText("log")).toBeInTheDocument(); + expect(screen.getByText("'Hello, World!'")).toBeInTheDocument(); + }); + + it("should render the copy to clipboard button when the user hovers over the message", async () => { + const user = userEvent.setup(); + render(); + const message = screen.getByText("Hello, World!"); + + expect(screen.getByTestId("copy-to-clipboard")).not.toBeVisible(); + + await user.hover(message); + + expect(screen.getByTestId("copy-to-clipboard")).toBeVisible(); + }); + + it("should copy content to clipboard", async () => { + const user = userEvent.setup(); + render(); + const copyToClipboardButton = screen.getByTestId("copy-to-clipboard"); + + await user.click(copyToClipboardButton); + + await waitFor(() => + expect(navigator.clipboard.readText()).resolves.toBe("Hello, World!"), + ); + }); + + it("should display an error toast if copying content to clipboard fails", async () => {}); + + it("should render a component passed as a prop", () => { + function Component() { + return
Custom Component
; + } + render( + + + , + ); + expect(screen.getByTestId("custom-component")).toBeInTheDocument(); + }); + + it("should apply correct styles to inline code", () => { + render( + , + ); + const codeElement = screen.getByText("inline code"); + + expect(codeElement.tagName.toLowerCase()).toBe("code"); + expect(codeElement.closest("article")).not.toBeNull(); + }); +}); diff --git a/frontend/__tests__/components/chat/action-suggestions.test.tsx b/frontend/__tests__/components/chat/action-suggestions.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..47ab983342c74ed68b285863d429e4d3d0a95a7a --- /dev/null +++ b/frontend/__tests__/components/chat/action-suggestions.test.tsx @@ -0,0 +1,132 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ActionSuggestions } from "#/components/features/chat/action-suggestions"; +import OpenHands from "#/api/open-hands"; +import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers"; + +// Mock dependencies +vi.mock("posthog-js", () => ({ + default: { + capture: vi.fn(), + }, +})); + +const { useSelectorMock } = vi.hoisted(() => ({ + useSelectorMock: vi.fn(), +})); + +vi.mock("react-redux", () => ({ + useSelector: useSelectorMock, +})); + +vi.mock("#/context/auth-context", () => ({ + useAuth: vi.fn(), +})); + +// Mock react-i18next +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + ACTION$PUSH_TO_BRANCH: "Push to Branch", + ACTION$PUSH_CREATE_PR: "Push & Create PR", + ACTION$PUSH_CHANGES_TO_PR: "Push Changes to PR", + }; + return translations[key] || key; + }, + }), +})); + +vi.mock("react-router", () => ({ + useParams: () => ({ + conversationId: "test-conversation-id", + }), +})); + +const renderActionSuggestions = () => + render( {}} />, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + +describe("ActionSuggestions", () => { + // Setup mocks for each test + beforeEach(() => { + vi.clearAllMocks(); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: { + github: "some-token", + }, + }); + + useSelectorMock.mockReturnValue({ + selectedRepository: "test-repo", + }); + }); + + it("should render both GitHub buttons when GitHub token is set and repository is selected", async () => { + const getConversationSpy = vi.spyOn(OpenHands, "getConversation"); + // @ts-expect-error - only required for testing + getConversationSpy.mockResolvedValue({ + selected_repository: "test-repo", + }); + renderActionSuggestions(); + + // Find all buttons with data-testid="suggestion" + const buttons = await screen.findAllByTestId("suggestion"); + + // Check if we have at least 2 buttons + expect(buttons.length).toBeGreaterThanOrEqual(2); + + // Check if the buttons contain the expected text + const pushButton = buttons.find((button) => + button.textContent?.includes("Push to Branch"), + ); + const prButton = buttons.find((button) => + button.textContent?.includes("Push & Create PR"), + ); + + expect(pushButton).toBeInTheDocument(); + expect(prButton).toBeInTheDocument(); + }); + + it("should not render buttons when GitHub token is not set", () => { + renderActionSuggestions(); + + expect(screen.queryByTestId("suggestion")).not.toBeInTheDocument(); + }); + + it("should not render buttons when no repository is selected", () => { + useSelectorMock.mockReturnValue({ + selectedRepository: null, + }); + + renderActionSuggestions(); + + expect(screen.queryByTestId("suggestion")).not.toBeInTheDocument(); + }); + + it("should have different prompts for 'Push to Branch' and 'Push & Create PR' buttons", () => { + // This test verifies that the prompts are different in the component + renderActionSuggestions(); + + // Get the component instance to access the internal values + const pushBranchPrompt = + "Please push the changes to a remote branch on GitHub, but do NOT create a pull request. Please use the exact SAME branch name as the one you are currently on."; + const createPRPrompt = + "Please push the changes to GitHub and open a pull request. Please create a meaningful branch name that describes the changes. If a pull request template exists in the repository, please follow it when creating the PR description."; + + // Verify the prompts are different + expect(pushBranchPrompt).not.toEqual(createPRPrompt); + + // Verify the PR prompt mentions creating a meaningful branch name + expect(createPRPrompt).toContain("meaningful branch name"); + expect(createPRPrompt).not.toContain("SAME branch name"); + }); +}); diff --git a/frontend/__tests__/components/chat/chat-input.test.tsx b/frontend/__tests__/components/chat/chat-input.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2e65f27bf5fd45b98a293ef4b75c84a4571e8fb5 --- /dev/null +++ b/frontend/__tests__/components/chat/chat-input.test.tsx @@ -0,0 +1,256 @@ +import userEvent from "@testing-library/user-event"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, afterEach, vi, it, expect } from "vitest"; +import { ChatInput } from "#/components/features/chat/chat-input"; + +describe("ChatInput", () => { + const onSubmitMock = vi.fn(); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render a textarea", () => { + render(); + expect(screen.getByTestId("chat-input")).toBeInTheDocument(); + expect(screen.getByRole("textbox")).toBeInTheDocument(); + }); + + it("should call onSubmit when the user types and presses enter", async () => { + const user = userEvent.setup(); + render(); + const textarea = screen.getByRole("textbox"); + + await user.type(textarea, "Hello, world!"); + await user.keyboard("{Enter}"); + + expect(onSubmitMock).toHaveBeenCalledWith("Hello, world!"); + }); + + it("should call onSubmit when pressing the submit button", async () => { + const user = userEvent.setup(); + render(); + const textarea = screen.getByRole("textbox"); + const button = screen.getByRole("button"); + + await user.type(textarea, "Hello, world!"); + await user.click(button); + + expect(onSubmitMock).toHaveBeenCalledWith("Hello, world!"); + }); + + it("should not call onSubmit when the message is empty", async () => { + const user = userEvent.setup(); + render(); + const button = screen.getByRole("button"); + + await user.click(button); + expect(onSubmitMock).not.toHaveBeenCalled(); + + await user.keyboard("{Enter}"); + expect(onSubmitMock).not.toHaveBeenCalled(); + }); + + it("should not call onSubmit when the message is only whitespace", async () => { + const user = userEvent.setup(); + render(); + const textarea = screen.getByRole("textbox"); + + await user.type(textarea, " "); + await user.keyboard("{Enter}"); + + expect(onSubmitMock).not.toHaveBeenCalled(); + + await user.type(textarea, " \t\n"); + await user.keyboard("{Enter}"); + + expect(onSubmitMock).not.toHaveBeenCalled(); + }); + + it("should disable submit", async () => { + const user = userEvent.setup(); + render(); + + const button = screen.getByRole("button"); + const textarea = screen.getByRole("textbox"); + + await user.type(textarea, "Hello, world!"); + + expect(button).toBeDisabled(); + await user.click(button); + expect(onSubmitMock).not.toHaveBeenCalled(); + + await user.keyboard("{Enter}"); + expect(onSubmitMock).not.toHaveBeenCalled(); + }); + + it("should render a placeholder with translation key", () => { + render(); + + const textarea = screen.getByPlaceholderText("SUGGESTIONS$WHAT_TO_BUILD"); + expect(textarea).toBeInTheDocument(); + }); + + it("should create a newline instead of submitting when shift + enter is pressed", async () => { + const user = userEvent.setup(); + render(); + const textarea = screen.getByRole("textbox"); + + await user.type(textarea, "Hello, world!"); + await user.keyboard("{Shift>} {Enter}"); // Shift + Enter + + expect(onSubmitMock).not.toHaveBeenCalled(); + // expect(textarea).toHaveValue("Hello, world!\n"); + }); + + it("should clear the input message after sending a message", async () => { + const user = userEvent.setup(); + render(); + const textarea = screen.getByRole("textbox"); + const button = screen.getByRole("button"); + + await user.type(textarea, "Hello, world!"); + await user.keyboard("{Enter}"); + expect(textarea).toHaveValue(""); + + await user.type(textarea, "Hello, world!"); + await user.click(button); + expect(textarea).toHaveValue(""); + }); + + it("should hide the submit button", () => { + render(); + expect(screen.queryByRole("button")).not.toBeInTheDocument(); + }); + + it("should call onChange when the user types", async () => { + const user = userEvent.setup(); + const onChangeMock = vi.fn(); + render(); + const textarea = screen.getByRole("textbox"); + + await user.type(textarea, "Hello, world!"); + + expect(onChangeMock).toHaveBeenCalledTimes("Hello, world!".length); + }); + + it("should have set the passed value", () => { + render(); + const textarea = screen.getByRole("textbox"); + + expect(textarea).toHaveValue("Hello, world!"); + }); + + it("should display the stop button and trigger the callback", async () => { + const user = userEvent.setup(); + const onStopMock = vi.fn(); + render( + , + ); + const stopButton = screen.getByTestId("stop-button"); + + await user.click(stopButton); + expect(onStopMock).toHaveBeenCalledOnce(); + }); + + it("should call onFocus and onBlur when the textarea is focused and blurred", async () => { + const user = userEvent.setup(); + const onFocusMock = vi.fn(); + const onBlurMock = vi.fn(); + render( + , + ); + const textarea = screen.getByRole("textbox"); + + await user.click(textarea); + expect(onFocusMock).toHaveBeenCalledOnce(); + + await user.tab(); + expect(onBlurMock).toHaveBeenCalledOnce(); + }); + + it("should handle text paste correctly", () => { + const onSubmit = vi.fn(); + const onChange = vi.fn(); + + render(); + + const input = screen.getByTestId("chat-input").querySelector("textarea"); + expect(input).toBeTruthy(); + + // Fire paste event with text data + fireEvent.paste(input!, { + clipboardData: { + getData: (type: string) => (type === "text/plain" ? "test paste" : ""), + files: [], + }, + }); + }); + + it("should handle image paste correctly", () => { + const onSubmit = vi.fn(); + const onImagePaste = vi.fn(); + + render(); + + const input = screen.getByTestId("chat-input").querySelector("textarea"); + expect(input).toBeTruthy(); + + // Create a paste event with an image file + const file = new File(["dummy content"], "image.png", { + type: "image/png", + }); + + // Fire paste event with image data + fireEvent.paste(input!, { + clipboardData: { + getData: () => "", + files: [file], + }, + }); + + // Verify image paste was handled + expect(onImagePaste).toHaveBeenCalledWith([file]); + }); + + it("should use the default maxRows value", () => { + // We can't directly test the maxRows prop as it's not exposed in the DOM + // Instead, we'll verify the component renders with the default props + render(); + const textarea = screen.getByRole("textbox"); + expect(textarea).toBeInTheDocument(); + + // The actual verification of maxRows=16 is handled internally by the TextareaAutosize component + // and affects how many rows the textarea can expand to + }); + + it("should not submit when Enter is pressed during IME composition", async () => { + const user = userEvent.setup(); + render(); + const textarea = screen.getByRole("textbox"); + + await user.type(textarea, "こんにちは"); + + // Simulate Enter during IME composition + fireEvent.keyDown(textarea, { + key: "Enter", + isComposing: true, + nativeEvent: { isComposing: true }, + }); + + expect(onSubmitMock).not.toHaveBeenCalled(); + + // Simulate normal Enter after composition is done + fireEvent.keyDown(textarea, { + key: "Enter", + isComposing: false, + nativeEvent: { isComposing: false }, + }); + + expect(onSubmitMock).toHaveBeenCalledWith("こんにちは"); + }); +}); diff --git a/frontend/__tests__/components/chat/chat-interface.test.tsx b/frontend/__tests__/components/chat/chat-interface.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c265010c7e1dfcbb514f2348ee89d76fa5b2a525 --- /dev/null +++ b/frontend/__tests__/components/chat/chat-interface.test.tsx @@ -0,0 +1,366 @@ +import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { screen, waitFor, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { renderWithProviders } from "test-utils"; +import type { Message } from "#/message"; +import { SUGGESTIONS } from "#/utils/suggestions"; +import { WsClientProviderStatus } from "#/context/ws-client-provider"; +import { ChatInterface } from "#/components/features/chat/chat-interface"; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const renderChatInterface = (messages: Message[]) => + renderWithProviders(); + +describe("Empty state", () => { + const { send: sendMock } = vi.hoisted(() => ({ + send: vi.fn(), + })); + + const { useWsClient: useWsClientMock } = vi.hoisted(() => ({ + useWsClient: vi.fn(() => ({ + send: sendMock, + status: WsClientProviderStatus.CONNECTED, + isLoadingMessages: false, + })), + })); + + beforeAll(() => { + vi.mock("react-router", async (importActual) => ({ + ...(await importActual()), + useRouteLoaderData: vi.fn(() => ({})), + })); + + vi.mock("#/context/socket", async (importActual) => ({ + ...(await importActual()), + useWsClient: useWsClientMock, + })); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it.todo("should render suggestions if empty"); + + it("should render the default suggestions", () => { + renderWithProviders(); + + const suggestions = screen.getByTestId("suggestions"); + const repoSuggestions = Object.keys(SUGGESTIONS.repo); + + // check that there are at most 4 suggestions displayed + const displayedSuggestions = within(suggestions).getAllByRole("button"); + expect(displayedSuggestions.length).toBeLessThanOrEqual(4); + + // Check that each displayed suggestion is one of the repo suggestions + displayedSuggestions.forEach((suggestion) => { + expect(repoSuggestions).toContain(suggestion.textContent); + }); + }); + + it.fails( + "should load the a user message to the input when selecting", + async () => { + // this is to test that the message is in the UI before the socket is called + useWsClientMock.mockImplementation(() => ({ + send: sendMock, + status: WsClientProviderStatus.CONNECTED, + isLoadingMessages: false, + })); + const user = userEvent.setup(); + renderWithProviders(); + + const suggestions = screen.getByTestId("suggestions"); + const displayedSuggestions = within(suggestions).getAllByRole("button"); + const input = screen.getByTestId("chat-input"); + + await user.click(displayedSuggestions[0]); + + // user message loaded to input + expect(screen.queryByTestId("suggestions")).toBeInTheDocument(); + expect(input).toHaveValue(displayedSuggestions[0].textContent); + }, + ); + + it.fails( + "should send the message to the socket only if the runtime is active", + async () => { + useWsClientMock.mockImplementation(() => ({ + send: sendMock, + status: WsClientProviderStatus.CONNECTED, + isLoadingMessages: false, + })); + const user = userEvent.setup(); + const { rerender } = renderWithProviders(); + + const suggestions = screen.getByTestId("suggestions"); + const displayedSuggestions = within(suggestions).getAllByRole("button"); + + await user.click(displayedSuggestions[0]); + expect(sendMock).not.toHaveBeenCalled(); + + useWsClientMock.mockImplementation(() => ({ + send: sendMock, + status: WsClientProviderStatus.CONNECTED, + isLoadingMessages: false, + })); + rerender(); + + await waitFor(() => + expect(sendMock).toHaveBeenCalledWith(expect.any(String)), + ); + }, + ); +}); + +describe.skip("ChatInterface", () => { + beforeAll(() => { + // mock useScrollToBottom hook + vi.mock("#/hooks/useScrollToBottom", () => ({ + useScrollToBottom: vi.fn(() => ({ + scrollDomToBottom: vi.fn(), + onChatBodyScroll: vi.fn(), + hitBottom: vi.fn(), + })), + })); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render messages", () => { + const messages: Message[] = [ + { + sender: "user", + content: "Hello", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + { + sender: "assistant", + content: "Hi", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + ]; + renderChatInterface(messages); + + expect(screen.getAllByTestId(/-message/)).toHaveLength(2); + }); + + it("should render a chat input", () => { + const messages: Message[] = []; + renderChatInterface(messages); + + expect(screen.getByTestId("chat-input")).toBeInTheDocument(); + }); + + it("should call socket send when submitting a message", async () => { + const user = userEvent.setup(); + const messages: Message[] = []; + renderChatInterface(messages); + + const input = screen.getByTestId("chat-input"); + await user.type(input, "Hello"); + await user.keyboard("{Enter}"); + + // spy on send and expect to have been called + }); + + it("should render an image carousel with a message", () => { + let messages: Message[] = [ + { + sender: "assistant", + content: "Here are some images", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + ]; + const { rerender } = renderChatInterface(messages); + + expect(screen.queryByTestId("image-carousel")).not.toBeInTheDocument(); + + messages = [ + { + sender: "assistant", + content: "Here are some images", + imageUrls: ["image1", "image2"], + timestamp: new Date().toISOString(), + pending: true, + }, + ]; + + rerender(); + + const imageCarousel = screen.getByTestId("image-carousel"); + expect(imageCarousel).toBeInTheDocument(); + expect(within(imageCarousel).getAllByTestId("image-preview")).toHaveLength( + 2, + ); + }); + + it("should render a 'continue' action when there are more than 2 messages and awaiting user input", () => { + const messages: Message[] = [ + { + sender: "assistant", + content: "Hello", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + { + sender: "user", + content: "Hi", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + ]; + const { rerender } = renderChatInterface(messages); + expect( + screen.queryByTestId("continue-action-button"), + ).not.toBeInTheDocument(); + + messages.push({ + sender: "assistant", + content: "How can I help you?", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }); + + rerender(); + + expect(screen.getByTestId("continue-action-button")).toBeInTheDocument(); + }); + + it("should render inline errors", () => { + const messages: Message[] = [ + { + sender: "assistant", + content: "Hello", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + { + type: "error", + content: "Something went wrong", + sender: "assistant", + timestamp: new Date().toISOString(), + }, + ]; + renderChatInterface(messages); + + const error = screen.getByTestId("error-message"); + expect(within(error).getByText("Something went wrong")).toBeInTheDocument(); + }); + + it("should render both GitHub buttons initially when ghToken is available", () => { + vi.mock("react-router", async (importActual) => ({ + ...(await importActual()), + useRouteLoaderData: vi.fn(() => ({ ghToken: "test-token" })), + })); + + const messages: Message[] = [ + { + sender: "assistant", + content: "Hello", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + ]; + renderChatInterface(messages); + + const pushButton = screen.getByRole("button", { name: "Push to Branch" }); + const prButton = screen.getByRole("button", { name: "Push & Create PR" }); + + expect(pushButton).toBeInTheDocument(); + expect(prButton).toBeInTheDocument(); + expect(pushButton).toHaveTextContent("Push to Branch"); + expect(prButton).toHaveTextContent("Push & Create PR"); + }); + + it("should render only 'Push changes to PR' button after PR is created", async () => { + vi.mock("react-router", async (importActual) => ({ + ...(await importActual()), + useRouteLoaderData: vi.fn(() => ({ ghToken: "test-token" })), + })); + + const messages: Message[] = [ + { + sender: "assistant", + content: "Hello", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + ]; + const { rerender } = renderChatInterface(messages); + const user = userEvent.setup(); + + // Click the "Push & Create PR" button + const prButton = screen.getByRole("button", { name: "Push & Create PR" }); + await user.click(prButton); + + // Re-render to trigger state update + rerender(); + + // Verify only one button is shown + const pushToPrButton = screen.getByRole("button", { + name: "Push changes to PR", + }); + expect(pushToPrButton).toBeInTheDocument(); + expect( + screen.queryByRole("button", { name: "Push to Branch" }), + ).not.toBeInTheDocument(); + expect( + screen.queryByRole("button", { name: "Push & Create PR" }), + ).not.toBeInTheDocument(); + }); + + it("should render feedback actions if there are more than 3 messages", () => { + const messages: Message[] = [ + { + sender: "assistant", + content: "Hello", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + { + sender: "user", + content: "Hi", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + { + sender: "assistant", + content: "How can I help you?", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }, + ]; + const { rerender } = renderChatInterface(messages); + expect(screen.queryByTestId("feedback-actions")).not.toBeInTheDocument(); + + messages.push({ + sender: "user", + content: "I need help", + imageUrls: [], + timestamp: new Date().toISOString(), + pending: true, + }); + + rerender(); + + expect(screen.getByTestId("feedback-actions")).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/components/chat/expandable-message.test.tsx b/frontend/__tests__/components/chat/expandable-message.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2da47c6d4aeaf0173eff806bd4134fb0dbd9abde --- /dev/null +++ b/frontend/__tests__/components/chat/expandable-message.test.tsx @@ -0,0 +1,141 @@ +import { describe, expect, it, vi } from "vitest"; +import { screen } from "@testing-library/react"; +import { renderWithProviders } from "test-utils"; +import { createRoutesStub } from "react-router"; +import { ExpandableMessage } from "#/components/features/chat/expandable-message"; +import OpenHands from "#/api/open-hands"; + +vi.mock("react-i18next", async () => { + const actual = await vi.importActual("react-i18next"); + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => key, + i18n: { + changeLanguage: () => new Promise(() => {}), + language: "en", + exists: () => true, + }, + }), + }; +}); + +describe("ExpandableMessage", () => { + it("should render with neutral border for non-action messages", () => { + renderWithProviders(); + const element = screen.getAllByText("Hello")[0]; + const container = element.closest( + "div.flex.gap-2.items-center.justify-start", + ); + expect(container).toHaveClass("border-neutral-300"); + expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument(); + }); + + it("should render with neutral border for error messages", () => { + renderWithProviders( + , + ); + const element = screen.getAllByText("Error occurred")[0]; + const container = element.closest( + "div.flex.gap-2.items-center.justify-start", + ); + expect(container).toHaveClass("border-danger"); + expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument(); + }); + + it("should render with success icon for successful action messages", () => { + renderWithProviders( + , + ); + const element = screen.getByText("OBSERVATION_MESSAGE$RUN"); + const container = element.closest( + "div.flex.gap-2.items-center.justify-start", + ); + expect(container).toHaveClass("border-neutral-300"); + const icon = screen.getByTestId("status-icon"); + expect(icon).toHaveClass("fill-success"); + }); + + it("should render with error icon for failed action messages", () => { + renderWithProviders( + , + ); + const element = screen.getByText("OBSERVATION_MESSAGE$RUN"); + const container = element.closest( + "div.flex.gap-2.items-center.justify-start", + ); + expect(container).toHaveClass("border-neutral-300"); + const icon = screen.getByTestId("status-icon"); + expect(icon).toHaveClass("fill-danger"); + }); + + it("should render with neutral border and no icon for action messages without success prop", () => { + renderWithProviders( + , + ); + const element = screen.getByText("OBSERVATION_MESSAGE$RUN"); + const container = element.closest( + "div.flex.gap-2.items-center.justify-start", + ); + expect(container).toHaveClass("border-neutral-300"); + expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument(); + }); + + it("should render with neutral border and no icon for action messages with undefined success (timeout case)", () => { + renderWithProviders( + , + ); + const element = screen.getByText("OBSERVATION_MESSAGE$RUN"); + const container = element.closest( + "div.flex.gap-2.items-center.justify-start", + ); + expect(container).toHaveClass("border-neutral-300"); + expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument(); + }); + + it("should render the out of credits message when the user is out of credits", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - We only care about the APP_MODE and FEATURE_FLAGS fields + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + FEATURE_FLAGS: { + ENABLE_BILLING: true, + HIDE_LLM_SETTINGS: false, + }, + }); + const RouterStub = createRoutesStub([ + { + Component: () => ( + + ), + path: "/", + }, + ]); + + renderWithProviders(); + await screen.findByTestId("out-of-credits"); + }); +}); diff --git a/frontend/__tests__/components/context-menu/account-settings-context-menu.test.tsx b/frontend/__tests__/components/context-menu/account-settings-context-menu.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8f80a1b2388e0eb90e970fa44dc524cfa3966596 --- /dev/null +++ b/frontend/__tests__/components/context-menu/account-settings-context-menu.test.tsx @@ -0,0 +1,74 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, test, vi } from "vitest"; +import { AccountSettingsContextMenu } from "#/components/features/context-menu/account-settings-context-menu"; + +describe("AccountSettingsContextMenu", () => { + const user = userEvent.setup(); + const onClickAccountSettingsMock = vi.fn(); + const onLogoutMock = vi.fn(); + const onCloseMock = vi.fn(); + + afterEach(() => { + onClickAccountSettingsMock.mockClear(); + onLogoutMock.mockClear(); + onCloseMock.mockClear(); + }); + + it("should always render the right options", () => { + render( + , + ); + + expect( + screen.getByTestId("account-settings-context-menu"), + ).toBeInTheDocument(); + expect(screen.getByText("ACCOUNT_SETTINGS$LOGOUT")).toBeInTheDocument(); + }); + + it("should call onLogout when the logout option is clicked", async () => { + render( + , + ); + + const logoutOption = screen.getByText("ACCOUNT_SETTINGS$LOGOUT"); + await user.click(logoutOption); + + expect(onLogoutMock).toHaveBeenCalledOnce(); + }); + + test("logout button is always enabled", async () => { + render( + , + ); + + const logoutOption = screen.getByText("ACCOUNT_SETTINGS$LOGOUT"); + await user.click(logoutOption); + + expect(onLogoutMock).toHaveBeenCalledOnce(); + }); + + it("should call onClose when clicking outside of the element", async () => { + render( + , + ); + + const accountSettingsButton = screen.getByText("ACCOUNT_SETTINGS$LOGOUT"); + await user.click(accountSettingsButton); + await user.click(document.body); + + expect(onCloseMock).toHaveBeenCalledOnce(); + }); +}); diff --git a/frontend/__tests__/components/context-menu/context-menu-list-item.test.tsx b/frontend/__tests__/components/context-menu/context-menu-list-item.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ffedba06e13d5fe0acdd84fd6423820ee2db0d55 --- /dev/null +++ b/frontend/__tests__/components/context-menu/context-menu-list-item.test.tsx @@ -0,0 +1,44 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { ContextMenuListItem } from "#/components/features/context-menu/context-menu-list-item"; + +describe("ContextMenuListItem", () => { + it("should render the component with the children", () => { + const onClickMock = vi.fn(); + render( + Test, + ); + + expect(screen.getByTestId("context-menu-list-item")).toBeInTheDocument(); + expect(screen.getByText("Test")).toBeInTheDocument(); + }); + + it("should call the onClick callback when clicked", async () => { + const user = userEvent.setup(); + const onClickMock = vi.fn(); + render( + Test, + ); + + const element = screen.getByTestId("context-menu-list-item"); + await user.click(element); + + expect(onClickMock).toHaveBeenCalledOnce(); + }); + + it("should not call the onClick callback when clicked and the button is disabled", async () => { + const user = userEvent.setup(); + const onClickMock = vi.fn(); + render( + + Test + , + ); + + const element = screen.getByTestId("context-menu-list-item"); + await user.click(element); + + expect(onClickMock).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/components/features/analytics/analytics-consent-form-modal.test.tsx b/frontend/__tests__/components/features/analytics/analytics-consent-form-modal.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7400babc53f5de3ac27df62da0395292492a24e4 --- /dev/null +++ b/frontend/__tests__/components/features/analytics/analytics-consent-form-modal.test.tsx @@ -0,0 +1,30 @@ +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { AnalyticsConsentFormModal } from "#/components/features/analytics/analytics-consent-form-modal"; +import OpenHands from "#/api/open-hands"; + +describe("AnalyticsConsentFormModal", () => { + it("should call saveUserSettings with consent", async () => { + const user = userEvent.setup(); + const onCloseMock = vi.fn(); + const saveUserSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + const confirmButton = screen.getByTestId("confirm-preferences"); + await user.click(confirmButton); + + expect(saveUserSettingsSpy).toHaveBeenCalledWith( + expect.objectContaining({ user_consents_to_analytics: true }), + ); + await waitFor(() => expect(onCloseMock).toHaveBeenCalled()); + }); +}); diff --git a/frontend/__tests__/components/features/auth-modal.test.tsx b/frontend/__tests__/components/features/auth-modal.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..15f2e52655636b38ab55170bc181ec44989996f0 --- /dev/null +++ b/frontend/__tests__/components/features/auth-modal.test.tsx @@ -0,0 +1,47 @@ +import { render, screen } from "@testing-library/react"; +import { it, describe, expect, vi, beforeEach, afterEach } from "vitest"; +import userEvent from "@testing-library/user-event"; +import { AuthModal } from "#/components/features/waitlist/auth-modal"; + +// Mock the useAuthUrl hook +vi.mock("#/hooks/use-auth-url", () => ({ + useAuthUrl: () => "https://gitlab.com/oauth/authorize", +})); + +describe("AuthModal", () => { + beforeEach(() => { + vi.stubGlobal("location", { href: "" }); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.resetAllMocks(); + }); + + it("should render the GitHub and GitLab buttons", () => { + render(); + + const githubButton = screen.getByRole("button", { + name: "GITHUB$CONNECT_TO_GITHUB", + }); + const gitlabButton = screen.getByRole("button", { + name: "GITLAB$CONNECT_TO_GITLAB", + }); + + expect(githubButton).toBeInTheDocument(); + expect(gitlabButton).toBeInTheDocument(); + }); + + it("should redirect to GitHub auth URL when GitHub button is clicked", async () => { + const user = userEvent.setup(); + const mockUrl = "https://github.com/login/oauth/authorize"; + render(); + + const githubButton = screen.getByRole("button", { + name: "GITHUB$CONNECT_TO_GITHUB", + }); + await user.click(githubButton); + + expect(window.location.href).toBe(mockUrl); + }); +}); diff --git a/frontend/__tests__/components/features/chat/path-component.test.tsx b/frontend/__tests__/components/features/chat/path-component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..90ee0e66e6ce59bfbe232d054aebd7233f1dd7c0 --- /dev/null +++ b/frontend/__tests__/components/features/chat/path-component.test.tsx @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { isLikelyDirectory } from "#/components/features/chat/path-component"; + +describe("isLikelyDirectory", () => { + it("should return false for empty path", () => { + expect(isLikelyDirectory("")).toBe(false); + }); + + it("should return true for paths ending with forward slash", () => { + expect(isLikelyDirectory("/path/to/dir/")).toBe(true); + expect(isLikelyDirectory("dir/")).toBe(true); + }); + + it("should return true for paths ending with backslash", () => { + expect(isLikelyDirectory("C:\\path\\to\\dir\\")).toBe(true); + expect(isLikelyDirectory("dir\\")).toBe(true); + }); + + it("should return true for paths without extension", () => { + expect(isLikelyDirectory("/path/to/dir")).toBe(true); + expect(isLikelyDirectory("dir")).toBe(true); + }); + + it("should return false for paths ending with dot", () => { + expect(isLikelyDirectory("/path/to/dir.")).toBe(false); + expect(isLikelyDirectory("dir.")).toBe(false); + }); + + it("should return false for paths with file extensions", () => { + expect(isLikelyDirectory("/path/to/file.txt")).toBe(false); + expect(isLikelyDirectory("file.js")).toBe(false); + expect(isLikelyDirectory("script.test.ts")).toBe(false); + }); +}); diff --git a/frontend/__tests__/components/features/conversation-panel/conversation-card.test.tsx b/frontend/__tests__/components/features/conversation-panel/conversation-card.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..32bde1cb5028c0a4528096448cffbb94edec45c4 --- /dev/null +++ b/frontend/__tests__/components/features/conversation-panel/conversation-card.test.tsx @@ -0,0 +1,489 @@ +import { screen, within } from "@testing-library/react"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + test, + vi, +} from "vitest"; +import userEvent from "@testing-library/user-event"; +import { renderWithProviders } from "test-utils"; +import { formatTimeDelta } from "#/utils/format-time-delta"; +import { ConversationCard } from "#/components/features/conversation-panel/conversation-card"; +import { clickOnEditButton } from "./utils"; + +// We'll use the actual i18next implementation but override the translation function +import { I18nextProvider } from "react-i18next"; +import i18n from "i18next"; + +// Mock the t function to return our custom translations +vi.mock("react-i18next", async () => { + const actual = await vi.importActual("react-i18next"); + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + "CONVERSATION$CREATED": "Created", + "CONVERSATION$AGO": "ago", + "CONVERSATION$UPDATED": "Updated" + }; + return translations[key] || key; + }, + i18n: { + changeLanguage: () => new Promise(() => {}), + }, + }), + }; +}); + +describe("ConversationCard", () => { + const onClick = vi.fn(); + const onDelete = vi.fn(); + const onChangeTitle = vi.fn(); + + beforeAll(() => { + vi.stubGlobal("window", { + open: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + afterAll(() => { + vi.unstubAllGlobals(); + }); + + it("should render the conversation card", () => { + renderWithProviders( + , + ); + + const card = screen.getByTestId("conversation-card"); + + within(card).getByText("Conversation 1"); + + // Just check that the card contains the expected text content + expect(card).toHaveTextContent("Created"); + expect(card).toHaveTextContent("ago"); + + // Use a regex to match the time part since it might have whitespace + const timeRegex = new RegExp(formatTimeDelta(new Date("2021-10-01T12:00:00Z"))); + expect(card).toHaveTextContent(timeRegex); + }); + + it("should render the selectedRepository if available", () => { + const { rerender } = renderWithProviders( + , + ); + + expect( + screen.queryByTestId("conversation-card-selected-repository"), + ).not.toBeInTheDocument(); + + rerender( + , + ); + + screen.getByTestId("conversation-card-selected-repository"); + }); + + it("should toggle a context menu when clicking the ellipsis button", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + expect(screen.queryByTestId("context-menu")).not.toBeInTheDocument(); + + const ellipsisButton = screen.getByTestId("ellipsis-button"); + await user.click(ellipsisButton); + + screen.getByTestId("context-menu"); + + await user.click(ellipsisButton); + + expect(screen.queryByTestId("context-menu")).not.toBeInTheDocument(); + }); + + it("should call onDelete when the delete button is clicked", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + const ellipsisButton = screen.getByTestId("ellipsis-button"); + await user.click(ellipsisButton); + + const menu = screen.getByTestId("context-menu"); + const deleteButton = within(menu).getByTestId("delete-button"); + + await user.click(deleteButton); + + expect(onDelete).toHaveBeenCalled(); + }); + + test("clicking the selectedRepository should not trigger the onClick handler", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + const selectedRepository = screen.getByTestId( + "conversation-card-selected-repository", + ); + await user.click(selectedRepository); + + expect(onClick).not.toHaveBeenCalled(); + }); + + test("conversation title should call onChangeTitle when changed and blurred", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + await clickOnEditButton(user); + const title = screen.getByTestId("conversation-card-title"); + + expect(title).toBeEnabled(); + expect(screen.queryByTestId("context-menu")).not.toBeInTheDocument(); + // expect to be focused + expect(document.activeElement).toBe(title); + + await user.clear(title); + await user.type(title, "New Conversation Name "); + await user.tab(); + + expect(onChangeTitle).toHaveBeenCalledWith("New Conversation Name"); + expect(title).toHaveValue("New Conversation Name"); + }); + + it("should reset title and not call onChangeTitle when the title is empty", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + await clickOnEditButton(user); + + const title = screen.getByTestId("conversation-card-title"); + + await user.clear(title); + await user.tab(); + + expect(onChangeTitle).not.toHaveBeenCalled(); + expect(title).toHaveValue("Conversation 1"); + }); + + test("clicking the title should trigger the onClick handler", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + const title = screen.getByTestId("conversation-card-title"); + await user.click(title); + + expect(onClick).toHaveBeenCalled(); + }); + + test("clicking the title should not trigger the onClick handler if edit mode", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + await clickOnEditButton(user); + + const title = screen.getByTestId("conversation-card-title"); + await user.click(title); + + expect(onClick).not.toHaveBeenCalled(); + }); + + test("clicking the delete button should not trigger the onClick handler", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + const ellipsisButton = screen.getByTestId("ellipsis-button"); + await user.click(ellipsisButton); + + const menu = screen.getByTestId("context-menu"); + const deleteButton = within(menu).getByTestId("delete-button"); + + await user.click(deleteButton); + + expect(onClick).not.toHaveBeenCalled(); + }); + + it("should show display cost button only when showOptions is true", async () => { + const user = userEvent.setup(); + const { rerender } = renderWithProviders( + , + ); + + const ellipsisButton = screen.getByTestId("ellipsis-button"); + await user.click(ellipsisButton); + + // Wait for context menu to appear + const menu = await screen.findByTestId("context-menu"); + expect( + within(menu).queryByTestId("display-cost-button"), + ).not.toBeInTheDocument(); + + // Close menu + await user.click(ellipsisButton); + + rerender( + , + ); + + // Open menu again + await user.click(ellipsisButton); + + // Wait for context menu to appear and check for display cost button + const newMenu = await screen.findByTestId("context-menu"); + within(newMenu).getByTestId("display-cost-button"); + }); + + it("should show metrics modal when clicking the display cost button", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + const ellipsisButton = screen.getByTestId("ellipsis-button"); + await user.click(ellipsisButton); + + const menu = screen.getByTestId("context-menu"); + const displayCostButton = within(menu).getByTestId("display-cost-button"); + + await user.click(displayCostButton); + + // Verify if metrics modal is displayed by checking for the modal content + expect(screen.getByTestId("metrics-modal")).toBeInTheDocument(); + }); + + it("should not display the edit or delete options if the handler is not provided", async () => { + const user = userEvent.setup(); + const { rerender } = renderWithProviders( + , + ); + + const ellipsisButton = screen.getByTestId("ellipsis-button"); + await user.click(ellipsisButton); + + const menu = await screen.findByTestId("context-menu"); + expect(within(menu).queryByTestId("edit-button")).toBeInTheDocument(); + expect(within(menu).queryByTestId("delete-button")).not.toBeInTheDocument(); + + // toggle to hide the context menu + await user.click(ellipsisButton); + + rerender( + , + ); + + await user.click(ellipsisButton); + const newMenu = await screen.findByTestId("context-menu"); + expect( + within(newMenu).queryByTestId("edit-button"), + ).not.toBeInTheDocument(); + expect(within(newMenu).queryByTestId("delete-button")).toBeInTheDocument(); + }); + + it("should not render the ellipsis button if there are no actions", () => { + const { rerender } = renderWithProviders( + , + ); + + expect(screen.getByTestId("ellipsis-button")).toBeInTheDocument(); + + rerender( + , + ); + + expect(screen.getByTestId("ellipsis-button")).toBeInTheDocument(); + + rerender( + , + ); + + expect(screen.queryByTestId("ellipsis-button")).not.toBeInTheDocument(); + }); + + describe("state indicator", () => { + it("should render the 'STOPPED' indicator by default", () => { + renderWithProviders( + , + ); + + screen.getByTestId("STOPPED-indicator"); + }); + + it("should render the other indicators when provided", () => { + renderWithProviders( + , + ); + + expect(screen.queryByTestId("STOPPED-indicator")).not.toBeInTheDocument(); + screen.getByTestId("RUNNING-indicator"); + }); + }); +}); diff --git a/frontend/__tests__/components/features/conversation-panel/conversation-panel.test.tsx b/frontend/__tests__/components/features/conversation-panel/conversation-panel.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..45cd05eaa9d20f0f3c0dfbaa956eb7046a51a4b6 --- /dev/null +++ b/frontend/__tests__/components/features/conversation-panel/conversation-panel.test.tsx @@ -0,0 +1,290 @@ +import { screen, waitFor, within } from "@testing-library/react"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { QueryClientConfig } from "@tanstack/react-query"; +import userEvent from "@testing-library/user-event"; +import { createRoutesStub } from "react-router"; +import React from "react"; +import { renderWithProviders } from "test-utils"; +import { ConversationPanel } from "#/components/features/conversation-panel/conversation-panel"; +import OpenHands from "#/api/open-hands"; +import { Conversation } from "#/api/open-hands.types"; + +describe("ConversationPanel", () => { + const onCloseMock = vi.fn(); + const RouterStub = createRoutesStub([ + { + Component: () => , + path: "/", + }, + ]); + + const renderConversationPanel = (config?: QueryClientConfig) => + renderWithProviders(, { + preloadedState: { + metrics: { + cost: null, + usage: null, + }, + }, + }); + + beforeAll(() => { + vi.mock("react-router", async (importOriginal) => ({ + ...(await importOriginal()), + Link: ({ children }: React.PropsWithChildren) => children, + useNavigate: vi.fn(() => vi.fn()), + useLocation: vi.fn(() => ({ pathname: "/conversation" })), + useParams: vi.fn(() => ({ conversationId: "2" })), + })); + }); + + const mockConversations: Conversation[] = [ + { + conversation_id: "1", + title: "Conversation 1", + selected_repository: null, + git_provider: null, + selected_branch: null, + last_updated_at: "2021-10-01T12:00:00Z", + created_at: "2021-10-01T12:00:00Z", + status: "STOPPED" as const, + url: null, + session_api_key: null, + }, + { + conversation_id: "2", + title: "Conversation 2", + selected_repository: null, + git_provider: null, + selected_branch: null, + last_updated_at: "2021-10-02T12:00:00Z", + created_at: "2021-10-02T12:00:00Z", + status: "STOPPED" as const, + url: null, + session_api_key: null, + }, + { + conversation_id: "3", + title: "Conversation 3", + selected_repository: null, + git_provider: null, + selected_branch: null, + last_updated_at: "2021-10-03T12:00:00Z", + created_at: "2021-10-03T12:00:00Z", + status: "STOPPED" as const, + url: null, + session_api_key: null, + }, + ]; + + beforeEach(() => { + vi.clearAllMocks(); + vi.restoreAllMocks(); + // Setup default mock for getUserConversations + vi.spyOn(OpenHands, "getUserConversations").mockResolvedValue([ + ...mockConversations, + ]); + }); + + it("should render the conversations", async () => { + renderConversationPanel(); + const cards = await screen.findAllByTestId("conversation-card"); + + // NOTE that we filter out conversations that don't have a created_at property + // (mock data has 4 conversations, but only 3 have a created_at property) + expect(cards).toHaveLength(3); + }); + + it("should display an empty state when there are no conversations", async () => { + const getUserConversationsSpy = vi.spyOn(OpenHands, "getUserConversations"); + getUserConversationsSpy.mockResolvedValue([]); + + renderConversationPanel(); + + const emptyState = await screen.findByText("CONVERSATION$NO_CONVERSATIONS"); + expect(emptyState).toBeInTheDocument(); + }); + + it("should handle an error when fetching conversations", async () => { + const getUserConversationsSpy = vi.spyOn(OpenHands, "getUserConversations"); + getUserConversationsSpy.mockRejectedValue( + new Error("Failed to fetch conversations"), + ); + + renderConversationPanel(); + + const error = await screen.findByText("Failed to fetch conversations"); + expect(error).toBeInTheDocument(); + }); + + it("should cancel deleting a conversation", async () => { + const user = userEvent.setup(); + renderConversationPanel(); + + let cards = await screen.findAllByTestId("conversation-card"); + expect( + within(cards[0]).queryByTestId("delete-button"), + ).not.toBeInTheDocument(); + + const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button"); + await user.click(ellipsisButton); + const deleteButton = screen.getByTestId("delete-button"); + + // Click the first delete button + await user.click(deleteButton); + + // Cancel the deletion + const cancelButton = screen.getByRole("button", { name: /cancel/i }); + await user.click(cancelButton); + + expect( + screen.queryByRole("button", { name: /cancel/i }), + ).not.toBeInTheDocument(); + + // Ensure the conversation is not deleted + cards = await screen.findAllByTestId("conversation-card"); + expect(cards).toHaveLength(3); + }); + + it("should delete a conversation", async () => { + const user = userEvent.setup(); + const mockData: Conversation[] = [ + { + conversation_id: "1", + title: "Conversation 1", + selected_repository: null, + git_provider: null, + selected_branch: null, + last_updated_at: "2021-10-01T12:00:00Z", + created_at: "2021-10-01T12:00:00Z", + status: "STOPPED" as const, + url: null, + session_api_key: null, + }, + { + conversation_id: "2", + title: "Conversation 2", + selected_repository: null, + git_provider: null, + selected_branch: null, + last_updated_at: "2021-10-02T12:00:00Z", + created_at: "2021-10-02T12:00:00Z", + status: "STOPPED" as const, + url: null, + session_api_key: null, + }, + { + conversation_id: "3", + title: "Conversation 3", + selected_repository: null, + git_provider: null, + selected_branch: null, + last_updated_at: "2021-10-03T12:00:00Z", + created_at: "2021-10-03T12:00:00Z", + status: "STOPPED" as const, + url: null, + session_api_key: null, + }, + ]; + + const getUserConversationsSpy = vi.spyOn(OpenHands, "getUserConversations"); + getUserConversationsSpy.mockImplementation(async () => mockData); + + const deleteUserConversationSpy = vi.spyOn( + OpenHands, + "deleteUserConversation", + ); + deleteUserConversationSpy.mockImplementation(async (id: string) => { + const index = mockData.findIndex((conv) => conv.conversation_id === id); + if (index !== -1) { + mockData.splice(index, 1); + } + }); + + renderConversationPanel(); + + const cards = await screen.findAllByTestId("conversation-card"); + expect(cards).toHaveLength(3); + + const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button"); + await user.click(ellipsisButton); + const deleteButton = screen.getByTestId("delete-button"); + + // Click the first delete button + await user.click(deleteButton); + + // Confirm the deletion + const confirmButton = screen.getByRole("button", { name: /confirm/i }); + await user.click(confirmButton); + + expect( + screen.queryByRole("button", { name: /confirm/i }), + ).not.toBeInTheDocument(); + + // Wait for the cards to update + await waitFor(() => { + const updatedCards = screen.getAllByTestId("conversation-card"); + expect(updatedCards).toHaveLength(2); + }); + }); + + it("should call onClose after clicking a card", async () => { + const user = userEvent.setup(); + renderConversationPanel(); + const cards = await screen.findAllByTestId("conversation-card"); + const firstCard = cards[1]; + + await user.click(firstCard); + + expect(onCloseMock).toHaveBeenCalledOnce(); + }); + + it("should refetch data on rerenders", async () => { + const user = userEvent.setup(); + const getUserConversationsSpy = vi.spyOn(OpenHands, "getUserConversations"); + getUserConversationsSpy.mockResolvedValue([...mockConversations]); + + function PanelWithToggle() { + const [isOpen, setIsOpen] = React.useState(true); + return ( + <> + + {isOpen && } + + ); + } + + const MyRouterStub = createRoutesStub([ + { + Component: PanelWithToggle, + path: "/", + }, + ]); + + renderWithProviders(, { + preloadedState: { + metrics: { + cost: null, + usage: null, + }, + }, + }); + + const toggleButton = screen.getByText("Toggle"); + + // Initial render + const cards = await screen.findAllByTestId("conversation-card"); + expect(cards).toHaveLength(3); + + // Toggle off + await user.click(toggleButton); + expect(screen.queryByTestId("conversation-card")).not.toBeInTheDocument(); + + // Toggle on + await user.click(toggleButton); + const newCards = await screen.findAllByTestId("conversation-card"); + expect(newCards).toHaveLength(3); + }); +}); diff --git a/frontend/__tests__/components/features/conversation-panel/utils.ts b/frontend/__tests__/components/features/conversation-panel/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..2bf27a9502e518c0e4ec4106192e1e3222e321f8 --- /dev/null +++ b/frontend/__tests__/components/features/conversation-panel/utils.ts @@ -0,0 +1,17 @@ +import { screen, within } from "@testing-library/react"; +import { UserEvent } from "@testing-library/user-event"; + +export const clickOnEditButton = async ( + user: UserEvent, + container?: HTMLElement, +) => { + const wrapper = container ? within(container) : screen; + + const ellipsisButton = wrapper.getByTestId("ellipsis-button"); + await user.click(ellipsisButton); + + const menu = wrapper.getByTestId("context-menu"); + const editButton = within(menu).getByTestId("edit-button"); + + await user.click(editButton); +}; diff --git a/frontend/__tests__/components/features/home/home-header.test.tsx b/frontend/__tests__/components/features/home/home-header.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a762b2f150ee7042b952a7767b161b7e77e39d46 --- /dev/null +++ b/frontend/__tests__/components/features/home/home-header.test.tsx @@ -0,0 +1,93 @@ +import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; +import { render, screen } from "@testing-library/react"; +import { Provider } from "react-redux"; +import { createRoutesStub } from "react-router"; +import { setupStore } from "test-utils"; +import { describe, expect, it, vi } from "vitest"; +import userEvent from "@testing-library/user-event"; +import { HomeHeader } from "#/components/features/home/home-header"; +import OpenHands from "#/api/open-hands"; + +// Mock the translation function +vi.mock("react-i18next", async () => { + const actual = await vi.importActual("react-i18next"); + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => { + // Return a mock translation for the test + const translations: Record = { + "HOME$LETS_START_BUILDING": "Let's start building", + "HOME$LAUNCH_FROM_SCRATCH": "Launch from Scratch", + "HOME$LOADING": "Loading...", + "HOME$OPENHANDS_DESCRIPTION": "OpenHands is an AI software engineer", + "HOME$NOT_SURE_HOW_TO_START": "Not sure how to start?", + "HOME$READ_THIS": "Read this" + }; + return translations[key] || key; + }, + i18n: { language: "en" }, + }), + }; +}); + +const renderHomeHeader = () => { + const RouterStub = createRoutesStub([ + { + Component: HomeHeader, + path: "/", + }, + { + Component: () =>
, + path: "/conversations/:conversationId", + }, + ]); + + return render(, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }); +}; + +describe("HomeHeader", () => { + it("should create an empty conversation and redirect when pressing the launch from scratch button", async () => { + const createConversationSpy = vi.spyOn(OpenHands, "createConversation"); + + renderHomeHeader(); + + const launchButton = screen.getByRole("button", { + name: /Launch from Scratch/i, + }); + await userEvent.click(launchButton); + + expect(createConversationSpy).toHaveBeenCalledExactlyOnceWith( + undefined, + undefined, + undefined, + [], + undefined, + undefined, + undefined, + ); + + // expect to be redirected to /conversations/:conversationId + await screen.findByTestId("conversation-screen"); + }); + + it("should change the launch button text to 'Loading...' when creating a conversation", async () => { + renderHomeHeader(); + + const launchButton = screen.getByRole("button", { + name: /Launch from Scratch/i, + }); + await userEvent.click(launchButton); + + expect(launchButton).toHaveTextContent(/Loading.../i); + expect(launchButton).toBeDisabled(); + }); +}); diff --git a/frontend/__tests__/components/features/home/repo-connector.test.tsx b/frontend/__tests__/components/features/home/repo-connector.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2c94f447b60e5c2c6146a017d9ff801c2aa401c5 --- /dev/null +++ b/frontend/__tests__/components/features/home/repo-connector.test.tsx @@ -0,0 +1,241 @@ +import { render, screen, waitFor, within } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import userEvent from "@testing-library/user-event"; +import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; +import { setupStore } from "test-utils"; +import { Provider } from "react-redux"; +import { createRoutesStub, Outlet } from "react-router"; +import OpenHands from "#/api/open-hands"; +import { GitRepository } from "#/types/git"; +import { RepoConnector } from "#/components/features/home/repo-connector"; +import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers"; + +const renderRepoConnector = () => { + const mockRepoSelection = vi.fn(); + const RouterStub = createRoutesStub([ + { + Component: () => , + path: "/", + }, + { + Component: () =>
, + path: "/conversations/:conversationId", + }, + { + Component: () => , + path: "/settings", + children: [ + { + Component: () =>
, + path: "/settings", + }, + { + Component: () =>
, + path: "/settings/git", + }, + ], + }, + ]); + + return render(, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }); +}; + +const MOCK_RESPOSITORIES: GitRepository[] = [ + { + id: 1, + full_name: "rbren/polaris", + git_provider: "github", + is_public: true, + }, + { + id: 2, + full_name: "All-Hands-AI/OpenHands", + git_provider: "github", + is_public: true, + }, +]; + +beforeEach(() => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: { + github: "some-token", + gitlab: null, + }, + }); +}); + +describe("RepoConnector", () => { + it("should render the repository connector section", () => { + renderRepoConnector(); + screen.getByTestId("repo-connector"); + }); + + it("should render the available repositories in the dropdown", async () => { + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_RESPOSITORIES); + + renderRepoConnector(); + + // Wait for the loading state to be replaced with the dropdown + const dropdown = await waitFor(() => screen.getByTestId("repo-dropdown")); + await userEvent.click(dropdown); + + await waitFor(() => { + screen.getByText("rbren/polaris"); + screen.getByText("All-Hands-AI/OpenHands"); + }); + }); + + it("should only enable the launch button if a repo is selected", async () => { + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_RESPOSITORIES); + + renderRepoConnector(); + + const launchButton = await screen.findByTestId("repo-launch-button"); + expect(launchButton).toBeDisabled(); + + // Wait for the loading state to be replaced with the dropdown + const dropdown = await waitFor(() => screen.getByTestId("repo-dropdown")); + await userEvent.click(dropdown); + await userEvent.click(screen.getByText("rbren/polaris")); + + expect(launchButton).toBeEnabled(); + }); + + it("should render the 'add git(hub|lab) repos' links if saas mode", async () => { + const getConfiSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - only return the APP_MODE + getConfiSpy.mockResolvedValue({ + APP_MODE: "saas", + }); + + renderRepoConnector(); + + await screen.findByText("Add GitHub repos"); + }); + + it("should not render the 'add git(hub|lab) repos' links if oss mode", async () => { + const getConfiSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - only return the APP_MODE + getConfiSpy.mockResolvedValue({ + APP_MODE: "oss", + }); + + renderRepoConnector(); + + expect(screen.queryByText("Add GitHub repos")).not.toBeInTheDocument(); + expect(screen.queryByText("Add GitLab repos")).not.toBeInTheDocument(); + }); + + it("should create a conversation and redirect with the selected repo when pressing the launch button", async () => { + const createConversationSpy = vi.spyOn(OpenHands, "createConversation"); + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_RESPOSITORIES); + + renderRepoConnector(); + + const repoConnector = screen.getByTestId("repo-connector"); + const launchButton = + await within(repoConnector).findByTestId("repo-launch-button"); + await userEvent.click(launchButton); + + // repo not selected yet + expect(createConversationSpy).not.toHaveBeenCalled(); + + // select a repository from the dropdown + const dropdown = await waitFor(() => + within(repoConnector).getByTestId("repo-dropdown"), + ); + await userEvent.click(dropdown); + + const repoOption = screen.getByText("rbren/polaris"); + await userEvent.click(repoOption); + await userEvent.click(launchButton); + + expect(createConversationSpy).toHaveBeenCalledExactlyOnceWith( + "rbren/polaris", + "github", + undefined, + [], + undefined, + undefined, + undefined, + ); + }); + + it("should change the launch button text to 'Loading...' when creating a conversation", async () => { + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_RESPOSITORIES); + + renderRepoConnector(); + + const launchButton = await screen.findByTestId("repo-launch-button"); + + // Wait for the loading state to be replaced with the dropdown + const dropdown = await waitFor(() => screen.getByTestId("repo-dropdown")); + await userEvent.click(dropdown); + await userEvent.click(screen.getByText("rbren/polaris")); + + await userEvent.click(launchButton); + expect(launchButton).toBeDisabled(); + expect(launchButton).toHaveTextContent(/Loading/i); + }); + + it("should not display a button to settings if the user is signed in with their git provider", async () => { + renderRepoConnector(); + + await waitFor(() => { + expect( + screen.queryByTestId("navigate-to-settings-button"), + ).not.toBeInTheDocument(); + }); + }); + + it("should display a button to settings if the user needs to sign in with their git provider", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: {}, + }); + renderRepoConnector(); + + const goToSettingsButton = await screen.findByTestId( + "navigate-to-settings-button", + ); + const dropdown = screen.queryByTestId("repo-dropdown"); + const launchButton = screen.queryByTestId("repo-launch-button"); + const providerLinks = screen.queryAllByText(/add git(hub|lab) repos/i); + + expect(dropdown).not.toBeInTheDocument(); + expect(launchButton).not.toBeInTheDocument(); + expect(providerLinks.length).toBe(0); + + expect(goToSettingsButton).toBeInTheDocument(); + + await userEvent.click(goToSettingsButton); + await screen.findByTestId("git-settings-screen"); + }); +}); diff --git a/frontend/__tests__/components/features/home/repo-selection-form.test.tsx b/frontend/__tests__/components/features/home/repo-selection-form.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..86a77537345a901639ef9bff4d87ff602ac4a681 --- /dev/null +++ b/frontend/__tests__/components/features/home/repo-selection-form.test.tsx @@ -0,0 +1,259 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, vi, beforeEach, it } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import userEvent from "@testing-library/user-event"; +import { RepositorySelectionForm } from "../../../../src/components/features/home/repo-selection-form"; +import OpenHands from "#/api/open-hands"; +import { GitRepository } from "#/types/git"; + +// Create mock functions +const mockUseUserRepositories = vi.fn(); +const mockUseCreateConversation = vi.fn(); +const mockUseIsCreatingConversation = vi.fn(); +const mockUseTranslation = vi.fn(); +const mockUseAuth = vi.fn(); + +// Setup default mock returns +mockUseUserRepositories.mockReturnValue({ + data: [], + isLoading: false, + isError: false, +}); + +mockUseCreateConversation.mockReturnValue({ + mutate: vi.fn(), + isPending: false, + isSuccess: false, +}); + +mockUseIsCreatingConversation.mockReturnValue(false); + +mockUseTranslation.mockReturnValue({ t: (key: string) => key }); + +mockUseAuth.mockReturnValue({ + isAuthenticated: true, + isLoading: false, + providersAreSet: true, + user: { + id: 1, + login: "testuser", + avatar_url: "https://example.com/avatar.png", + name: "Test User", + email: "test@example.com", + company: "Test Company", + }, + login: vi.fn(), + logout: vi.fn(), +}); + +vi.mock("#/hooks/mutation/use-create-conversation", () => ({ + useCreateConversation: () => mockUseCreateConversation(), +})); + +vi.mock("#/hooks/use-is-creating-conversation", () => ({ + useIsCreatingConversation: () => mockUseIsCreatingConversation(), +})); + +vi.mock("react-i18next", () => ({ + useTranslation: () => mockUseTranslation(), +})); + +vi.mock("#/context/auth-context", () => ({ + useAuth: () => mockUseAuth(), +})); + +vi.mock("#/hooks/use-debounce", () => ({ + useDebounce: (value: string) => value, +})); + +const mockOnRepoSelection = vi.fn(); +const renderForm = () => + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + +describe("RepositorySelectionForm", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("shows loading indicator when repositories are being fetched", () => { + const MOCK_REPOS: GitRepository[] = [ + { + id: 1, + full_name: "user/repo1", + git_provider: "github", + is_public: true, + }, + { + id: 2, + full_name: "user/repo2", + git_provider: "github", + is_public: true, + }, + ]; + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_REPOS); + + renderForm(); + + // Check if loading indicator is displayed + expect(screen.getByTestId("repo-dropdown-loading")).toBeInTheDocument(); + expect(screen.getByText("HOME$LOADING_REPOSITORIES")).toBeInTheDocument(); + }); + + it("shows dropdown when repositories are loaded", async () => { + const MOCK_REPOS: GitRepository[] = [ + { + id: 1, + full_name: "user/repo1", + git_provider: "github", + is_public: true, + }, + { + id: 2, + full_name: "user/repo2", + git_provider: "github", + is_public: true, + }, + ]; + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_REPOS); + + renderForm(); + expect(await screen.findByTestId("repo-dropdown")).toBeInTheDocument(); + }); + + it("shows error message when repository fetch fails", async () => { + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockRejectedValue( + new Error("Failed to load"), + ); + + renderForm(); + + expect( + await screen.findByTestId("repo-dropdown-error"), + ).toBeInTheDocument(); + expect( + screen.getByText("HOME$FAILED_TO_LOAD_REPOSITORIES"), + ).toBeInTheDocument(); + }); + + it("should call the search repos API when searching a URL", async () => { + const MOCK_REPOS: GitRepository[] = [ + { + id: 1, + full_name: "user/repo1", + git_provider: "github", + is_public: true, + }, + { + id: 2, + full_name: "user/repo2", + git_provider: "github", + is_public: true, + }, + ]; + + const MOCK_SEARCH_REPOS: GitRepository[] = [ + { + id: 3, + full_name: "kubernetes/kubernetes", + git_provider: "github", + is_public: true, + }, + ]; + + const searchGitReposSpy = vi.spyOn(OpenHands, "searchGitRepositories"); + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + + searchGitReposSpy.mockResolvedValue(MOCK_SEARCH_REPOS); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_REPOS); + + renderForm(); + + const input = await screen.findByTestId("repo-dropdown"); + await userEvent.click(input); + + for (const repo of MOCK_REPOS) { + expect(screen.getByText(repo.full_name)).toBeInTheDocument(); + } + expect( + screen.queryByText(MOCK_SEARCH_REPOS[0].full_name), + ).not.toBeInTheDocument(); + + expect(searchGitReposSpy).not.toHaveBeenCalled(); + + await userEvent.type(input, "https://github.com/kubernetes/kubernetes"); + expect(searchGitReposSpy).toHaveBeenLastCalledWith( + "kubernetes/kubernetes", + 3, + ); + + expect( + screen.getByText(MOCK_SEARCH_REPOS[0].full_name), + ).toBeInTheDocument(); + for (const repo of MOCK_REPOS) { + expect(screen.queryByText(repo.full_name)).not.toBeInTheDocument(); + } + }); + + it("should call onRepoSelection when a searched repository is selected", async () => { + const MOCK_SEARCH_REPOS: GitRepository[] = [ + { + id: 3, + full_name: "kubernetes/kubernetes", + git_provider: "github", + is_public: true, + }, + ]; + + const searchGitReposSpy = vi.spyOn(OpenHands, "searchGitRepositories"); + searchGitReposSpy.mockResolvedValue(MOCK_SEARCH_REPOS); + + renderForm(); + + const input = await screen.findByTestId("repo-dropdown"); + + await userEvent.type(input, "https://github.com/kubernetes/kubernetes"); + expect(searchGitReposSpy).toHaveBeenLastCalledWith( + "kubernetes/kubernetes", + 3, + ); + + const searchedRepo = screen.getByText(MOCK_SEARCH_REPOS[0].full_name); + expect(searchedRepo).toBeInTheDocument(); + + await userEvent.click(searchedRepo); + expect(mockOnRepoSelection).toHaveBeenCalledWith( + MOCK_SEARCH_REPOS[0].full_name, + ); + }); +}); diff --git a/frontend/__tests__/components/features/home/task-card.test.tsx b/frontend/__tests__/components/features/home/task-card.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..83bf2c17636c8c20943f4948a543af9b033a8b05 --- /dev/null +++ b/frontend/__tests__/components/features/home/task-card.test.tsx @@ -0,0 +1,108 @@ +import { render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import userEvent from "@testing-library/user-event"; +import { Provider } from "react-redux"; +import { createRoutesStub } from "react-router"; +import { setupStore } from "test-utils"; +import { SuggestedTask } from "#/components/features/home/tasks/task.types"; +import OpenHands from "#/api/open-hands"; +import { TaskCard } from "#/components/features/home/tasks/task-card"; +import { GitRepository } from "#/types/git"; + +const MOCK_TASK_1: SuggestedTask = { + issue_number: 123, + repo: "repo1", + title: "Task 1", + task_type: "MERGE_CONFLICTS", + git_provider: "github", +}; + +const MOCK_RESPOSITORIES: GitRepository[] = [ + { id: 1, full_name: "repo1", git_provider: "github", is_public: true }, + { id: 2, full_name: "repo2", git_provider: "github", is_public: true }, + { id: 3, full_name: "repo3", git_provider: "gitlab", is_public: true }, + { id: 4, full_name: "repo4", git_provider: "gitlab", is_public: true }, +]; + +const renderTaskCard = (task = MOCK_TASK_1) => { + const RouterStub = createRoutesStub([ + { + Component: () => , + path: "/", + }, + { + Component: () =>
, + path: "/conversations/:conversationId", + }, + ]); + + return render(, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }); +}; + +describe("TaskCard", () => { + it("format the issue id", async () => { + renderTaskCard(); + + const taskId = screen.getByTestId("task-id"); + expect(taskId).toHaveTextContent(/#123/i); + }); + + it("should call createConversation when clicking the launch button", async () => { + const createConversationSpy = vi.spyOn(OpenHands, "createConversation"); + + renderTaskCard(); + + const launchButton = screen.getByTestId("task-launch-button"); + await userEvent.click(launchButton); + + expect(createConversationSpy).toHaveBeenCalled(); + }); + + describe("creating suggested task conversation", () => { + beforeEach(() => { + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_RESPOSITORIES); + }); + + it("should call create conversation with suggest task trigger and selected suggested task", async () => { + const createConversationSpy = vi.spyOn(OpenHands, "createConversation"); + + renderTaskCard(MOCK_TASK_1); + + const launchButton = screen.getByTestId("task-launch-button"); + await userEvent.click(launchButton); + + expect(createConversationSpy).toHaveBeenCalledWith( + MOCK_RESPOSITORIES[0].full_name, + MOCK_RESPOSITORIES[0].git_provider, + undefined, + [], + undefined, + MOCK_TASK_1, + undefined, + ); + }); + }); + + it("should disable the launch button and update text content when creating a conversation", async () => { + renderTaskCard(); + + const launchButton = screen.getByTestId("task-launch-button"); + await userEvent.click(launchButton); + + expect(launchButton).toHaveTextContent(/Loading/i); + expect(launchButton).toBeDisabled(); + }); +}); diff --git a/frontend/__tests__/components/features/home/task-suggestions.test.tsx b/frontend/__tests__/components/features/home/task-suggestions.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4a3fa3a1f3bd3ccf7fb04afc44cdbd0ac23c73c8 --- /dev/null +++ b/frontend/__tests__/components/features/home/task-suggestions.test.tsx @@ -0,0 +1,96 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Provider } from "react-redux"; +import { createRoutesStub } from "react-router"; +import { setupStore } from "test-utils"; +import { TaskSuggestions } from "#/components/features/home/tasks/task-suggestions"; +import { SuggestionsService } from "#/api/suggestions-service/suggestions-service.api"; +import { MOCK_TASKS } from "#/mocks/task-suggestions-handlers"; + +const renderTaskSuggestions = () => { + const RouterStub = createRoutesStub([ + { + Component: () => , + path: "/", + }, + { + Component: () =>
, + path: "/conversations/:conversationId", + }, + { + Component: () =>
, + path: "/settings", + }, + ]); + + return render(, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }); +}; + +describe("TaskSuggestions", () => { + const getSuggestedTasksSpy = vi.spyOn( + SuggestionsService, + "getSuggestedTasks", + ); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render the task suggestions section", () => { + renderTaskSuggestions(); + screen.getByTestId("task-suggestions"); + }); + + it("should render an empty message if there are no tasks", async () => { + getSuggestedTasksSpy.mockResolvedValue([]); + renderTaskSuggestions(); + await screen.findByText(/No tasks available/i); + }); + + it("should render the task groups with the correct titles", async () => { + getSuggestedTasksSpy.mockResolvedValue(MOCK_TASKS); + renderTaskSuggestions(); + + await waitFor(() => { + MOCK_TASKS.forEach((taskGroup) => { + screen.getByText(taskGroup.title); + }); + }); + }); + + it("should render the task cards with the correct task details", async () => { + getSuggestedTasksSpy.mockResolvedValue(MOCK_TASKS); + renderTaskSuggestions(); + + await waitFor(() => { + MOCK_TASKS.forEach((task) => { + screen.getByText(task.title); + }); + }); + }); + + it("should render skeletons when loading", async () => { + getSuggestedTasksSpy.mockResolvedValue(MOCK_TASKS); + renderTaskSuggestions(); + + const skeletons = await screen.findAllByTestId("task-group-skeleton"); + expect(skeletons.length).toBeGreaterThan(0); + + await waitFor(() => { + MOCK_TASKS.forEach((taskGroup) => { + screen.getByText(taskGroup.title); + }); + }); + + expect(screen.queryByTestId("task-group-skeleton")).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/components/features/payment/payment-form.test.tsx b/frontend/__tests__/components/features/payment/payment-form.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7269933424103fd3f8fa7b0cc229aef6a19917e6 --- /dev/null +++ b/frontend/__tests__/components/features/payment/payment-form.test.tsx @@ -0,0 +1,180 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, beforeEach, describe, expect, it, test, vi } from "vitest"; +import OpenHands from "#/api/open-hands"; +import { PaymentForm } from "#/components/features/payment/payment-form"; + +describe("PaymentForm", () => { + const getBalanceSpy = vi.spyOn(OpenHands, "getBalance"); + const createCheckoutSessionSpy = vi.spyOn(OpenHands, "createCheckoutSession"); + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + + const renderPaymentForm = () => + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + beforeEach(() => { + // useBalance hook will return the balance only if the APP_MODE is "saas" and the billing feature is enabled + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + GITHUB_CLIENT_ID: "123", + POSTHOG_CLIENT_KEY: "456", + FEATURE_FLAGS: { + ENABLE_BILLING: true, + HIDE_LLM_SETTINGS: false, + }, + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render the users current balance", async () => { + getBalanceSpy.mockResolvedValue("100.50"); + renderPaymentForm(); + + await waitFor(() => { + const balance = screen.getByTestId("user-balance"); + expect(balance).toHaveTextContent("$100.50"); + }); + }); + + it("should render the users current balance to two decimal places", async () => { + getBalanceSpy.mockResolvedValue("100"); + renderPaymentForm(); + + await waitFor(() => { + const balance = screen.getByTestId("user-balance"); + expect(balance).toHaveTextContent("$100.00"); + }); + }); + + test("the user can top-up a specific amount", async () => { + const user = userEvent.setup(); + renderPaymentForm(); + + const topUpInput = await screen.findByTestId("top-up-input"); + await user.type(topUpInput, "50"); + + const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); + await user.click(topUpButton); + + expect(createCheckoutSessionSpy).toHaveBeenCalledWith(50); + }); + + it("should only accept integer values", async () => { + const user = userEvent.setup(); + renderPaymentForm(); + + const topUpInput = await screen.findByTestId("top-up-input"); + await user.type(topUpInput, "50"); + + const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); + await user.click(topUpButton); + + expect(createCheckoutSessionSpy).toHaveBeenCalledWith(50); + }); + + it("should disable the top-up button if the user enters an invalid amount", async () => { + const user = userEvent.setup(); + renderPaymentForm(); + + const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); + expect(topUpButton).toBeDisabled(); + + const topUpInput = await screen.findByTestId("top-up-input"); + await user.type(topUpInput, " "); + + expect(topUpButton).toBeDisabled(); + }); + + it("should disable the top-up button after submission", async () => { + const user = userEvent.setup(); + renderPaymentForm(); + + const topUpInput = await screen.findByTestId("top-up-input"); + await user.type(topUpInput, "50"); + + const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); + await user.click(topUpButton); + + expect(topUpButton).toBeDisabled(); + }); + + describe("prevent submission if", () => { + test("user enters a negative amount", async () => { + const user = userEvent.setup(); + renderPaymentForm(); + + const topUpInput = await screen.findByTestId("top-up-input"); + await user.type(topUpInput, "-50"); + + const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); + await user.click(topUpButton); + + expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); + }); + + test("user enters an empty string", async () => { + const user = userEvent.setup(); + renderPaymentForm(); + + const topUpInput = await screen.findByTestId("top-up-input"); + await user.type(topUpInput, " "); + + const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); + await user.click(topUpButton); + + expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); + }); + + test("user enters a non-numeric value", async () => { + const user = userEvent.setup(); + renderPaymentForm(); + + // With type="number", the browser would prevent non-numeric input, + // but we'll test the validation logic anyway + const topUpInput = await screen.findByTestId("top-up-input"); + await user.type(topUpInput, "abc"); + + const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); + await user.click(topUpButton); + + expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); + }); + + test("user enters less than the minimum amount", async () => { + const user = userEvent.setup(); + renderPaymentForm(); + + const topUpInput = await screen.findByTestId("top-up-input"); + await user.type(topUpInput, "9"); // test assumes the minimum is 10 + + const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); + await user.click(topUpButton); + + expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); + }); + + test("user enters a decimal value", async () => { + const user = userEvent.setup(); + renderPaymentForm(); + + // With step="1", the browser would validate this, but we'll test our validation logic + const topUpInput = await screen.findByTestId("top-up-input"); + await user.type(topUpInput, "50.5"); + + const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT"); + await user.click(topUpButton); + + expect(createCheckoutSessionSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/frontend/__tests__/components/features/settings/api-keys-manager.test.tsx b/frontend/__tests__/components/features/settings/api-keys-manager.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..711c50a19443e765d6fd038b9509a626c60e6055 --- /dev/null +++ b/frontend/__tests__/components/features/settings/api-keys-manager.test.tsx @@ -0,0 +1,59 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ApiKeysManager } from "#/components/features/settings/api-keys-manager"; + +// Mock the react-i18next +vi.mock("react-i18next", async () => { + const actual = await vi.importActual("react-i18next"); + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => key, + }), + Trans: ({ i18nKey, components }: { i18nKey: string; components: Record }) => { + // Simplified Trans component that renders the link + if (i18nKey === "SETTINGS$API_KEYS_DESCRIPTION") { + return ( + + API keys allow you to authenticate with the OpenHands API programmatically. + Keep your API keys secure; anyone with your API key can access your account. + For more information on how to use the API, see our {components.a} + + ); + } + return {i18nKey}; + }, + }; +}); + +// Mock the API keys hook +vi.mock("#/hooks/query/use-api-keys", () => ({ + useApiKeys: () => ({ + data: [], + isLoading: false, + error: null, + }), +})); + +describe("ApiKeysManager", () => { + const renderComponent = () => { + const queryClient = new QueryClient(); + return render( + + + + ); + }; + + it("should render the API documentation link", () => { + renderComponent(); + + // Find the link to the API documentation + const link = screen.getByRole("link"); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute("href", "https://docs.all-hands.dev/usage/cloud/cloud-api"); + expect(link).toHaveAttribute("target", "_blank"); + expect(link).toHaveAttribute("rel", "noopener noreferrer"); + }); +}); \ No newline at end of file diff --git a/frontend/__tests__/components/features/sidebar/sidebar.test.tsx b/frontend/__tests__/components/features/sidebar/sidebar.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9229cb38287071fd3b2071c41e78a69dd6769244 --- /dev/null +++ b/frontend/__tests__/components/features/sidebar/sidebar.test.tsx @@ -0,0 +1,32 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { renderWithProviders } from "test-utils"; +import { createRoutesStub } from "react-router"; +import { waitFor } from "@testing-library/react"; +import { Sidebar } from "#/components/features/sidebar/sidebar"; +import OpenHands from "#/api/open-hands"; + +// These tests will now fail because the conversation panel is rendered through a portal +// and technically not a child of the Sidebar component. + +const RouterStub = createRoutesStub([ + { + path: "/conversation/:conversationId", + Component: () => , + }, +]); + +const renderSidebar = () => + renderWithProviders(); + +describe("Sidebar", () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should fetch settings data on mount", async () => { + renderSidebar(); + await waitFor(() => expect(getSettingsSpy).toHaveBeenCalled()); + }); +}); diff --git a/frontend/__tests__/components/feedback-actions.test.tsx b/frontend/__tests__/components/feedback-actions.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ad619017292ebfd9bfa6fee4849701892631dc62 --- /dev/null +++ b/frontend/__tests__/components/feedback-actions.test.tsx @@ -0,0 +1,76 @@ +import { screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { renderWithProviders } from "test-utils"; +import { TrajectoryActions } from "#/components/features/trajectory/trajectory-actions"; + +describe("TrajectoryActions", () => { + const user = userEvent.setup(); + const onPositiveFeedback = vi.fn(); + const onNegativeFeedback = vi.fn(); + const onExportTrajectory = vi.fn(); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render correctly", () => { + renderWithProviders( + , + ); + + const actions = screen.getByTestId("feedback-actions"); + within(actions).getByTestId("positive-feedback"); + within(actions).getByTestId("negative-feedback"); + within(actions).getByTestId("export-trajectory"); + }); + + it("should call onPositiveFeedback when positive feedback is clicked", async () => { + renderWithProviders( + , + ); + + const positiveFeedback = screen.getByTestId("positive-feedback"); + await user.click(positiveFeedback); + + expect(onPositiveFeedback).toHaveBeenCalled(); + }); + + it("should call onNegativeFeedback when negative feedback is clicked", async () => { + renderWithProviders( + , + ); + + const negativeFeedback = screen.getByTestId("negative-feedback"); + await user.click(negativeFeedback); + + expect(onNegativeFeedback).toHaveBeenCalled(); + }); + + it("should call onExportTrajectory when export button is clicked", async () => { + renderWithProviders( + , + ); + + const exportButton = screen.getByTestId("export-trajectory"); + await user.click(exportButton); + + expect(onExportTrajectory).toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/components/feedback-form.test.tsx b/frontend/__tests__/components/feedback-form.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6c571ee02b2516948984304bb3ad6edfd2deb29a --- /dev/null +++ b/frontend/__tests__/components/feedback-form.test.tsx @@ -0,0 +1,68 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +// Mock useParams before importing components +vi.mock("react-router", async () => { + const actual = await vi.importActual("react-router"); + return { + ...(actual as object), + useParams: () => ({ conversationId: "test-conversation-id" }), + }; +}); + +import { screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { renderWithProviders } from "test-utils"; +import { FeedbackForm } from "#/components/features/feedback/feedback-form"; +import { I18nKey } from "#/i18n/declaration"; + +describe("FeedbackForm", () => { + const user = userEvent.setup(); + const onCloseMock = vi.fn(); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render correctly", () => { + renderWithProviders( + , + ); + + screen.getByLabelText(I18nKey.FEEDBACK$EMAIL_LABEL); + screen.getByLabelText(I18nKey.FEEDBACK$PRIVATE_LABEL); + screen.getByLabelText(I18nKey.FEEDBACK$PUBLIC_LABEL); + + screen.getByRole("button", { name: I18nKey.FEEDBACK$SHARE_LABEL }); + screen.getByRole("button", { name: I18nKey.FEEDBACK$CANCEL_LABEL }); + }); + + it("should switch between private and public permissions", async () => { + renderWithProviders( + , + ); + const privateRadio = screen.getByLabelText(I18nKey.FEEDBACK$PRIVATE_LABEL); + const publicRadio = screen.getByLabelText(I18nKey.FEEDBACK$PUBLIC_LABEL); + + expect(privateRadio).toBeChecked(); // private is the default value + expect(publicRadio).not.toBeChecked(); + + await user.click(publicRadio); + expect(publicRadio).toBeChecked(); + expect(privateRadio).not.toBeChecked(); + + await user.click(privateRadio); + expect(privateRadio).toBeChecked(); + expect(publicRadio).not.toBeChecked(); + }); + + it("should call onClose when the close button is clicked", async () => { + renderWithProviders( + , + ); + await user.click( + screen.getByRole("button", { name: I18nKey.FEEDBACK$CANCEL_LABEL }), + ); + + expect(onCloseMock).toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/components/file-operations.test.tsx b/frontend/__tests__/components/file-operations.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a8ad9e14dbff31bf0bb617ba5ea2dcca3ab303ea --- /dev/null +++ b/frontend/__tests__/components/file-operations.test.tsx @@ -0,0 +1,11 @@ +import { describe, it } from "vitest"; + +describe("File Operations Messages", () => { + it.todo("should show success indicator for successful file read operation"); + + it.todo("should show failure indicator for failed file read operation"); + + it.todo("should show success indicator for successful file edit operation"); + + it.todo("should show failure indicator for failed file edit operation"); +}); diff --git a/frontend/__tests__/components/image-preview.test.tsx b/frontend/__tests__/components/image-preview.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..39d2f089fb9b1e1ce18d37755fc2c3e934bd2f89 --- /dev/null +++ b/frontend/__tests__/components/image-preview.test.tsx @@ -0,0 +1,37 @@ +import { ImagePreview } from "#/components/features/images/image-preview"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; + +describe("ImagePreview", () => { + it("should render an image", () => { + render( + , + ); + const img = screen.getByRole("img"); + + expect(screen.getByTestId("image-preview")).toBeInTheDocument(); + expect(img).toHaveAttribute("src", "https://example.com/image.jpg"); + }); + + it("should call onRemove when the close button is clicked", async () => { + const user = userEvent.setup(); + const onRemoveMock = vi.fn(); + render( + , + ); + + const closeButton = screen.getByRole("button"); + await user.click(closeButton); + + expect(onRemoveMock).toHaveBeenCalledOnce(); + }); + + it("shoud not display the close button when onRemove is not provided", () => { + render(); + expect(screen.queryByRole("button")).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/components/interactive-chat-box.test.tsx b/frontend/__tests__/components/interactive-chat-box.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6ec74ddc88a6f99f1d55829ac99753b6ec2926fe --- /dev/null +++ b/frontend/__tests__/components/interactive-chat-box.test.tsx @@ -0,0 +1,190 @@ +import { render, screen, within, fireEvent } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { InteractiveChatBox } from "#/components/features/chat/interactive-chat-box"; + +describe("InteractiveChatBox", () => { + const onSubmitMock = vi.fn(); + const onStopMock = vi.fn(); + + beforeAll(() => { + global.URL.createObjectURL = vi + .fn() + .mockReturnValue("blob:http://example.com"); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render", () => { + render(); + + const chatBox = screen.getByTestId("interactive-chat-box"); + within(chatBox).getByTestId("chat-input"); + within(chatBox).getByTestId("upload-image-input"); + }); + + it.fails("should set custom values", () => { + render( + , + ); + + const chatBox = screen.getByTestId("interactive-chat-box"); + const chatInput = within(chatBox).getByTestId("chat-input"); + + expect(chatInput).toHaveValue("Hello, world!"); + }); + + it("should display the image previews when images are uploaded", async () => { + const user = userEvent.setup(); + render(); + + const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }); + const input = screen.getByTestId("upload-image-input"); + + expect(screen.queryAllByTestId("image-preview")).toHaveLength(0); + + await user.upload(input, file); + expect(screen.queryAllByTestId("image-preview")).toHaveLength(1); + + const files = [ + new File(["(⌐□_□)"], "chucknorris2.png", { type: "image/png" }), + new File(["(⌐□_□)"], "chucknorris3.png", { type: "image/png" }), + ]; + + await user.upload(input, files); + expect(screen.queryAllByTestId("image-preview")).toHaveLength(3); + }); + + it("should remove the image preview when the close button is clicked", async () => { + const user = userEvent.setup(); + render(); + + const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }); + const input = screen.getByTestId("upload-image-input"); + + await user.upload(input, file); + expect(screen.queryAllByTestId("image-preview")).toHaveLength(1); + + const imagePreview = screen.getByTestId("image-preview"); + const closeButton = within(imagePreview).getByRole("button"); + await user.click(closeButton); + + expect(screen.queryAllByTestId("image-preview")).toHaveLength(0); + }); + + it("should call onSubmit with the message and images", async () => { + const user = userEvent.setup(); + render(); + + const textarea = within(screen.getByTestId("chat-input")).getByRole( + "textbox", + ); + const input = screen.getByTestId("upload-image-input"); + const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }); + + await user.upload(input, file); + await user.type(textarea, "Hello, world!"); + await user.keyboard("{Enter}"); + + expect(onSubmitMock).toHaveBeenCalledWith("Hello, world!", [file]); + + // clear images after submission + expect(screen.queryAllByTestId("image-preview")).toHaveLength(0); + }); + + it("should disable the submit button", async () => { + const user = userEvent.setup(); + render( + , + ); + + const button = screen.getByRole("button"); + expect(button).toBeDisabled(); + + await user.click(button); + expect(onSubmitMock).not.toHaveBeenCalled(); + }); + + it("should display the stop button if set and call onStop when clicked", async () => { + const user = userEvent.setup(); + render( + , + ); + + const stopButton = screen.getByTestId("stop-button"); + expect(stopButton).toBeInTheDocument(); + + await user.click(stopButton); + expect(onStopMock).toHaveBeenCalledOnce(); + }); + + it("should handle image upload and message submission correctly", async () => { + const user = userEvent.setup(); + const onSubmit = vi.fn(); + const onStop = vi.fn(); + const onChange = vi.fn(); + + const { rerender } = render( + + ); + + // Upload an image via the upload button - this should NOT clear the text input + const file = new File(["dummy content"], "test.png", { type: "image/png" }); + const input = screen.getByTestId("upload-image-input"); + await user.upload(input, file); + + // Verify text input was not cleared + expect(screen.getByRole("textbox")).toHaveValue("test message"); + expect(onChange).not.toHaveBeenCalledWith(""); + + // Submit the message with image + const submitButton = screen.getByRole("button", { name: "BUTTON$SEND" }); + await user.click(submitButton); + + // Verify onSubmit was called with the message and image + expect(onSubmit).toHaveBeenCalledWith("test message", [file]); + + // Verify onChange was called to clear the text input + expect(onChange).toHaveBeenCalledWith(""); + + // Simulate parent component updating the value prop + rerender( + + ); + + // Verify the text input was cleared + expect(screen.getByRole("textbox")).toHaveValue(""); + + // Upload another image - this should NOT clear the text input + onChange.mockClear(); + await user.upload(input, file); + + // Verify text input is still empty and onChange was not called + expect(screen.getByRole("textbox")).toHaveValue(""); + expect(onChange).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/components/jupyter/jupyter.test.tsx b/frontend/__tests__/components/jupyter/jupyter.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4683a4763479ac4fa2b722aeb0e09117870a35dd --- /dev/null +++ b/frontend/__tests__/components/jupyter/jupyter.test.tsx @@ -0,0 +1,45 @@ +import { render, screen } from "@testing-library/react"; +import { Provider } from "react-redux"; +import { configureStore } from "@reduxjs/toolkit"; +import { JupyterEditor } from "#/components/features/jupyter/jupyter"; +import { jupyterReducer } from "#/state/jupyter-slice"; +import { vi, describe, it, expect } from "vitest"; + +describe("JupyterEditor", () => { + const mockStore = configureStore({ + reducer: { + fileState: () => ({}), + initalQuery: () => ({}), + browser: () => ({}), + chat: () => ({}), + code: () => ({}), + cmd: () => ({}), + agent: () => ({}), + jupyter: jupyterReducer, + securityAnalyzer: () => ({}), + status: () => ({}), + }, + preloadedState: { + jupyter: { + cells: Array(20).fill({ + content: "Test cell content", + type: "input", + output: "Test output", + }), + }, + }, + }); + + it("should have a scrollable container", () => { + render( + +
+ +
+
+ ); + + const container = screen.getByTestId("jupyter-container"); + expect(container).toHaveClass("flex-1 overflow-y-auto"); + }); +}); diff --git a/frontend/__tests__/components/landing-translations.test.tsx b/frontend/__tests__/components/landing-translations.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7075ccae4dcc1738c8b005de35cf39b690e69471 --- /dev/null +++ b/frontend/__tests__/components/landing-translations.test.tsx @@ -0,0 +1,190 @@ +import { render, screen } from "@testing-library/react"; +import { test, expect, describe, vi } from "vitest"; +import { useTranslation } from "react-i18next"; +import translations from "../../src/i18n/translation.json"; +import { UserAvatar } from "../../src/components/features/sidebar/user-avatar"; + +vi.mock("@heroui/react", () => ({ + Tooltip: ({ content, children }: { content: string; children: React.ReactNode }) => ( +
+ {children} +
{content}
+
+ ), +})); + +const supportedLanguages = ['en', 'ja', 'zh-CN', 'zh-TW', 'ko-KR', 'de', 'no', 'it', 'pt', 'es', 'ar', 'fr', 'tr']; + +// Helper function to check if a translation exists for all supported languages +function checkTranslationExists(key: string) { + const missingTranslations: string[] = []; + + const translationEntry = (translations as Record>)[key]; + if (!translationEntry) { + throw new Error(`Translation key "${key}" does not exist in translation.json`); + } + + for (const lang of supportedLanguages) { + if (!translationEntry[lang]) { + missingTranslations.push(lang); + } + } + + return missingTranslations; +} + +// Helper function to find duplicate translation keys +function findDuplicateKeys(obj: Record) { + const seen = new Set(); + const duplicates = new Set(); + + // Only check top-level keys as these are our translation keys + for (const key in obj) { + if (seen.has(key)) { + duplicates.add(key); + } else { + seen.add(key); + } + } + + return Array.from(duplicates); +} + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => { + const translationEntry = (translations as Record>)[key]; + return translationEntry?.ja || key; + }, + }), +})); + +describe("Landing page translations", () => { + test("should render Japanese translations correctly", () => { + // Mock a simple component that uses the translations + const TestComponent = () => { + const { t } = useTranslation(); + return ( +
+ {}} /> +
+

{t("LANDING$TITLE")}

+ + + + + +
+
+ {t("WORKSPACE$TERMINAL_TAB_LABEL")} + {t("WORKSPACE$BROWSER_TAB_LABEL")} + {t("WORKSPACE$JUPYTER_TAB_LABEL")} + {t("WORKSPACE$CODE_EDITOR_TAB_LABEL")} +
+
{t("WORKSPACE$TITLE")}
+ +
+ {t("TERMINAL$WAITING_FOR_CLIENT")} + {t("STATUS$CONNECTED")} + {t("STATUS$CONNECTED_TO_SERVER")} +
+
+ {`5 ${t("TIME$MINUTES_AGO")}`} + {`2 ${t("TIME$HOURS_AGO")}`} + {`3 ${t("TIME$DAYS_AGO")}`} +
+
+ ); + }; + + render(); + + // Check main content translations + expect(screen.getByText("開発を始めましょう!")).toBeInTheDocument(); + expect(screen.getByText("VS Codeで開く")).toBeInTheDocument(); + expect(screen.getByText("テストカバレッジを向上させる")).toBeInTheDocument(); + expect(screen.getByText("Dependabot PRを自動マージ")).toBeInTheDocument(); + expect(screen.getByText("READMEを改善")).toBeInTheDocument(); + expect(screen.getByText("依存関係を整理")).toBeInTheDocument(); + + // Check user avatar tooltip + const userAvatar = screen.getByTestId("user-avatar"); + userAvatar.focus(); + expect(screen.getByText("アカウント設定")).toBeInTheDocument(); + + // Check tab labels + const tabs = screen.getByTestId("tabs"); + expect(tabs).toHaveTextContent("ターミナル"); + expect(tabs).toHaveTextContent("ブラウザ"); + expect(tabs).toHaveTextContent("Jupyter"); + expect(tabs).toHaveTextContent("コードエディタ"); + + // Check workspace label and new project button + expect(screen.getByTestId("workspace-label")).toHaveTextContent("ワークスペース"); + expect(screen.getByTestId("new-project")).toHaveTextContent("新規プロジェクト"); + + // Check status messages + const status = screen.getByTestId("status"); + expect(status).toHaveTextContent("クライアントの準備を待機中"); + expect(status).toHaveTextContent("接続済み"); + expect(status).toHaveTextContent("サーバーに接続済み"); + + // Check account settings menu + expect(screen.getByText("アカウント設定")).toBeInTheDocument(); + + // Check time-related translations + const time = screen.getByTestId("time"); + expect(time).toHaveTextContent("5 分前"); + expect(time).toHaveTextContent("2 時間前"); + expect(time).toHaveTextContent("3 日前"); + }); + + test("all translation keys should have translations for all supported languages", () => { + // Test all translation keys used in the component + const translationKeys = [ + "LANDING$TITLE", + "VSCODE$OPEN", + "SUGGESTIONS$INCREASE_TEST_COVERAGE", + "SUGGESTIONS$AUTO_MERGE_PRS", + "SUGGESTIONS$FIX_README", + "SUGGESTIONS$CLEAN_DEPENDENCIES", + "WORKSPACE$TERMINAL_TAB_LABEL", + "WORKSPACE$BROWSER_TAB_LABEL", + "WORKSPACE$JUPYTER_TAB_LABEL", + "WORKSPACE$CODE_EDITOR_TAB_LABEL", + "WORKSPACE$TITLE", + "PROJECT$NEW_PROJECT", + "TERMINAL$WAITING_FOR_CLIENT", + "STATUS$CONNECTED", + "STATUS$CONNECTED_TO_SERVER", + "TIME$MINUTES_AGO", + "TIME$HOURS_AGO", + "TIME$DAYS_AGO" + ]; + + // Check all keys and collect missing translations + const missingTranslationsMap = new Map(); + translationKeys.forEach(key => { + const missing = checkTranslationExists(key); + if (missing.length > 0) { + missingTranslationsMap.set(key, missing); + } + }); + + // If any translations are missing, throw an error with all missing translations + if (missingTranslationsMap.size > 0) { + const errorMessage = Array.from(missingTranslationsMap.entries()) + .map(([key, langs]) => `\n- "${key}" is missing translations for: ${langs.join(', ')}`) + .join(''); + throw new Error(`Missing translations:${errorMessage}`); + } + }); + + test("translation file should not have duplicate keys", () => { + const duplicates = findDuplicateKeys(translations); + + if (duplicates.length > 0) { + throw new Error(`Found duplicate translation keys: ${duplicates.join(', ')}`); + } + }); +}); diff --git a/frontend/__tests__/components/modals/base-modal/base-modal.test.tsx b/frontend/__tests__/components/modals/base-modal/base-modal.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c3e5fdfa87c57c62f9d5e1c3ab2b48dfa47cf483 --- /dev/null +++ b/frontend/__tests__/components/modals/base-modal/base-modal.test.tsx @@ -0,0 +1,151 @@ +import { render, screen, act } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, it, vi, expect } from "vitest"; +import { BaseModal } from "#/components/shared/modals/base-modal/base-modal"; + +describe("BaseModal", () => { + const onOpenChangeMock = vi.fn(); + + it("should render if the modal is open", () => { + const { rerender } = render( + , + ); + expect(screen.queryByText("Settings")).not.toBeInTheDocument(); + + rerender( + , + ); + expect(screen.getByText("Settings")).toBeInTheDocument(); + }); + + it("should render an optional subtitle", () => { + render( + , + ); + expect(screen.getByText("Subtitle")).toBeInTheDocument(); + }); + + it("should render actions", async () => { + const onPrimaryClickMock = vi.fn(); + const onSecondaryClickMock = vi.fn(); + + const primaryAction = { + action: onPrimaryClickMock, + label: "Save", + }; + + const secondaryAction = { + action: onSecondaryClickMock, + label: "Cancel", + }; + + render( + , + ); + + expect(screen.getByText("Save")).toBeInTheDocument(); + expect(screen.getByText("Cancel")).toBeInTheDocument(); + + await userEvent.click(screen.getByText("Save")); + expect(onPrimaryClickMock).toHaveBeenCalledTimes(1); + + await userEvent.click(screen.getByText("Cancel")); + expect(onSecondaryClickMock).toHaveBeenCalledTimes(1); + }); + + it("should close the modal after an action is performed", async () => { + render( + {}, + closeAfterAction: true, + }, + ]} + />, + ); + + await userEvent.click(screen.getByText("Save")); + expect(onOpenChangeMock).toHaveBeenCalledTimes(1); + }); + + it("should render children", () => { + render( + +
Children
+
, + ); + expect(screen.getByText("Children")).toBeInTheDocument(); + }); + + it("should disable the action given the condition", () => { + const { rerender } = render( + {}, + isDisabled: true, + }, + ]} + />, + ); + + expect(screen.getByText("Save")).toBeDisabled(); + + rerender( + {}, + isDisabled: false, + }, + ]} + />, + ); + + expect(screen.getByText("Save")).not.toBeDisabled(); + }); + + it.skip("should not close if the backdrop or escape key is pressed", () => { + render( + , + ); + + act(() => { + userEvent.keyboard("{esc}"); + }); + // fails because the nextui component wraps the modal content in an aria-hidden div + expect(screen.getByRole("dialog")).toBeVisible(); + }); +}); diff --git a/frontend/__tests__/components/modals/settings/model-selector.test.tsx b/frontend/__tests__/components/modals/settings/model-selector.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cacc6fad1053676d8fd186432b12e152e99c8809 --- /dev/null +++ b/frontend/__tests__/components/modals/settings/model-selector.test.tsx @@ -0,0 +1,136 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { ModelSelector } from "#/components/shared/modals/settings/model-selector"; + +// Mock react-i18next +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => { + const translations: { [key: string]: string } = { + LLM$PROVIDER: "LLM Provider", + LLM$MODEL: "LLM Model", + LLM$SELECT_PROVIDER_PLACEHOLDER: "Select a provider", + LLM$SELECT_MODEL_PLACEHOLDER: "Select a model", + }; + return translations[key] || key; + }, + }), +})); + +describe("ModelSelector", () => { + const models = { + openai: { + separator: "/", + models: ["gpt-4o", "gpt-4o-mini"], + }, + azure: { + separator: "/", + models: ["ada", "gpt-35-turbo"], + }, + vertex_ai: { + separator: "/", + models: ["chat-bison", "chat-bison-32k"], + }, + cohere: { + separator: ".", + models: ["command-r-v1:0"], + }, + }; + + it("should display the provider selector", async () => { + const user = userEvent.setup(); + render(); + + const selector = screen.getByLabelText("LLM Provider"); + expect(selector).toBeInTheDocument(); + + await user.click(selector); + + expect(screen.getByText("OpenAI")).toBeInTheDocument(); + expect(screen.getByText("Azure")).toBeInTheDocument(); + expect(screen.getByText("VertexAI")).toBeInTheDocument(); + expect(screen.getByText("cohere")).toBeInTheDocument(); + }); + + it("should disable the model selector if the provider is not selected", async () => { + const user = userEvent.setup(); + render(); + + const modelSelector = screen.getByLabelText("LLM Model"); + expect(modelSelector).toBeDisabled(); + + const providerSelector = screen.getByLabelText("LLM Provider"); + await user.click(providerSelector); + + const vertexAI = screen.getByText("VertexAI"); + await user.click(vertexAI); + + expect(modelSelector).not.toBeDisabled(); + }); + + it("should display the model selector", async () => { + const user = userEvent.setup(); + render(); + + const providerSelector = screen.getByLabelText("LLM Provider"); + await user.click(providerSelector); + + const azureProvider = screen.getByText("Azure"); + await user.click(azureProvider); + + const modelSelector = screen.getByLabelText("LLM Model"); + await user.click(modelSelector); + + expect(screen.getByText("ada")).toBeInTheDocument(); + expect(screen.getByText("gpt-35-turbo")).toBeInTheDocument(); + + await user.click(providerSelector); + const vertexProvider = screen.getByText("VertexAI"); + await user.click(vertexProvider); + + await user.click(modelSelector); + + // Test fails when expecting these values to be present. + // My hypothesis is that it has something to do with NextUI's + // list virtualization + + // expect(screen.getByText("chat-bison")).toBeInTheDocument(); + // expect(screen.getByText("chat-bison-32k")).toBeInTheDocument(); + }); + + it("should call onModelChange when the model is changed", async () => { + const user = userEvent.setup(); + render(); + + const providerSelector = screen.getByLabelText("LLM Provider"); + const modelSelector = screen.getByLabelText("LLM Model"); + + await user.click(providerSelector); + await user.click(screen.getByText("Azure")); + + await user.click(modelSelector); + await user.click(screen.getByText("ada")); + + await user.click(modelSelector); + await user.click(screen.getByText("gpt-35-turbo")); + + await user.click(providerSelector); + await user.click(screen.getByText("cohere")); + + await user.click(modelSelector); + + // Test fails when expecting this values to be present. + // My hypothesis is that it has something to do with NextUI's + // list virtualization + + // await user.click(screen.getByText("command-r-v1:0")); + }); + + it("should have a default value if passed", async () => { + render(); + + expect(screen.getByLabelText("LLM Provider")).toHaveValue("Azure"); + expect(screen.getByLabelText("LLM Model")).toHaveValue("ada"); + }); +}); diff --git a/frontend/__tests__/components/settings/settings-input.test.tsx b/frontend/__tests__/components/settings/settings-input.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c2c6f650679ddc063d024105bf8a0a8c7d2d6b07 --- /dev/null +++ b/frontend/__tests__/components/settings/settings-input.test.tsx @@ -0,0 +1,109 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import userEvent from "@testing-library/user-event"; +import { SettingsInput } from "#/components/features/settings/settings-input"; + +describe("SettingsInput", () => { + it("should render an optional tag if showOptionalTag is true", async () => { + const { rerender } = render( + , + ); + + expect(screen.queryByText(/optional/i)).not.toBeInTheDocument(); + + rerender( + , + ); + + expect(screen.getByText(/optional/i)).toBeInTheDocument(); + }); + + it("should disable the input if isDisabled is true", async () => { + const { rerender } = render( + , + ); + + expect(screen.getByTestId("test-input")).toBeEnabled(); + + rerender( + , + ); + + expect(screen.getByTestId("test-input")).toBeDisabled(); + }); + + it("should set a placeholder on the input", async () => { + render( + , + ); + + expect(screen.getByTestId("test-input")).toHaveAttribute( + "placeholder", + "Test Placeholder", + ); + }); + + it("should set a default value on the input", async () => { + render( + , + ); + + expect(screen.getByTestId("test-input")).toHaveValue("Test Value"); + }); + + it("should render start content", async () => { + const startContent =
Start Content
; + + render( + , + ); + + expect(screen.getByText("Start Content")).toBeInTheDocument(); + }); + + it("should call onChange with the input value", async () => { + const onChangeMock = vi.fn(); + const user = userEvent.setup(); + + render( + , + ); + + const input = screen.getByTestId("test-input"); + await user.type(input, "Test"); + + expect(onChangeMock).toHaveBeenCalledTimes(4); + expect(onChangeMock).toHaveBeenNthCalledWith(4, "Test"); + }); +}); diff --git a/frontend/__tests__/components/settings/settings-switch.test.tsx b/frontend/__tests__/components/settings/settings-switch.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..054bbc932823d2b65a0c48eb5f4b833e0987e80f --- /dev/null +++ b/frontend/__tests__/components/settings/settings-switch.test.tsx @@ -0,0 +1,64 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import { SettingsSwitch } from "#/components/features/settings/settings-switch"; + +describe("SettingsSwitch", () => { + it("should call the onChange handler when the input is clicked", async () => { + const user = userEvent.setup(); + const onToggleMock = vi.fn(); + render( + + Test Switch + , + ); + + const switchInput = screen.getByTestId("test-switch"); + + await user.click(switchInput); + expect(onToggleMock).toHaveBeenCalledWith(true); + + await user.click(switchInput); + expect(onToggleMock).toHaveBeenCalledWith(false); + }); + + it("should render a beta tag if isBeta is true", () => { + const { rerender } = render( + + Test Switch + , + ); + + expect(screen.queryByText(/beta/i)).not.toBeInTheDocument(); + + rerender( + + Test Switch + , + ); + + expect(screen.getByText(/beta/i)).toBeInTheDocument(); + }); + + it("should be able to set a default toggle state", async () => { + const user = userEvent.setup(); + const onToggleMock = vi.fn(); + render( + + Test Switch + , + ); + + expect(screen.getByTestId("test-switch")).toBeChecked(); + + const switchInput = screen.getByTestId("test-switch"); + await user.click(switchInput); + expect(onToggleMock).toHaveBeenCalledWith(false); + + expect(screen.getByTestId("test-switch")).not.toBeChecked(); + }); +}); diff --git a/frontend/__tests__/components/shared/brand-button.test.tsx b/frontend/__tests__/components/shared/brand-button.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ee8eb5d456a5d2106985ebea8240657d968475d5 --- /dev/null +++ b/frontend/__tests__/components/shared/brand-button.test.tsx @@ -0,0 +1,55 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import { BrandButton } from "#/components/features/settings/brand-button"; + +describe("BrandButton", () => { + const onClickMock = vi.fn(); + + it("should set a test id", () => { + render( + + Test Button + , + ); + + expect(screen.getByTestId("brand-button")).toBeInTheDocument(); + }); + + it("should call onClick when clicked", async () => { + const user = userEvent.setup(); + render( + + Test Button + , + ); + + await user.click(screen.getByText("Test Button")); + }); + + it("should be disabled if isDisabled is true", () => { + render( + + Test Button + , + ); + + expect(screen.getByText("Test Button")).toBeDisabled(); + }); + + it("should pass a start content", () => { + render( + Start Content
+ } + > + Test Button + , + ); + + screen.getByTestId("custom-start-content"); + }); +}); diff --git a/frontend/__tests__/components/shared/modals/settings/settings-form.test.tsx b/frontend/__tests__/components/shared/modals/settings/settings-form.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d1d623f137f50efc40f1e430dd5219087e176859 --- /dev/null +++ b/frontend/__tests__/components/shared/modals/settings/settings-form.test.tsx @@ -0,0 +1,40 @@ +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import { renderWithProviders } from "test-utils"; +import { createRoutesStub } from "react-router"; +import { screen } from "@testing-library/react"; +import OpenHands from "#/api/open-hands"; +import { SettingsForm } from "#/components/shared/modals/settings/settings-form"; +import { DEFAULT_SETTINGS } from "#/services/settings"; + +describe("SettingsForm", () => { + const onCloseMock = vi.fn(); + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + + const RouteStub = createRoutesStub([ + { + Component: () => ( + + ), + path: "/", + }, + ]); + + it("should save the user settings and close the modal when the form is submitted", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.click(saveButton); + + expect(saveSettingsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + llm_model: DEFAULT_SETTINGS.LLM_MODEL, + }), + ); + }); +}); diff --git a/frontend/__tests__/components/suggestion-item.test.tsx b/frontend/__tests__/components/suggestion-item.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f44c6e101c2909475b53e6664414fbd8a810b6f5 --- /dev/null +++ b/frontend/__tests__/components/suggestion-item.test.tsx @@ -0,0 +1,58 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { SuggestionItem } from "#/components/features/suggestions/suggestion-item"; +import { I18nKey } from "#/i18n/declaration"; + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + SUGGESTIONS$TODO_APP: "ToDoリストアプリを開発する", + LANDING$BUILD_APP_BUTTON: "プルリクエストを表示するアプリを開発する", + SUGGESTIONS$HACKER_NEWS: + "Hacker Newsのトップ記事を表示するbashスクリプトを作成する", + }; + return translations[key] || key; + }, + }), +})); + +describe("SuggestionItem", () => { + const suggestionItem = { label: "suggestion1", value: "a long text value" }; + const onClick = vi.fn(); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render a suggestion", () => { + render(); + + expect(screen.getByTestId("suggestion")).toBeInTheDocument(); + expect(screen.getByText(/suggestion1/i)).toBeInTheDocument(); + }); + + it("should render a translated suggestion when using I18nKey", async () => { + const translatedSuggestion = { + label: I18nKey.SUGGESTIONS$TODO_APP, + value: "todo app value", + }; + + render( + , + ); + + expect(screen.getByText("ToDoリストアプリを開発する")).toBeInTheDocument(); + }); + + it("should call onClick when clicking a suggestion", async () => { + const user = userEvent.setup(); + render(); + + const suggestion = screen.getByTestId("suggestion"); + await user.click(suggestion); + + expect(onClick).toHaveBeenCalledWith("a long text value"); + }); +}); diff --git a/frontend/__tests__/components/suggestions.test.tsx b/frontend/__tests__/components/suggestions.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..33290819c3bc33057740534595f24b75bd8f9e11 --- /dev/null +++ b/frontend/__tests__/components/suggestions.test.tsx @@ -0,0 +1,60 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { Suggestions } from "#/components/features/suggestions/suggestions"; + +describe("Suggestions", () => { + const firstSuggestion = { + label: "first-suggestion", + value: "value-of-first-suggestion", + }; + const secondSuggestion = { + label: "second-suggestion", + value: "value-of-second-suggestion", + }; + const suggestions = [firstSuggestion, secondSuggestion]; + + const onSuggestionClickMock = vi.fn(); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render suggestions", () => { + render( + , + ); + + expect(screen.getByTestId("suggestions")).toBeInTheDocument(); + const suggestionElements = screen.getAllByTestId("suggestion"); + + expect(suggestionElements).toHaveLength(2); + expect(suggestionElements[0]).toHaveTextContent("first-suggestion"); + expect(suggestionElements[1]).toHaveTextContent("second-suggestion"); + }); + + it("should call onSuggestionClick when clicking a suggestion", async () => { + const user = userEvent.setup(); + render( + , + ); + + const suggestionElements = screen.getAllByTestId("suggestion"); + + await user.click(suggestionElements[0]); + expect(onSuggestionClickMock).toHaveBeenCalledWith( + "value-of-first-suggestion", + ); + + await user.click(suggestionElements[1]); + expect(onSuggestionClickMock).toHaveBeenCalledWith( + "value-of-second-suggestion", + ); + }); +}); diff --git a/frontend/__tests__/components/terminal/terminal.test.tsx b/frontend/__tests__/components/terminal/terminal.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5a075e7d9f1d472eadea6ce3fa6e48623549d91a --- /dev/null +++ b/frontend/__tests__/components/terminal/terminal.test.tsx @@ -0,0 +1,132 @@ +import { act, screen } from "@testing-library/react"; +import { renderWithProviders } from "test-utils"; +import { vi, describe, afterEach, it, expect } from "vitest"; +import { Command, appendInput, appendOutput } from "#/state/command-slice"; +import Terminal from "#/components/features/terminal/terminal"; + +const renderTerminal = (commands: Command[] = []) => + renderWithProviders(, { + preloadedState: { + cmd: { + commands, + }, + }, + }); + +describe.skip("Terminal", () => { + global.ResizeObserver = vi.fn().mockImplementation(() => ({ + observe: vi.fn(), + disconnect: vi.fn(), + })); + + const mockTerminal = { + open: vi.fn(), + write: vi.fn(), + writeln: vi.fn(), + dispose: vi.fn(), + onKey: vi.fn(), + attachCustomKeyEventHandler: vi.fn(), + loadAddon: vi.fn(), + }; + + vi.mock("@xterm/xterm", async (importOriginal) => ({ + ...(await importOriginal()), + Terminal: vi.fn().mockImplementation(() => mockTerminal), + })); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render a terminal", () => { + renderTerminal(); + + expect(screen.getByText("Terminal")).toBeInTheDocument(); + expect(mockTerminal.open).toHaveBeenCalledTimes(1); + + expect(mockTerminal.write).toHaveBeenCalledWith("$ "); + }); + + it("should load commands to the terminal", () => { + renderTerminal([ + { type: "input", content: "INPUT" }, + { type: "output", content: "OUTPUT" }, + ]); + + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(1, "INPUT"); + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(2, "OUTPUT"); + }); + + it("should write commands to the terminal", () => { + const { store } = renderTerminal(); + + act(() => { + store.dispatch(appendInput("echo Hello")); + store.dispatch(appendOutput("Hello")); + }); + + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(1, "echo Hello"); + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(2, "Hello"); + + act(() => { + store.dispatch(appendInput("echo World")); + }); + + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(3, "echo World"); + }); + + it("should load and write commands to the terminal", () => { + const { store } = renderTerminal([ + { type: "input", content: "echo Hello" }, + { type: "output", content: "Hello" }, + ]); + + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(1, "echo Hello"); + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(2, "Hello"); + + act(() => { + store.dispatch(appendInput("echo Hello")); + }); + + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(3, "echo Hello"); + }); + + it("should end the line with a dollar sign after writing a command", () => { + const { store } = renderTerminal(); + + act(() => { + store.dispatch(appendInput("echo Hello")); + }); + + expect(mockTerminal.writeln).toHaveBeenCalledWith("echo Hello"); + expect(mockTerminal.write).toHaveBeenCalledWith("$ "); + }); + + it("should display a custom symbol if output contains a custom symbol", () => { + renderTerminal([ + { type: "input", content: "echo Hello" }, + { + type: "output", + content: + "Hello\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.12/bin/python]\nopenhands@659478cb008c:/workspace $ ", + }, + ]); + + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(1, "echo Hello"); + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(2, "Hello"); + expect(mockTerminal.write).toHaveBeenCalledWith( + "\nopenhands@659478cb008c:/workspace $ ", + ); + }); + + // This test fails because it expects `disposeMock` to have been called before the component is unmounted. + it.skip("should dispose the terminal on unmount", () => { + const { unmount } = renderWithProviders(); + + expect(mockTerminal.dispose).not.toHaveBeenCalled(); + + unmount(); + + expect(mockTerminal.dispose).toHaveBeenCalledTimes(1); + }); +}); diff --git a/frontend/__tests__/components/upload-image-input.test.tsx b/frontend/__tests__/components/upload-image-input.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e95b80c9697bb391439a0224371eabfc7d14bf09 --- /dev/null +++ b/frontend/__tests__/components/upload-image-input.test.tsx @@ -0,0 +1,71 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { UploadImageInput } from "#/components/features/images/upload-image-input"; + +describe("UploadImageInput", () => { + const user = userEvent.setup(); + const onUploadMock = vi.fn(); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render an input", () => { + render(); + expect(screen.getByTestId("upload-image-input")).toBeInTheDocument(); + }); + + it("should call onUpload when a file is selected", async () => { + render(); + + const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }); + const input = screen.getByTestId("upload-image-input"); + + await user.upload(input, file); + + expect(onUploadMock).toHaveBeenNthCalledWith(1, [file]); + }); + + it("should call onUpload when multiple files are selected", async () => { + render(); + + const files = [ + new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }), + new File(["(⌐□_□)"], "chucknorris2.png", { type: "image/png" }), + ]; + const input = screen.getByTestId("upload-image-input"); + + await user.upload(input, files); + + expect(onUploadMock).toHaveBeenNthCalledWith(1, files); + }); + + it("should not upload any file that is not an image", async () => { + render(); + + const file = new File(["(⌐□_□)"], "chucknorris.txt", { + type: "text/plain", + }); + const input = screen.getByTestId("upload-image-input"); + + await user.upload(input, file); + + expect(onUploadMock).not.toHaveBeenCalled(); + }); + + it("should render custom labels", () => { + const { rerender } = render(); + expect(screen.getByTestId("default-label")).toBeInTheDocument(); + + function CustomLabel() { + return Custom label; + } + rerender( + } />, + ); + + expect(screen.getByText("Custom label")).toBeInTheDocument(); + expect(screen.queryByTestId("default-label")).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/components/user-actions.test.tsx b/frontend/__tests__/components/user-actions.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3d37f42acb2cbab3f036af4b40bd50def2f49ea1 --- /dev/null +++ b/frontend/__tests__/components/user-actions.test.tsx @@ -0,0 +1,71 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, test, vi, afterEach } from "vitest"; +import userEvent from "@testing-library/user-event"; +import { UserActions } from "#/components/features/sidebar/user-actions"; + +describe("UserActions", () => { + const user = userEvent.setup(); + const onClickAccountSettingsMock = vi.fn(); + const onLogoutMock = vi.fn(); + + afterEach(() => { + onClickAccountSettingsMock.mockClear(); + onLogoutMock.mockClear(); + }); + + it("should render", () => { + render(); + + expect(screen.getByTestId("user-actions")).toBeInTheDocument(); + expect(screen.getByTestId("user-avatar")).toBeInTheDocument(); + }); + + it("should toggle the user menu when the user avatar is clicked", async () => { + render(); + + const userAvatar = screen.getByTestId("user-avatar"); + await user.click(userAvatar); + + expect( + screen.getByTestId("account-settings-context-menu"), + ).toBeInTheDocument(); + + await user.click(userAvatar); + + expect( + screen.queryByTestId("account-settings-context-menu"), + ).not.toBeInTheDocument(); + }); + + it("should call onLogout and close the menu when the logout option is clicked", async () => { + render( + , + ); + + const userAvatar = screen.getByTestId("user-avatar"); + await user.click(userAvatar); + + const logoutOption = screen.getByText("ACCOUNT_SETTINGS$LOGOUT"); + await user.click(logoutOption); + + expect(onLogoutMock).toHaveBeenCalledOnce(); + expect( + screen.queryByTestId("account-settings-context-menu"), + ).not.toBeInTheDocument(); + }); + + test("logout button is always enabled", async () => { + render(); + + const userAvatar = screen.getByTestId("user-avatar"); + await user.click(userAvatar); + + const logoutOption = screen.getByText("ACCOUNT_SETTINGS$LOGOUT"); + await user.click(logoutOption); + + expect(onLogoutMock).toHaveBeenCalledOnce(); + }); +}); diff --git a/frontend/__tests__/components/user-avatar.test.tsx b/frontend/__tests__/components/user-avatar.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5e46a6643e3a07299f25bb8e2310fc794a973960 --- /dev/null +++ b/frontend/__tests__/components/user-avatar.test.tsx @@ -0,0 +1,68 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { UserAvatar } from "#/components/features/sidebar/user-avatar"; + +describe("UserAvatar", () => { + const onClickMock = vi.fn(); + + afterEach(() => { + onClickMock.mockClear(); + }); + + it("(default) should render the placeholder avatar when the user is logged out", () => { + render(); + expect(screen.getByTestId("user-avatar")).toBeInTheDocument(); + expect( + screen.getByLabelText("USER$AVATAR_PLACEHOLDER"), + ).toBeInTheDocument(); + }); + + it("should call onClick when clicked", async () => { + const user = userEvent.setup(); + render(); + + const userAvatarContainer = screen.getByTestId("user-avatar"); + await user.click(userAvatarContainer); + + expect(onClickMock).toHaveBeenCalledOnce(); + }); + + it("should display the user's avatar when available", () => { + render( + , + ); + + expect(screen.getByAltText("AVATAR$ALT_TEXT")).toBeInTheDocument(); + expect( + screen.queryByLabelText("USER$AVATAR_PLACEHOLDER"), + ).not.toBeInTheDocument(); + }); + + it("should display a loading spinner instead of an avatar when isLoading is true", () => { + const { rerender } = render(); + expect(screen.queryByTestId("loading-spinner")).not.toBeInTheDocument(); + expect( + screen.getByLabelText("USER$AVATAR_PLACEHOLDER"), + ).toBeInTheDocument(); + + rerender(); + expect(screen.getByTestId("loading-spinner")).toBeInTheDocument(); + expect( + screen.queryByLabelText("USER$AVATAR_PLACEHOLDER"), + ).not.toBeInTheDocument(); + + rerender( + , + ); + expect(screen.getByTestId("loading-spinner")).toBeInTheDocument(); + expect(screen.queryByAltText("AVATAR$ALT_TEXT")).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/context/ws-client-provider.test.tsx b/frontend/__tests__/context/ws-client-provider.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..00de1947ba03e42e72a99036e49f9fa1a76f3131 --- /dev/null +++ b/frontend/__tests__/context/ws-client-provider.test.tsx @@ -0,0 +1,98 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, waitFor } from "@testing-library/react"; +import React from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + updateStatusWhenErrorMessagePresent, + WsClientProvider, + useWsClient, +} from "#/context/ws-client-provider"; + +describe("Propagate error message", () => { + it("should do nothing when no message was passed from server", () => { + updateStatusWhenErrorMessagePresent(null); + updateStatusWhenErrorMessagePresent(undefined); + updateStatusWhenErrorMessagePresent({}); + updateStatusWhenErrorMessagePresent({ message: null }); + }); + + it.todo("should display error to user when present"); + + it.todo("should display error including translation id when present"); +}); + +// Create a mock for socket.io-client +const mockEmit = vi.fn(); +const mockOn = vi.fn(); +const mockOff = vi.fn(); +const mockDisconnect = vi.fn(); + +vi.mock("socket.io-client", () => ({ + io: vi.fn(() => ({ + emit: mockEmit, + on: mockOn, + off: mockOff, + disconnect: mockDisconnect, + io: { + opts: { + query: {}, + }, + }, + })), +})); + +// Mock component to test the hook +function TestComponent() { + const { send } = useWsClient(); + + React.useEffect(() => { + // Send a test event + send({ type: "test_event" }); + }, [send]); + + return
Test Component
; +} + +describe("WsClientProvider", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mock("#/hooks/query/use-active-conversation", () => ({ + useActiveConversation: () => { + return { data: { + conversation_id: "1", + title: "Conversation 1", + selected_repository: null, + last_updated_at: "2021-10-01T12:00:00Z", + created_at: "2021-10-01T12:00:00Z", + status: "RUNNING" as const, + url: null, + session_api_key: null, + }}}, + })); + }); + + it("should emit oh_user_action event when send is called", async () => { + const { getByText } = render(, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }); + + // Assert + expect(getByText("Test Component")).toBeInTheDocument(); + + // Wait for the emit call to happen (useEffect needs time to run) + await waitFor( + () => { + expect(mockEmit).toHaveBeenCalledWith("oh_user_action", { + type: "test_event", + }); + }, + { timeout: 1000 }, + ); + }); +}); diff --git a/frontend/__tests__/hooks/mutation/use-save-settings.test.tsx b/frontend/__tests__/hooks/mutation/use-save-settings.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..37c8695096fc1b4d94cc246c9dc1b7ecc48d9f1c --- /dev/null +++ b/frontend/__tests__/hooks/mutation/use-save-settings.test.tsx @@ -0,0 +1,36 @@ +import { renderHook, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import OpenHands from "#/api/open-hands"; +import { useSaveSettings } from "#/hooks/mutation/use-save-settings"; + +describe("useSaveSettings", () => { + it("should send an empty string for llm_api_key if an empty string is passed, otherwise undefined", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + const { result } = renderHook(() => useSaveSettings(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + result.current.mutate({ llm_api_key: "" }); + await waitFor(() => { + expect(saveSettingsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + llm_api_key: "", + }), + ); + }); + + result.current.mutate({ llm_api_key: null }); + await waitFor(() => { + expect(saveSettingsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + llm_api_key: undefined, + }), + ); + }); + }); +}); diff --git a/frontend/__tests__/hooks/use-click-outside-element.test.tsx b/frontend/__tests__/hooks/use-click-outside-element.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3f9c77c0ce832889a0aaef7e472828e857f04bb5 --- /dev/null +++ b/frontend/__tests__/hooks/use-click-outside-element.test.tsx @@ -0,0 +1,36 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { expect, test, vi } from "vitest"; +import { useClickOutsideElement } from "#/hooks/use-click-outside-element"; + +interface ClickOutsideTestComponentProps { + callback: () => void; +} + +function ClickOutsideTestComponent({ + callback, +}: ClickOutsideTestComponentProps) { + const ref = useClickOutsideElement(callback); + + return ( +
+
+
+
+ ); +} + +test("call the callback when the element is clicked outside", async () => { + const user = userEvent.setup(); + const callback = vi.fn(); + render(); + + const insideElement = screen.getByTestId("inside-element"); + const outsideElement = screen.getByTestId("outside-element"); + + await user.click(insideElement); + expect(callback).not.toHaveBeenCalled(); + + await user.click(outsideElement); + expect(callback).toHaveBeenCalled(); +}); diff --git a/frontend/__tests__/hooks/use-rate.test.ts b/frontend/__tests__/hooks/use-rate.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..cade3e65545bb1393e5dbfb3ba0531c7d6c2a8c1 --- /dev/null +++ b/frontend/__tests__/hooks/use-rate.test.ts @@ -0,0 +1,93 @@ +import { act, renderHook } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { useRate } from "#/hooks/use-rate"; + +describe("useRate", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should initialize", () => { + const { result } = renderHook(() => useRate()); + + expect(result.current.items).toHaveLength(0); + expect(result.current.rate).toBeNull(); + expect(result.current.lastUpdated).toBeNull(); + expect(result.current.isUnderThreshold).toBe(true); + }); + + it("should handle the case of a single element", () => { + const { result } = renderHook(() => useRate()); + + act(() => { + result.current.record(123); + }); + + expect(result.current.items).toHaveLength(1); + expect(result.current.lastUpdated).not.toBeNull(); + }); + + it("should return the difference between the last two elements", () => { + const { result } = renderHook(() => useRate()); + + vi.setSystemTime(500); + act(() => { + result.current.record(4); + }); + + vi.advanceTimersByTime(500); + act(() => { + result.current.record(9); + }); + + expect(result.current.items).toHaveLength(2); + expect(result.current.rate).toBe(5); + expect(result.current.lastUpdated).toBe(1000); + }); + + it("should update isUnderThreshold after [threshold]ms of no activity", () => { + const { result } = renderHook(() => useRate({ threshold: 500 })); + + expect(result.current.isUnderThreshold).toBe(true); + + act(() => { + // not sure if fake timers is buggy with intervals, + // but I need to call it twice to register + vi.advanceTimersToNextTimer(); + vi.advanceTimersToNextTimer(); + }); + + expect(result.current.isUnderThreshold).toBe(false); + }); + + it("should return an isUnderThreshold boolean", () => { + const { result } = renderHook(() => useRate({ threshold: 500 })); + + vi.setSystemTime(500); + act(() => { + result.current.record(400); + }); + act(() => { + result.current.record(1000); + }); + + expect(result.current.isUnderThreshold).toBe(false); + + act(() => { + result.current.record(1500); + }); + + expect(result.current.isUnderThreshold).toBe(true); + + act(() => { + vi.advanceTimersToNextTimer(); + vi.advanceTimersToNextTimer(); + }); + + expect(result.current.isUnderThreshold).toBe(false); + }); +}); diff --git a/frontend/__tests__/hooks/use-terminal.test.tsx b/frontend/__tests__/hooks/use-terminal.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..10f93a86ef19489cbc9bee024a5196c21b2df523 --- /dev/null +++ b/frontend/__tests__/hooks/use-terminal.test.tsx @@ -0,0 +1,111 @@ +import { beforeAll, describe, expect, it, vi } from "vitest"; +import { afterEach } from "node:test"; +import { useTerminal } from "#/hooks/use-terminal"; +import { Command } from "#/state/command-slice"; +import { AgentState } from "#/types/agent-state"; +import { renderWithProviders } from "../../test-utils"; + +// Mock the WsClient context +vi.mock("#/context/ws-client-provider", () => ({ + useWsClient: () => ({ + send: vi.fn(), + status: "CONNECTED", + isLoadingMessages: false, + events: [], + }), +})); + +interface TestTerminalComponentProps { + commands: Command[]; +} + +function TestTerminalComponent({ + commands, +}: TestTerminalComponentProps) { + const ref = useTerminal({ commands }); + return
; +} + +describe("useTerminal", () => { + const mockTerminal = vi.hoisted(() => ({ + loadAddon: vi.fn(), + open: vi.fn(), + write: vi.fn(), + writeln: vi.fn(), + onKey: vi.fn(), + attachCustomKeyEventHandler: vi.fn(), + dispose: vi.fn(), + })); + + beforeAll(() => { + // mock ResizeObserver + window.ResizeObserver = vi.fn().mockImplementation(() => ({ + observe: vi.fn(), + unobserve: vi.fn(), + disconnect: vi.fn(), + })); + + // mock Terminal + vi.mock("@xterm/xterm", async (importOriginal) => ({ + ...(await importOriginal()), + Terminal: vi.fn().mockImplementation(() => mockTerminal), + })); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should render", () => { + renderWithProviders(, { + preloadedState: { + agent: { curAgentState: AgentState.RUNNING }, + cmd: { commands: [] }, + }, + }); + }); + + it("should render the commands in the terminal", () => { + const commands: Command[] = [ + { content: "echo hello", type: "input" }, + { content: "hello", type: "output" }, + ]; + + renderWithProviders(, { + preloadedState: { + agent: { curAgentState: AgentState.RUNNING }, + cmd: { commands }, + }, + }); + + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(1, "echo hello"); + expect(mockTerminal.writeln).toHaveBeenNthCalledWith(2, "hello"); + }); + + // This test is no longer relevant as secrets filtering has been removed + it.skip("should hide secrets in the terminal", () => { + const secret = "super_secret_github_token"; + const anotherSecret = "super_secret_another_token"; + const commands: Command[] = [ + { + content: `export GITHUB_TOKEN=${secret},${anotherSecret},${secret}`, + type: "input", + }, + { content: secret, type: "output" }, + ]; + + renderWithProviders( + , + { + preloadedState: { + agent: { curAgentState: AgentState.RUNNING }, + cmd: { commands }, + }, + }, + ); + + // This test is no longer relevant as secrets filtering has been removed + }); +}); diff --git a/frontend/__tests__/i18n/duplicate-keys.test.ts b/frontend/__tests__/i18n/duplicate-keys.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..35ab9b89c9711305bb1e82365b7675b909d330db --- /dev/null +++ b/frontend/__tests__/i18n/duplicate-keys.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from 'vitest'; +import fs from 'fs'; +import path from 'path'; + +describe('translation.json', () => { + it('should not have duplicate translation keys', () => { + // Read the translation.json file + const translationPath = path.join(__dirname, '../../src/i18n/translation.json'); + const translationContent = fs.readFileSync(translationPath, 'utf-8'); + + // First, let's check for exact string matches of key definitions + const keyRegex = /"([^"]+)": {/g; + const matches = translationContent.matchAll(keyRegex); + const keyOccurrences = new Map(); + const duplicateKeys: string[] = []; + + for (const match of matches) { + const key = match[1]; + const count = (keyOccurrences.get(key) || 0) + 1; + keyOccurrences.set(key, count); + if (count > 1) { + duplicateKeys.push(key); + } + } + + // Remove duplicates from duplicateKeys array + const uniqueDuplicates = [...new Set(duplicateKeys)]; + + // If there are duplicates, create a helpful error message + if (uniqueDuplicates.length > 0) { + const errorMessage = `Found duplicate translation keys:\n${uniqueDuplicates + .map((key) => ` - "${key}" appears ${keyOccurrences.get(key)} times`) + .join('\n')}`; + throw new Error(errorMessage); + } + + // Expect no duplicates (this will pass if we reach here) + expect(uniqueDuplicates).toHaveLength(0); + }); + + it('should have consistent translations for each key', () => { + // Read the translation.json file + const translationPath = path.join(__dirname, '../../src/i18n/translation.json'); + const translationContent = fs.readFileSync(translationPath, 'utf-8'); + const translations = JSON.parse(translationContent); + + // Create a map to store English translations for each key + const englishTranslations = new Map(); + const inconsistentKeys: string[] = []; + + // Check each key's English translation + Object.entries(translations).forEach(([key, value]: [string, any]) => { + if (typeof value === 'object' && value.en !== undefined) { + const currentEn = value.en.toLowerCase(); + const existingEn = englishTranslations.get(key)?.toLowerCase(); + + if (existingEn !== undefined && existingEn !== currentEn) { + inconsistentKeys.push(key); + } else { + englishTranslations.set(key, value.en); + } + } + }); + + // If there are inconsistencies, create a helpful error message + if (inconsistentKeys.length > 0) { + const errorMessage = `Found inconsistent translations for keys:\n${inconsistentKeys + .map((key) => ` - "${key}" has multiple different English translations`) + .join('\n')}`; + throw new Error(errorMessage); + } + + // Expect no inconsistencies + expect(inconsistentKeys).toHaveLength(0); + }); +}); diff --git a/frontend/__tests__/i18n/translations.test.tsx b/frontend/__tests__/i18n/translations.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a19d263f748d51641f00e94a020458725758f204 --- /dev/null +++ b/frontend/__tests__/i18n/translations.test.tsx @@ -0,0 +1,20 @@ +import { screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import i18n from "../../src/i18n"; +import { AccountSettingsContextMenu } from "../../src/components/features/context-menu/account-settings-context-menu"; +import { renderWithProviders } from "../../test-utils"; + +describe("Translations", () => { + it("should render translated text", () => { + i18n.changeLanguage("en"); + renderWithProviders( + {}} + onClose={() => {}} + />, + ); + expect( + screen.getByTestId("account-settings-context-menu"), + ).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/initial-query.test.tsx b/frontend/__tests__/initial-query.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..824921752973721ec9b439ceebcfa98e55a0123c --- /dev/null +++ b/frontend/__tests__/initial-query.test.tsx @@ -0,0 +1,20 @@ +import { describe, it, expect } from "vitest"; +import store from "../src/store"; +import { + setInitialPrompt, + clearInitialPrompt, +} from "../src/state/initial-query-slice"; + +describe("Initial Query Behavior", () => { + it("should clear initial query when clearInitialPrompt is dispatched", () => { + // Set up initial query in the store + store.dispatch(setInitialPrompt("test query")); + expect(store.getState().initialQuery.initialPrompt).toBe("test query"); + + // Clear the initial query + store.dispatch(clearInitialPrompt()); + + // Verify initial query is cleared + expect(store.getState().initialQuery.initialPrompt).toBeNull(); + }); +}); diff --git a/frontend/__tests__/routes/_oh.test.tsx b/frontend/__tests__/routes/_oh.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3bd4af610ec03bffd4f083001ab5f55f3cd59186 --- /dev/null +++ b/frontend/__tests__/routes/_oh.test.tsx @@ -0,0 +1,207 @@ +import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { createRoutesStub } from "react-router"; +import { screen, waitFor, within } from "@testing-library/react"; +import { + createAxiosNotFoundErrorObject, + renderWithProviders, +} from "test-utils"; +import userEvent from "@testing-library/user-event"; +import MainApp from "#/routes/root-layout"; +import i18n from "#/i18n"; +import * as CaptureConsent from "#/utils/handle-capture-consent"; +import OpenHands from "#/api/open-hands"; +import * as ToastHandlers from "#/utils/custom-toast-handlers"; + +describe("frontend/routes/_oh", () => { + const RouteStub = createRoutesStub([{ Component: MainApp, path: "/" }]); + + const { userIsAuthenticatedMock, settingsAreUpToDateMock } = vi.hoisted( + () => ({ + userIsAuthenticatedMock: vi.fn(), + settingsAreUpToDateMock: vi.fn(), + }), + ); + + beforeAll(() => { + vi.mock("#/utils/user-is-authenticated", () => ({ + userIsAuthenticated: userIsAuthenticatedMock.mockReturnValue(true), + })); + + vi.mock("#/services/settings", async (importOriginal) => ({ + ...(await importOriginal()), + settingsAreUpToDate: settingsAreUpToDateMock, + })); + }); + + afterEach(() => { + vi.clearAllMocks(); + localStorage.clear(); + }); + + it("should render", async () => { + renderWithProviders(); + await screen.findByTestId("root-layout"); + }); + + it.skip("should render the AI config modal if settings are not up-to-date", async () => { + settingsAreUpToDateMock.mockReturnValue(false); + renderWithProviders(); + + await screen.findByTestId("ai-config-modal"); + }); + + it("should not render the AI config modal if the settings are up-to-date", async () => { + settingsAreUpToDateMock.mockReturnValue(true); + renderWithProviders(); + + await waitFor(() => { + expect(screen.queryByTestId("ai-config-modal")).not.toBeInTheDocument(); + }); + }); + + // FIXME: This test fails when it shouldn't be, please investigate + it.skip("should render and capture the user's consent if oss mode", async () => { + const user = userEvent.setup(); + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + const handleCaptureConsentSpy = vi.spyOn( + CaptureConsent, + "handleCaptureConsent", + ); + + getConfigSpy.mockResolvedValue({ + APP_MODE: "oss", + GITHUB_CLIENT_ID: "test-id", + POSTHOG_CLIENT_KEY: "test-key", + FEATURE_FLAGS: { + ENABLE_BILLING: false, + HIDE_LLM_SETTINGS: false, + }, + }); + + // @ts-expect-error - We only care about the user_consents_to_analytics field + getSettingsSpy.mockResolvedValue({ + user_consents_to_analytics: null, + }); + + renderWithProviders(); + + // The user has not consented to tracking + const consentForm = await screen.findByTestId("user-capture-consent-form"); + expect(handleCaptureConsentSpy).not.toHaveBeenCalled(); + + const submitButton = within(consentForm).getByRole("button", { + name: /confirm preferences/i, + }); + await user.click(submitButton); + + // The user has now consented to tracking + expect(handleCaptureConsentSpy).toHaveBeenCalledWith(true); + expect( + screen.queryByTestId("user-capture-consent-form"), + ).not.toBeInTheDocument(); + }); + + it("should not render the user consent form if saas mode", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + GITHUB_CLIENT_ID: "test-id", + POSTHOG_CLIENT_KEY: "test-key", + FEATURE_FLAGS: { + ENABLE_BILLING: false, + HIDE_LLM_SETTINGS: false, + }, + }); + + renderWithProviders(); + + await waitFor(() => { + expect( + screen.queryByTestId("user-capture-consent-form"), + ).not.toBeInTheDocument(); + }); + }); + + // TODO: Likely failing due to how tokens are now handled in context. Move to e2e tests + it.skip("should render a new project button if a token is set", async () => { + localStorage.setItem("token", "test-token"); + const { rerender } = renderWithProviders(); + + await screen.findByTestId("new-project-button"); + + localStorage.removeItem("token"); + rerender(); + + await waitFor(() => { + expect( + screen.queryByTestId("new-project-button"), + ).not.toBeInTheDocument(); + }); + }); + + // TODO: Move to e2e tests + it.skip("should update the i18n language when the language settings change", async () => { + const changeLanguageSpy = vi.spyOn(i18n, "changeLanguage"); + const { rerender } = renderWithProviders(); + + // The default language is English + expect(changeLanguageSpy).toHaveBeenCalledWith("en"); + + localStorage.setItem("LANGUAGE", "es"); + + rerender(); + expect(changeLanguageSpy).toHaveBeenCalledWith("es"); + + rerender(); + // The language has not changed, so the spy should not have been called again + expect(changeLanguageSpy).toHaveBeenCalledTimes(2); + }); + + // FIXME: logoutCleanup has been replaced with a hook + it.skip("should call logoutCleanup after a logout", async () => { + const user = userEvent.setup(); + localStorage.setItem("ghToken", "test-token"); + + // const logoutCleanupSpy = vi.spyOn(LogoutCleanup, "logoutCleanup"); + renderWithProviders(); + + const userActions = await screen.findByTestId("user-actions"); + const userAvatar = within(userActions).getByTestId("user-avatar"); + await user.click(userAvatar); + + const logout = within(userActions).getByRole("button", { name: /logout/i }); + await user.click(logout); + + // expect(logoutCleanupSpy).toHaveBeenCalled(); + expect(localStorage.getItem("ghToken")).toBeNull(); + }); + + it("should render a you're in toast if it is a new user and in saas mode", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + const displaySuccessToastSpy = vi.spyOn( + ToastHandlers, + "displaySuccessToast", + ); + + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + GITHUB_CLIENT_ID: "test-id", + POSTHOG_CLIENT_KEY: "test-key", + FEATURE_FLAGS: { + ENABLE_BILLING: false, + HIDE_LLM_SETTINGS: false, + }, + }); + + getSettingsSpy.mockRejectedValue(createAxiosNotFoundErrorObject()); + + renderWithProviders(); + + await waitFor(() => { + expect(displaySuccessToastSpy).toHaveBeenCalledWith("BILLING$YOURE_IN"); + expect(displaySuccessToastSpy).toHaveBeenCalledOnce(); + }); + }); +}); diff --git a/frontend/__tests__/routes/accept-tos.test.tsx b/frontend/__tests__/routes/accept-tos.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ce6f36793bc83146f24a05b98fca56cfca16e3fe --- /dev/null +++ b/frontend/__tests__/routes/accept-tos.test.tsx @@ -0,0 +1,136 @@ +import { render, screen } from "@testing-library/react"; +import { it, describe, expect, vi, beforeEach, afterEach } from "vitest"; +import userEvent from "@testing-library/user-event"; +import AcceptTOS from "#/routes/accept-tos"; +import * as CaptureConsent from "#/utils/handle-capture-consent"; +import * as ToastHandlers from "#/utils/custom-toast-handlers"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { openHands } from "#/api/open-hands-axios"; + +// Mock the react-router hooks +vi.mock("react-router", () => ({ + useNavigate: () => vi.fn(), + useSearchParams: () => [ + { + get: (param: string) => { + if (param === "redirect_url") { + return "/dashboard"; + } + return null; + }, + }, + ], +})); + +// Mock the axios instance +vi.mock("#/api/open-hands-axios", () => ({ + openHands: { + post: vi.fn(), + }, +})); + +// Mock the toast handlers +vi.mock("#/utils/custom-toast-handlers", () => ({ + displayErrorToast: vi.fn(), +})); + +// Create a wrapper with QueryClientProvider +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + + return ({ children }: { children: React.ReactNode }) => ( + {children} + ); +}; + +describe("AcceptTOS", () => { + beforeEach(() => { + vi.stubGlobal("location", { href: "" }); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.resetAllMocks(); + }); + + it("should render a TOS checkbox that is unchecked by default", () => { + render(, { wrapper: createWrapper() }); + + const checkbox = screen.getByRole("checkbox"); + const continueButton = screen.getByRole("button", { name: "TOS$CONTINUE" }); + + expect(checkbox).not.toBeChecked(); + expect(continueButton).toBeDisabled(); + }); + + it("should enable the continue button when the TOS checkbox is checked", async () => { + const user = userEvent.setup(); + render(, { wrapper: createWrapper() }); + + const checkbox = screen.getByRole("checkbox"); + const continueButton = screen.getByRole("button", { name: "TOS$CONTINUE" }); + + expect(continueButton).toBeDisabled(); + + await user.click(checkbox); + + expect(continueButton).not.toBeDisabled(); + }); + + it("should set user analytics consent to true when the user accepts TOS", async () => { + const handleCaptureConsentSpy = vi.spyOn( + CaptureConsent, + "handleCaptureConsent", + ); + + // Mock the API response + vi.mocked(openHands.post).mockResolvedValue({ + data: { redirect_url: "/dashboard" }, + }); + + const user = userEvent.setup(); + render(, { wrapper: createWrapper() }); + + const checkbox = screen.getByRole("checkbox"); + await user.click(checkbox); + + const continueButton = screen.getByRole("button", { name: "TOS$CONTINUE" }); + await user.click(continueButton); + + // Wait for the mutation to complete + await new Promise(process.nextTick); + + expect(handleCaptureConsentSpy).toHaveBeenCalledWith(true); + expect(openHands.post).toHaveBeenCalledWith("/api/accept_tos", { + redirect_url: "/dashboard", + }); + }); + + it("should handle external redirect URLs", async () => { + // Mock the API response with an external URL + const externalUrl = "https://example.com/callback"; + vi.mocked(openHands.post).mockResolvedValue({ + data: { redirect_url: externalUrl }, + }); + + const user = userEvent.setup(); + render(, { wrapper: createWrapper() }); + + const checkbox = screen.getByRole("checkbox"); + await user.click(checkbox); + + const continueButton = screen.getByRole("button", { name: "TOS$CONTINUE" }); + await user.click(continueButton); + + // Wait for the mutation to complete + await new Promise(process.nextTick); + + expect(window.location.href).toBe(externalUrl); + }); +}); diff --git a/frontend/__tests__/routes/app-settings.test.tsx b/frontend/__tests__/routes/app-settings.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4927846c3c887406740f47dc044ee50fd74d4ff2 --- /dev/null +++ b/frontend/__tests__/routes/app-settings.test.tsx @@ -0,0 +1,290 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import userEvent from "@testing-library/user-event"; +import AppSettingsScreen from "#/routes/app-settings"; +import OpenHands from "#/api/open-hands"; +import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers"; +import { AvailableLanguages } from "#/i18n"; +import * as CaptureConsent from "#/utils/handle-capture-consent"; +import * as ToastHandlers from "#/utils/custom-toast-handlers"; + +const renderAppSettingsScreen = () => + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + +describe("Content", () => { + it("should render the screen", () => { + renderAppSettingsScreen(); + screen.getByTestId("app-settings-screen"); + }); + + it("should render the correct default values", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + language: "no", + user_consents_to_analytics: true, + enable_sound_notifications: true, + }); + + renderAppSettingsScreen(); + + await waitFor(() => { + const language = screen.getByTestId("language-input"); + const analytics = screen.getByTestId("enable-analytics-switch"); + const sound = screen.getByTestId("enable-sound-notifications-switch"); + + expect(language).toHaveValue("Norsk"); + expect(analytics).toBeChecked(); + expect(sound).toBeChecked(); + }); + }); + + it("should render the language options", async () => { + renderAppSettingsScreen(); + + const language = await screen.findByTestId("language-input"); + await userEvent.click(language); + + AvailableLanguages.forEach((lang) => { + const option = screen.getByText(lang.label); + expect(option).toBeInTheDocument(); + }); + }); +}); + +describe("Form submission", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should submit the form with the correct values", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS); + + renderAppSettingsScreen(); + + const language = await screen.findByTestId("language-input"); + const analytics = await screen.findByTestId("enable-analytics-switch"); + const sound = await screen.findByTestId( + "enable-sound-notifications-switch", + ); + + expect(language).toHaveValue("English"); + expect(analytics).not.toBeChecked(); + expect(sound).not.toBeChecked(); + + // change language + await userEvent.click(language); + const norsk = screen.getByText("Norsk"); + await userEvent.click(norsk); + expect(language).toHaveValue("Norsk"); + + // toggle options + await userEvent.click(analytics); + expect(analytics).toBeChecked(); + await userEvent.click(sound); + expect(sound).toBeChecked(); + + // submit the form + const submit = await screen.findByTestId("submit-button"); + await userEvent.click(submit); + expect(saveSettingsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + language: "no", + user_consents_to_analytics: true, + enable_sound_notifications: true, + }), + ); + }); + + it("should only enable the submit button when there are changes", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS); + + renderAppSettingsScreen(); + + const submit = await screen.findByTestId("submit-button"); + expect(submit).toBeDisabled(); + + // Language check + const language = await screen.findByTestId("language-input"); + await userEvent.click(language); + const norsk = screen.getByText("Norsk"); + await userEvent.click(norsk); + expect(submit).not.toBeDisabled(); + + await userEvent.click(language); + const english = screen.getByText("English"); + await userEvent.click(english); + expect(submit).toBeDisabled(); + + // Analytics check + const analytics = await screen.findByTestId("enable-analytics-switch"); + await userEvent.click(analytics); + expect(submit).not.toBeDisabled(); + + await userEvent.click(analytics); + expect(submit).toBeDisabled(); + + // Sound check + const sound = await screen.findByTestId( + "enable-sound-notifications-switch", + ); + await userEvent.click(sound); + expect(submit).not.toBeDisabled(); + + await userEvent.click(sound); + expect(submit).toBeDisabled(); + }); + + it("should call handleCaptureConsents with true when the analytics switch is toggled", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS); + + const handleCaptureConsentsSpy = vi.spyOn( + CaptureConsent, + "handleCaptureConsent", + ); + + renderAppSettingsScreen(); + + const analytics = await screen.findByTestId("enable-analytics-switch"); + const submit = await screen.findByTestId("submit-button"); + + await userEvent.click(analytics); + await userEvent.click(submit); + + await waitFor(() => + expect(handleCaptureConsentsSpy).toHaveBeenCalledWith(true), + ); + }); + + it("should call handleCaptureConsents with false when the analytics switch is toggled", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + user_consents_to_analytics: true, + }); + + const handleCaptureConsentsSpy = vi.spyOn( + CaptureConsent, + "handleCaptureConsent", + ); + + renderAppSettingsScreen(); + + const analytics = await screen.findByTestId("enable-analytics-switch"); + const submit = await screen.findByTestId("submit-button"); + + await userEvent.click(analytics); + await userEvent.click(submit); + + await waitFor(() => + expect(handleCaptureConsentsSpy).toHaveBeenCalledWith(false), + ); + }); + + // flaky test + it.skip("should disable the button when submitting changes", async () => { + renderAppSettingsScreen(); + + const submit = await screen.findByTestId("submit-button"); + expect(submit).toBeDisabled(); + + const sound = await screen.findByTestId( + "enable-sound-notifications-switch", + ); + await userEvent.click(sound); + expect(submit).not.toBeDisabled(); + + // submit the form + await userEvent.click(submit); + + expect(submit).toHaveTextContent("Saving..."); + expect(submit).toBeDisabled(); + + await waitFor(() => expect(submit).toHaveTextContent("Save")); + }); + + it("should disable the button after submitting changes", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS); + + renderAppSettingsScreen(); + + const submit = await screen.findByTestId("submit-button"); + expect(submit).toBeDisabled(); + + const sound = await screen.findByTestId( + "enable-sound-notifications-switch", + ); + await userEvent.click(sound); + expect(submit).not.toBeDisabled(); + + // submit the form + await userEvent.click(submit); + expect(saveSettingsSpy).toHaveBeenCalled(); + + await waitFor(() => expect(submit).toBeDisabled()); + }); +}); + +describe("Status toasts", () => { + it("should call displaySuccessToast when the settings are saved", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS); + + const displaySuccessToastSpy = vi.spyOn( + ToastHandlers, + "displaySuccessToast", + ); + + renderAppSettingsScreen(); + + // Toggle setting to change + const sound = await screen.findByTestId( + "enable-sound-notifications-switch", + ); + await userEvent.click(sound); + + const submit = await screen.findByTestId("submit-button"); + await userEvent.click(submit); + + expect(saveSettingsSpy).toHaveBeenCalled(); + await waitFor(() => expect(displaySuccessToastSpy).toHaveBeenCalled()); + }); + + it("should call displayErrorToast when the settings fail to save", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS); + + const displayErrorToastSpy = vi.spyOn(ToastHandlers, "displayErrorToast"); + + saveSettingsSpy.mockRejectedValue(new Error("Failed to save settings")); + + renderAppSettingsScreen(); + + // Toggle setting to change + const sound = await screen.findByTestId( + "enable-sound-notifications-switch", + ); + await userEvent.click(sound); + + const submit = await screen.findByTestId("submit-button"); + await userEvent.click(submit); + + expect(saveSettingsSpy).toHaveBeenCalled(); + expect(displayErrorToastSpy).toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/routes/git-settings.test.tsx b/frontend/__tests__/routes/git-settings.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0297a2d3c9ddb3a3762fac055d7ec62d9f0136cf --- /dev/null +++ b/frontend/__tests__/routes/git-settings.test.tsx @@ -0,0 +1,442 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { createRoutesStub } from "react-router"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import userEvent from "@testing-library/user-event"; +import GitSettingsScreen from "#/routes/git-settings"; +import OpenHands from "#/api/open-hands"; +import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers"; +import { GetConfigResponse } from "#/api/open-hands.types"; +import * as ToastHandlers from "#/utils/custom-toast-handlers"; +import { SecretsService } from "#/api/secrets-service"; + +const VALID_OSS_CONFIG: GetConfigResponse = { + APP_MODE: "oss", + GITHUB_CLIENT_ID: "123", + POSTHOG_CLIENT_KEY: "456", + FEATURE_FLAGS: { + ENABLE_BILLING: false, + HIDE_LLM_SETTINGS: false, + }, +}; + +const VALID_SAAS_CONFIG: GetConfigResponse = { + APP_MODE: "saas", + GITHUB_CLIENT_ID: "123", + POSTHOG_CLIENT_KEY: "456", + FEATURE_FLAGS: { + ENABLE_BILLING: false, + HIDE_LLM_SETTINGS: false, + }, +}; + +const queryClient = new QueryClient(); + +const GitSettingsRouterStub = createRoutesStub([ + { + Component: GitSettingsScreen, + path: "/settings/github", + }, +]); + +const renderGitSettingsScreen = () => { + const { rerender, ...rest } = render( + , + { + wrapper: ({ children }) => ( + + {children} + + ), + }, + ); + + const rerenderGitSettingsScreen = () => + rerender( + + + , + ); + + return { + ...rest, + rerender: rerenderGitSettingsScreen, + }; +}; + +beforeEach(() => { + // Since we don't recreate the query client on every test, we need to + // reset the query client before each test to avoid state leaks + // between tests. + queryClient.invalidateQueries(); +}); + +describe("Content", () => { + it("should render", async () => { + renderGitSettingsScreen(); + await screen.findByTestId("git-settings-screen"); + }); + + it("should render the inputs if OSS mode", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG); + + const { rerender } = renderGitSettingsScreen(); + + await screen.findByTestId("github-token-input"); + await screen.findByTestId("github-token-help-anchor"); + + await screen.findByTestId("gitlab-token-input"); + await screen.findByTestId("gitlab-token-help-anchor"); + + getConfigSpy.mockResolvedValue(VALID_SAAS_CONFIG); + queryClient.invalidateQueries(); + rerender(); + + await waitFor(() => { + expect( + screen.queryByTestId("github-token-input"), + ).not.toBeInTheDocument(); + expect( + screen.queryByTestId("github-token-help-anchor"), + ).not.toBeInTheDocument(); + + expect( + screen.queryByTestId("gitlab-token-input"), + ).not.toBeInTheDocument(); + expect( + screen.queryByTestId("gitlab-token-help-anchor"), + ).not.toBeInTheDocument(); + }); + }); + + it("should set '' placeholder and indicator if the GitHub token is set", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + + getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + }); + + const { rerender } = renderGitSettingsScreen(); + + await waitFor(() => { + const githubInput = screen.getByTestId("github-token-input"); + expect(githubInput).toHaveProperty("placeholder", ""); + expect( + screen.queryByTestId("gh-set-token-indicator"), + ).not.toBeInTheDocument(); + + const gitlabInput = screen.getByTestId("gitlab-token-input"); + expect(gitlabInput).toHaveProperty("placeholder", ""); + expect( + screen.queryByTestId("gl-set-token-indicator"), + ).not.toBeInTheDocument(); + }); + + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: { + github: null, + gitlab: null, + }, + }); + queryClient.invalidateQueries(); + + rerender(); + + await waitFor(() => { + const githubInput = screen.getByTestId("github-token-input"); + expect(githubInput).toHaveProperty("placeholder", ""); + expect( + screen.queryByTestId("gh-set-token-indicator"), + ).toBeInTheDocument(); + + const gitlabInput = screen.getByTestId("gitlab-token-input"); + expect(gitlabInput).toHaveProperty("placeholder", ""); + expect( + screen.queryByTestId("gl-set-token-indicator"), + ).toBeInTheDocument(); + }); + + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: { + gitlab: null, + }, + }); + queryClient.invalidateQueries(); + + rerender(); + + await waitFor(() => { + const githubInput = screen.getByTestId("github-token-input"); + expect(githubInput).toHaveProperty("placeholder", ""); + expect( + screen.queryByTestId("gh-set-token-indicator"), + ).not.toBeInTheDocument(); + + const gitlabInput = screen.getByTestId("gitlab-token-input"); + expect(gitlabInput).toHaveProperty("placeholder", ""); + expect( + screen.queryByTestId("gl-set-token-indicator"), + ).toBeInTheDocument(); + }); + }); + + it("should render the 'Configure GitHub Repositories' button if SaaS mode and app slug exists", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG); + + const { rerender } = renderGitSettingsScreen(); + + let button = screen.queryByTestId("configure-github-repositories-button"); + expect(button).not.toBeInTheDocument(); + + expect(screen.getByTestId("submit-button")).toBeInTheDocument(); + expect(screen.getByTestId("disconnect-tokens-button")).toBeInTheDocument(); + + getConfigSpy.mockResolvedValue(VALID_SAAS_CONFIG); + queryClient.invalidateQueries(); + rerender(); + + await waitFor(() => { + // wait until queries are resolved + expect(queryClient.isFetching()).toBe(0); + button = screen.queryByTestId("configure-github-repositories-button"); + expect(button).not.toBeInTheDocument(); + }); + + getConfigSpy.mockResolvedValue({ + ...VALID_SAAS_CONFIG, + APP_SLUG: "test-slug", + }); + queryClient.invalidateQueries(); + rerender(); + + await waitFor(() => { + button = screen.getByTestId("configure-github-repositories-button"); + expect(button).toBeInTheDocument(); + expect(screen.queryByTestId("submit-button")).not.toBeInTheDocument(); + expect( + screen.queryByTestId("disconnect-tokens-button"), + ).not.toBeInTheDocument(); + }); + }); +}); + +describe("Form submission", () => { + it("should save the GitHub token", async () => { + const saveProvidersSpy = vi.spyOn(SecretsService, "addGitProvider"); + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG); + + renderGitSettingsScreen(); + + const githubInput = await screen.findByTestId("github-token-input"); + const submit = await screen.findByTestId("submit-button"); + + await userEvent.type(githubInput, "test-token"); + await userEvent.click(submit); + + expect(saveProvidersSpy).toHaveBeenCalledWith({ + github: { token: "test-token", host: "" }, + gitlab: { token: "", host: "" }, + }); + + const gitlabInput = await screen.findByTestId("gitlab-token-input"); + await userEvent.type(gitlabInput, "test-token"); + await userEvent.click(submit); + + expect(saveProvidersSpy).toHaveBeenCalledWith({ + github: { token: "test-token", host: "" }, + gitlab: { token: "", host: "" }, + }); + }); + + it("should disable the button if there is no input", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG); + + renderGitSettingsScreen(); + + const submit = await screen.findByTestId("submit-button"); + expect(submit).toBeDisabled(); + + const githubInput = await screen.findByTestId("github-token-input"); + await userEvent.type(githubInput, "test-token"); + + expect(submit).not.toBeDisabled(); + + await userEvent.clear(githubInput); + expect(submit).toBeDisabled(); + + const gitlabInput = await screen.findByTestId("gitlab-token-input"); + await userEvent.type(gitlabInput, "test-token"); + + expect(submit).not.toBeDisabled(); + + await userEvent.clear(gitlabInput); + expect(submit).toBeDisabled(); + }); + + it("should enable a disconnect tokens button if there is at least one token set", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + + getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: { + github: null, + gitlab: null, + }, + }); + + renderGitSettingsScreen(); + await screen.findByTestId("git-settings-screen"); + + let disconnectButton = await screen.findByTestId( + "disconnect-tokens-button", + ); + await waitFor(() => expect(disconnectButton).not.toBeDisabled()); + + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + }); + queryClient.invalidateQueries(); + + disconnectButton = await screen.findByTestId("disconnect-tokens-button"); + await waitFor(() => expect(disconnectButton).toBeDisabled()); + }); + + it("should call logout when pressing the disconnect tokens button", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + const logoutSpy = vi.spyOn(OpenHands, "logout"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + + getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: { + github: null, + gitlab: null, + }, + }); + + renderGitSettingsScreen(); + + const disconnectButton = await screen.findByTestId( + "disconnect-tokens-button", + ); + await waitFor(() => expect(disconnectButton).not.toBeDisabled()); + await userEvent.click(disconnectButton); + + expect(logoutSpy).toHaveBeenCalled(); + }); + + // flaky test + it.skip("should disable the button when submitting changes", async () => { + const saveSettingsSpy = vi.spyOn(SecretsService, "addGitProvider"); + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG); + + renderGitSettingsScreen(); + + const submit = await screen.findByTestId("submit-button"); + expect(submit).toBeDisabled(); + + const githubInput = await screen.findByTestId("github-token-input"); + await userEvent.type(githubInput, "test-token"); + expect(submit).not.toBeDisabled(); + + // submit the form + await userEvent.click(submit); + expect(saveSettingsSpy).toHaveBeenCalled(); + + expect(submit).toHaveTextContent("Saving..."); + expect(submit).toBeDisabled(); + + await waitFor(() => expect(submit).toHaveTextContent("Save")); + }); + + it("should disable the button after submitting changes", async () => { + const saveProvidersSpy = vi.spyOn(SecretsService, "addGitProvider"); + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG); + + renderGitSettingsScreen(); + await screen.findByTestId("git-settings-screen"); + + const submit = await screen.findByTestId("submit-button"); + expect(submit).toBeDisabled(); + + const githubInput = await screen.findByTestId("github-token-input"); + await userEvent.type(githubInput, "test-token"); + expect(submit).not.toBeDisabled(); + + // submit the form + await userEvent.click(submit); + expect(saveProvidersSpy).toHaveBeenCalled(); + expect(submit).toBeDisabled(); + + const gitlabInput = await screen.findByTestId("gitlab-token-input"); + await userEvent.type(gitlabInput, "test-token"); + expect(gitlabInput).toHaveValue("test-token"); + expect(submit).not.toBeDisabled(); + + // submit the form + await userEvent.click(submit); + expect(saveProvidersSpy).toHaveBeenCalled(); + + await waitFor(() => expect(submit).toBeDisabled()); + }); +}); + +describe("Status toasts", () => { + it("should call displaySuccessToast when the settings are saved", async () => { + const saveProvidersSpy = vi.spyOn(SecretsService, "addGitProvider"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS); + + const displaySuccessToastSpy = vi.spyOn( + ToastHandlers, + "displaySuccessToast", + ); + + renderGitSettingsScreen(); + + // Toggle setting to change + const githubInput = await screen.findByTestId("github-token-input"); + await userEvent.type(githubInput, "test-token"); + + const submit = await screen.findByTestId("submit-button"); + await userEvent.click(submit); + + expect(saveProvidersSpy).toHaveBeenCalled(); + await waitFor(() => expect(displaySuccessToastSpy).toHaveBeenCalled()); + }); + + it("should call displayErrorToast when the settings fail to save", async () => { + const saveProvidersSpy = vi.spyOn(SecretsService, "addGitProvider"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS); + + const displayErrorToastSpy = vi.spyOn(ToastHandlers, "displayErrorToast"); + + saveProvidersSpy.mockRejectedValue(new Error("Failed to save settings")); + + renderGitSettingsScreen(); + + // Toggle setting to change + const gitlabInput = await screen.findByTestId("gitlab-token-input"); + await userEvent.type(gitlabInput, "test-token"); + + const submit = await screen.findByTestId("submit-button"); + await userEvent.click(submit); + + expect(saveProvidersSpy).toHaveBeenCalled(); + expect(displayErrorToastSpy).toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/routes/home-screen.test.tsx b/frontend/__tests__/routes/home-screen.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0789dda955e121014ec2a9e173e6095a7c77359b --- /dev/null +++ b/frontend/__tests__/routes/home-screen.test.tsx @@ -0,0 +1,367 @@ +import { render, screen, waitFor, within } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; +import userEvent from "@testing-library/user-event"; +import { createRoutesStub } from "react-router"; +import { Provider } from "react-redux"; +import { createAxiosNotFoundErrorObject, setupStore } from "test-utils"; +import HomeScreen from "#/routes/home"; +import { GitRepository } from "#/types/git"; +import OpenHands from "#/api/open-hands"; +import MainApp from "#/routes/root-layout"; +import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers"; + +const RouterStub = createRoutesStub([ + { + Component: MainApp, + path: "/", + children: [ + { + Component: HomeScreen, + path: "/", + }, + { + Component: () =>
, + path: "/conversations/:conversationId", + }, + { + Component: () =>
, + path: "/settings", + }, + ], + }, +]); + +const renderHomeScreen = () => + render(, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }); + +const MOCK_RESPOSITORIES: GitRepository[] = [ + { + id: 1, + full_name: "octocat/hello-world", + git_provider: "github", + is_public: true, + }, + { + id: 2, + full_name: "octocat/earth", + git_provider: "github", + is_public: true, + }, +]; + +describe("HomeScreen", () => { + beforeEach(() => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: { + github: null, + gitlab: null, + }, + }); + }); + + it("should render", () => { + renderHomeScreen(); + screen.getByTestId("home-screen"); + }); + + it("should render the repository connector and suggested tasks sections", async () => { + renderHomeScreen(); + + await waitFor(() => { + screen.getByTestId("repo-connector"); + screen.getByTestId("task-suggestions"); + }); + }); + + it("should have responsive layout for mobile and desktop screens", async () => { + renderHomeScreen(); + + const mainContainer = screen + .getByTestId("home-screen") + .querySelector("main"); + expect(mainContainer).toHaveClass("flex", "flex-col", "md:flex-row"); + }); + + it("should filter the suggested tasks based on the selected repository", async () => { + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_RESPOSITORIES); + + renderHomeScreen(); + + const taskSuggestions = await screen.findByTestId("task-suggestions"); + + // Initially, all tasks should be visible + await waitFor(() => { + within(taskSuggestions).getByText("octocat/hello-world"); + within(taskSuggestions).getByText("octocat/earth"); + }); + + // Select a repository from the dropdown + const repoConnector = screen.getByTestId("repo-connector"); + + const dropdown = within(repoConnector).getByTestId("repo-dropdown"); + await userEvent.click(dropdown); + + const repoOption = screen.getAllByText("octocat/hello-world")[1]; + await userEvent.click(repoOption); + + // After selecting a repository, only tasks related to that repository should be visible + await waitFor(() => { + within(taskSuggestions).getByText("octocat/hello-world"); + expect( + within(taskSuggestions).queryByText("octocat/earth"), + ).not.toBeInTheDocument(); + }); + }); + + it("should reset the filtered tasks when the selected repository is cleared", async () => { + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_RESPOSITORIES); + + renderHomeScreen(); + + const taskSuggestions = await screen.findByTestId("task-suggestions"); + + // Initially, all tasks should be visible + await waitFor(() => { + within(taskSuggestions).getByText("octocat/hello-world"); + within(taskSuggestions).getByText("octocat/earth"); + }); + + // Select a repository from the dropdown + const repoConnector = screen.getByTestId("repo-connector"); + + const dropdown = within(repoConnector).getByTestId("repo-dropdown"); + await userEvent.click(dropdown); + + const repoOption = screen.getAllByText("octocat/hello-world")[1]; + await userEvent.click(repoOption); + + // After selecting a repository, only tasks related to that repository should be visible + await waitFor(() => { + within(taskSuggestions).getByText("octocat/hello-world"); + expect( + within(taskSuggestions).queryByText("octocat/earth"), + ).not.toBeInTheDocument(); + }); + + // Clear the selected repository + await userEvent.clear(dropdown); + + // All tasks should be visible again + await waitFor(() => { + within(taskSuggestions).getByText("octocat/hello-world"); + within(taskSuggestions).getByText("octocat/earth"); + }); + }); + + describe("launch buttons", () => { + const setupLaunchButtons = async () => { + let headerLaunchButton = screen.getByTestId("header-launch-button"); + let repoLaunchButton = await screen.findByTestId("repo-launch-button"); + let tasksLaunchButtons = + await screen.findAllByTestId("task-launch-button"); + + // Select a repository from the dropdown to enable the repo launch button + const repoConnector = screen.getByTestId("repo-connector"); + const dropdown = within(repoConnector).getByTestId("repo-dropdown"); + await userEvent.click(dropdown); + const repoOption = screen.getAllByText("octocat/hello-world")[1]; + await userEvent.click(repoOption); + + expect(headerLaunchButton).not.toBeDisabled(); + expect(repoLaunchButton).not.toBeDisabled(); + tasksLaunchButtons.forEach((button) => { + expect(button).not.toBeDisabled(); + }); + + headerLaunchButton = screen.getByTestId("header-launch-button"); + repoLaunchButton = screen.getByTestId("repo-launch-button"); + tasksLaunchButtons = await screen.findAllByTestId("task-launch-button"); + + return { + headerLaunchButton, + repoLaunchButton, + tasksLaunchButtons, + }; + }; + + beforeEach(() => { + const retrieveUserGitRepositoriesSpy = vi.spyOn( + OpenHands, + "retrieveUserGitRepositories", + ); + retrieveUserGitRepositoriesSpy.mockResolvedValue(MOCK_RESPOSITORIES); + }); + + it("should disable the other launch buttons when the header launch button is clicked", async () => { + renderHomeScreen(); + const { headerLaunchButton, repoLaunchButton } = + await setupLaunchButtons(); + + const tasksLaunchButtonsAfter = + await screen.findAllByTestId("task-launch-button"); + + // All other buttons should be disabled when the header button is clicked + await userEvent.click(headerLaunchButton); + + expect(headerLaunchButton).toBeDisabled(); + expect(repoLaunchButton).toBeDisabled(); + tasksLaunchButtonsAfter.forEach((button) => { + expect(button).toBeDisabled(); + }); + }); + + it("should disable the other launch buttons when the repo launch button is clicked", async () => { + renderHomeScreen(); + const { headerLaunchButton, repoLaunchButton } = + await setupLaunchButtons(); + + const tasksLaunchButtonsAfter = + await screen.findAllByTestId("task-launch-button"); + + // All other buttons should be disabled when the repo button is clicked + await userEvent.click(repoLaunchButton); + + expect(headerLaunchButton).toBeDisabled(); + expect(repoLaunchButton).toBeDisabled(); + tasksLaunchButtonsAfter.forEach((button) => { + expect(button).toBeDisabled(); + }); + }); + + it("should disable the other launch buttons when any task launch button is clicked", async () => { + renderHomeScreen(); + const { headerLaunchButton, repoLaunchButton, tasksLaunchButtons } = + await setupLaunchButtons(); + + const tasksLaunchButtonsAfter = + await screen.findAllByTestId("task-launch-button"); + + // All other buttons should be disabled when the task button is clicked + await userEvent.click(tasksLaunchButtons[0]); + + expect(headerLaunchButton).toBeDisabled(); + expect(repoLaunchButton).toBeDisabled(); + tasksLaunchButtonsAfter.forEach((button) => { + expect(button).toBeDisabled(); + }); + }); + }); + + it("should hide the suggested tasks section if not authed with git(hub|lab)", async () => { + renderHomeScreen(); + + const taskSuggestions = screen.queryByTestId("task-suggestions"); + const repoConnector = screen.getByTestId("repo-connector"); + + expect(taskSuggestions).not.toBeInTheDocument(); + expect(repoConnector).toBeInTheDocument(); + }); +}); + +describe("Settings 404", () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + + it("should open the settings modal if GET /settings fails with a 404", async () => { + const error = createAxiosNotFoundErrorObject(); + getSettingsSpy.mockRejectedValue(error); + + renderHomeScreen(); + + const settingsModal = await screen.findByTestId("ai-config-modal"); + expect(settingsModal).toBeInTheDocument(); + }); + + it("should navigate to the settings screen when clicking the advanced settings button", async () => { + const error = createAxiosNotFoundErrorObject(); + getSettingsSpy.mockRejectedValue(error); + + const user = userEvent.setup(); + renderHomeScreen(); + + const settingsScreen = screen.queryByTestId("settings-screen"); + expect(settingsScreen).not.toBeInTheDocument(); + + const settingsModal = await screen.findByTestId("ai-config-modal"); + expect(settingsModal).toBeInTheDocument(); + + const advancedSettingsButton = await screen.findByTestId( + "advanced-settings-link", + ); + await user.click(advancedSettingsButton); + + const settingsScreenAfter = await screen.findByTestId("settings-screen"); + expect(settingsScreenAfter).toBeInTheDocument(); + + const settingsModalAfter = screen.queryByTestId("ai-config-modal"); + expect(settingsModalAfter).not.toBeInTheDocument(); + }); + + it("should not open the settings modal if GET /settings fails but is SaaS mode", async () => { + // @ts-expect-error - we only need APP_MODE for this test + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + FEATURE_FLAGS: { + ENABLE_BILLING: false, + HIDE_LLM_SETTINGS: false, + }, + }); + const error = createAxiosNotFoundErrorObject(); + getSettingsSpy.mockRejectedValue(error); + + renderHomeScreen(); + + // small hack to wait for the modal to not appear + await expect( + screen.findByTestId("ai-config-modal", {}, { timeout: 1000 }), + ).rejects.toThrow(); + }); +}); + +describe("Setup Payment modal", () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + + it("should only render if SaaS mode and is new user", async () => { + // @ts-expect-error - we only need the APP_MODE for this test + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + FEATURE_FLAGS: { + ENABLE_BILLING: true, + HIDE_LLM_SETTINGS: false, + }, + }); + const error = createAxiosNotFoundErrorObject(); + getSettingsSpy.mockRejectedValue(error); + + renderHomeScreen(); + + const setupPaymentModal = await screen.findByTestId( + "proceed-to-stripe-button", + ); + expect(setupPaymentModal).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/routes/llm-settings.test.tsx b/frontend/__tests__/routes/llm-settings.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..86cce488c1e4380520ae43e3efd48467360301c7 --- /dev/null +++ b/frontend/__tests__/routes/llm-settings.test.tsx @@ -0,0 +1,714 @@ +import { render, screen, waitFor, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; +import LlmSettingsScreen from "#/routes/llm-settings"; +import OpenHands from "#/api/open-hands"; +import { + MOCK_DEFAULT_USER_SETTINGS, + resetTestHandlersMockSettings, +} from "#/mocks/handlers"; +import * as AdvancedSettingsUtlls from "#/utils/has-advanced-settings-set"; +import * as ToastHandlers from "#/utils/custom-toast-handlers"; + +const renderLlmSettingsScreen = () => + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + +beforeEach(() => { + vi.resetAllMocks(); + resetTestHandlersMockSettings(); +}); + +describe("Content", () => { + describe("Basic form", () => { + it("should render the basic form by default", async () => { + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const basicFom = screen.getByTestId("llm-settings-form-basic"); + within(basicFom).getByTestId("llm-provider-input"); + within(basicFom).getByTestId("llm-model-input"); + within(basicFom).getByTestId("llm-api-key-input"); + within(basicFom).getByTestId("llm-api-key-help-anchor"); + }); + + it("should render the default values if non exist", async () => { + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const provider = screen.getByTestId("llm-provider-input"); + const model = screen.getByTestId("llm-model-input"); + const apiKey = screen.getByTestId("llm-api-key-input"); + + await waitFor(() => { + expect(provider).toHaveValue("Anthropic"); + expect(model).toHaveValue("claude-sonnet-4-20250514"); + + expect(apiKey).toHaveValue(""); + expect(apiKey).toHaveProperty("placeholder", ""); + }); + }); + + it("should render the existing settings values", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + llm_model: "openai/gpt-4o", + llm_api_key_set: true, + }); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const provider = screen.getByTestId("llm-provider-input"); + const model = screen.getByTestId("llm-model-input"); + const apiKey = screen.getByTestId("llm-api-key-input"); + + await waitFor(() => { + expect(provider).toHaveValue("OpenAI"); + expect(model).toHaveValue("gpt-4o"); + + expect(apiKey).toHaveValue(""); + expect(apiKey).toHaveProperty("placeholder", ""); + expect(screen.getByTestId("set-indicator")).toBeInTheDocument(); + }); + }); + }); + + describe("Advanced form", () => { + it("should render the advanced form if the switch is toggled", async () => { + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + const basicForm = screen.getByTestId("llm-settings-form-basic"); + + expect( + screen.queryByTestId("llm-settings-form-advanced"), + ).not.toBeInTheDocument(); + expect(basicForm).toBeInTheDocument(); + + await userEvent.click(advancedSwitch); + + expect( + screen.queryByTestId("llm-settings-form-advanced"), + ).toBeInTheDocument(); + expect(basicForm).not.toBeInTheDocument(); + + const advancedForm = screen.getByTestId("llm-settings-form-advanced"); + within(advancedForm).getByTestId("llm-custom-model-input"); + within(advancedForm).getByTestId("base-url-input"); + within(advancedForm).getByTestId("llm-api-key-input"); + within(advancedForm).getByTestId("llm-api-key-help-anchor-advanced"); + within(advancedForm).getByTestId("agent-input"); + within(advancedForm).getByTestId("enable-confirmation-mode-switch"); + within(advancedForm).getByTestId("enable-memory-condenser-switch"); + + await userEvent.click(advancedSwitch); + expect( + screen.queryByTestId("llm-settings-form-advanced"), + ).not.toBeInTheDocument(); + expect(screen.getByTestId("llm-settings-form-basic")).toBeInTheDocument(); + }); + + it("should render the default advanced settings", async () => { + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + expect(advancedSwitch).not.toBeChecked(); + + await userEvent.click(advancedSwitch); + + const model = screen.getByTestId("llm-custom-model-input"); + const baseUrl = screen.getByTestId("base-url-input"); + const apiKey = screen.getByTestId("llm-api-key-input"); + const agent = screen.getByTestId("agent-input"); + const confirmation = screen.getByTestId( + "enable-confirmation-mode-switch", + ); + const condensor = screen.getByTestId("enable-memory-condenser-switch"); + + expect(model).toHaveValue("anthropic/claude-sonnet-4-20250514"); + expect(baseUrl).toHaveValue(""); + expect(apiKey).toHaveValue(""); + expect(apiKey).toHaveProperty("placeholder", ""); + expect(agent).toHaveValue("CodeActAgent"); + expect(confirmation).not.toBeChecked(); + expect(condensor).toBeChecked(); + + // check that security analyzer is present + expect( + screen.queryByTestId("security-analyzer-input"), + ).not.toBeInTheDocument(); + await userEvent.click(confirmation); + screen.getByTestId("security-analyzer-input"); + }); + + it("should render the advanced form if existings settings are advanced", async () => { + const hasAdvancedSettingsSetSpy = vi.spyOn( + AdvancedSettingsUtlls, + "hasAdvancedSettingsSet", + ); + hasAdvancedSettingsSetSpy.mockReturnValue(true); + + renderLlmSettingsScreen(); + + await waitFor(() => { + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + expect(advancedSwitch).toBeChecked(); + screen.getByTestId("llm-settings-form-advanced"); + }); + }); + + it("should render existing advanced settings correctly", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + llm_model: "openai/gpt-4o", + llm_base_url: "https://api.openai.com/v1/chat/completions", + llm_api_key_set: true, + agent: "CoActAgent", + confirmation_mode: true, + enable_default_condenser: false, + security_analyzer: "mock-invariant", + }); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const model = screen.getByTestId("llm-custom-model-input"); + const baseUrl = screen.getByTestId("base-url-input"); + const apiKey = screen.getByTestId("llm-api-key-input"); + const agent = screen.getByTestId("agent-input"); + const confirmation = screen.getByTestId( + "enable-confirmation-mode-switch", + ); + const condensor = screen.getByTestId("enable-memory-condenser-switch"); + const securityAnalyzer = screen.getByTestId("security-analyzer-input"); + + await waitFor(() => { + expect(model).toHaveValue("openai/gpt-4o"); + expect(baseUrl).toHaveValue( + "https://api.openai.com/v1/chat/completions", + ); + expect(apiKey).toHaveValue(""); + expect(apiKey).toHaveProperty("placeholder", ""); + expect(agent).toHaveValue("CoActAgent"); + expect(confirmation).toBeChecked(); + expect(condensor).not.toBeChecked(); + expect(securityAnalyzer).toHaveValue("mock-invariant"); + }); + }); + }); + + it.todo("should render an indicator if the llm api key is set"); +}); + +describe("Form submission", () => { + it("should submit the basic form with the correct values", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const provider = screen.getByTestId("llm-provider-input"); + const model = screen.getByTestId("llm-model-input"); + const apiKey = screen.getByTestId("llm-api-key-input"); + + // select provider + await userEvent.click(provider); + const providerOption = screen.getByText("OpenAI"); + await userEvent.click(providerOption); + expect(provider).toHaveValue("OpenAI"); + + // enter api key + await userEvent.type(apiKey, "test-api-key"); + + // select model + await userEvent.click(model); + const modelOption = screen.getByText("gpt-4o"); + await userEvent.click(modelOption); + expect(model).toHaveValue("gpt-4o"); + + const submitButton = screen.getByTestId("submit-button"); + await userEvent.click(submitButton); + + expect(saveSettingsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + llm_model: "openai/gpt-4o", + llm_api_key: "test-api-key", + }), + ); + }); + + it("should submit the advanced form with the correct values", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + await userEvent.click(advancedSwitch); + + const model = screen.getByTestId("llm-custom-model-input"); + const baseUrl = screen.getByTestId("base-url-input"); + const apiKey = screen.getByTestId("llm-api-key-input"); + const agent = screen.getByTestId("agent-input"); + const confirmation = screen.getByTestId("enable-confirmation-mode-switch"); + const condensor = screen.getByTestId("enable-memory-condenser-switch"); + + // enter custom model + await userEvent.clear(model); + await userEvent.type(model, "openai/gpt-4o"); + expect(model).toHaveValue("openai/gpt-4o"); + + // enter base url + await userEvent.type(baseUrl, "https://api.openai.com/v1/chat/completions"); + expect(baseUrl).toHaveValue("https://api.openai.com/v1/chat/completions"); + + // enter api key + await userEvent.type(apiKey, "test-api-key"); + + // toggle confirmation mode + await userEvent.click(confirmation); + expect(confirmation).toBeChecked(); + + // toggle memory condensor + await userEvent.click(condensor); + expect(condensor).not.toBeChecked(); + + // select agent + await userEvent.click(agent); + const agentOption = screen.getByText("CoActAgent"); + await userEvent.click(agentOption); + expect(agent).toHaveValue("CoActAgent"); + + // select security analyzer + const securityAnalyzer = screen.getByTestId("security-analyzer-input"); + await userEvent.click(securityAnalyzer); + const securityAnalyzerOption = screen.getByText("mock-invariant"); + await userEvent.click(securityAnalyzerOption); + + const submitButton = screen.getByTestId("submit-button"); + await userEvent.click(submitButton); + + expect(saveSettingsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + llm_model: "openai/gpt-4o", + llm_base_url: "https://api.openai.com/v1/chat/completions", + agent: "CoActAgent", + confirmation_mode: true, + enable_default_condenser: false, + security_analyzer: "mock-invariant", + }), + ); + }); + + it("should disable the button if there are no changes in the basic form", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + llm_model: "openai/gpt-4o", + llm_api_key_set: true, + }); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + screen.getByTestId("llm-settings-form-basic"); + + const submitButton = screen.getByTestId("submit-button"); + expect(submitButton).toBeDisabled(); + + const model = screen.getByTestId("llm-model-input"); + const apiKey = screen.getByTestId("llm-api-key-input"); + + // select model + await userEvent.click(model); + const modelOption = screen.getByText("gpt-4o-mini"); + await userEvent.click(modelOption); + expect(model).toHaveValue("gpt-4o-mini"); + expect(submitButton).not.toBeDisabled(); + + // reset model + await userEvent.click(model); + const modelOption2 = screen.getByText("gpt-4o"); + await userEvent.click(modelOption2); + expect(model).toHaveValue("gpt-4o"); + expect(submitButton).toBeDisabled(); + + // set api key + await userEvent.type(apiKey, "test-api-key"); + expect(apiKey).toHaveValue("test-api-key"); + expect(submitButton).not.toBeDisabled(); + + // reset api key + await userEvent.clear(apiKey); + expect(apiKey).toHaveValue(""); + expect(submitButton).toBeDisabled(); + }); + + it("should disable the button if there are no changes in the advanced form", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + llm_model: "openai/gpt-4o", + llm_base_url: "https://api.openai.com/v1/chat/completions", + llm_api_key_set: true, + confirmation_mode: true, + }); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + screen.getByTestId("llm-settings-form-advanced"); + + const submitButton = screen.getByTestId("submit-button"); + expect(submitButton).toBeDisabled(); + + const model = screen.getByTestId("llm-custom-model-input"); + const baseUrl = screen.getByTestId("base-url-input"); + const apiKey = screen.getByTestId("llm-api-key-input"); + const agent = screen.getByTestId("agent-input"); + const confirmation = screen.getByTestId("enable-confirmation-mode-switch"); + const condensor = screen.getByTestId("enable-memory-condenser-switch"); + + // enter custom model + await userEvent.type(model, "-mini"); + expect(model).toHaveValue("openai/gpt-4o-mini"); + expect(submitButton).not.toBeDisabled(); + + // reset model + await userEvent.clear(model); + expect(model).toHaveValue(""); + expect(submitButton).toBeDisabled(); + + await userEvent.type(model, "openai/gpt-4o"); + expect(model).toHaveValue("openai/gpt-4o"); + expect(submitButton).toBeDisabled(); + + // enter base url + await userEvent.type(baseUrl, "/extra"); + expect(baseUrl).toHaveValue( + "https://api.openai.com/v1/chat/completions/extra", + ); + expect(submitButton).not.toBeDisabled(); + + await userEvent.clear(baseUrl); + expect(baseUrl).toHaveValue(""); + expect(submitButton).not.toBeDisabled(); + + await userEvent.type(baseUrl, "https://api.openai.com/v1/chat/completions"); + expect(baseUrl).toHaveValue("https://api.openai.com/v1/chat/completions"); + expect(submitButton).toBeDisabled(); + + // set api key + await userEvent.type(apiKey, "test-api-key"); + expect(apiKey).toHaveValue("test-api-key"); + expect(submitButton).not.toBeDisabled(); + + // reset api key + await userEvent.clear(apiKey); + expect(apiKey).toHaveValue(""); + expect(submitButton).toBeDisabled(); + + // set agent + await userEvent.clear(agent); + await userEvent.type(agent, "test-agent"); + expect(agent).toHaveValue("test-agent"); + expect(submitButton).not.toBeDisabled(); + + // reset agent + await userEvent.clear(agent); + expect(agent).toHaveValue(""); + expect(submitButton).toBeDisabled(); + + await userEvent.type(agent, "CodeActAgent"); + expect(agent).toHaveValue("CodeActAgent"); + expect(submitButton).toBeDisabled(); + + // toggle confirmation mode + await userEvent.click(confirmation); + expect(confirmation).not.toBeChecked(); + expect(submitButton).not.toBeDisabled(); + await userEvent.click(confirmation); + expect(confirmation).toBeChecked(); + expect(submitButton).toBeDisabled(); + + // toggle memory condensor + await userEvent.click(condensor); + expect(condensor).not.toBeChecked(); + expect(submitButton).not.toBeDisabled(); + await userEvent.click(condensor); + expect(condensor).toBeChecked(); + expect(submitButton).toBeDisabled(); + + // select security analyzer + const securityAnalyzer = screen.getByTestId("security-analyzer-input"); + await userEvent.click(securityAnalyzer); + const securityAnalyzerOption = screen.getByText("mock-invariant"); + await userEvent.click(securityAnalyzerOption); + expect(securityAnalyzer).toHaveValue("mock-invariant"); + + expect(submitButton).not.toBeDisabled(); + + await userEvent.clear(securityAnalyzer); + expect(securityAnalyzer).toHaveValue(""); + expect(submitButton).toBeDisabled(); + }); + + it("should reset button state when switching between forms", async () => { + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + const submitButton = screen.getByTestId("submit-button"); + + expect(submitButton).toBeDisabled(); + + // dirty the basic form + const apiKey = screen.getByTestId("llm-api-key-input"); + await userEvent.type(apiKey, "test-api-key"); + expect(submitButton).not.toBeDisabled(); + + await userEvent.click(advancedSwitch); + expect(submitButton).toBeDisabled(); + + // dirty the advanced form + const model = screen.getByTestId("llm-custom-model-input"); + await userEvent.type(model, "openai/gpt-4o"); + expect(submitButton).not.toBeDisabled(); + + await userEvent.click(advancedSwitch); + expect(submitButton).toBeDisabled(); + }); + + // flaky test + it.skip("should disable the button when submitting changes", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const apiKey = screen.getByTestId("llm-api-key-input"); + await userEvent.type(apiKey, "test-api-key"); + + const submitButton = screen.getByTestId("submit-button"); + await userEvent.click(submitButton); + + expect(saveSettingsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + llm_api_key: "test-api-key", + }), + ); + + expect(submitButton).toHaveTextContent("Saving..."); + expect(submitButton).toBeDisabled(); + + await waitFor(() => { + expect(submitButton).toHaveTextContent("Save"); + expect(submitButton).toBeDisabled(); + }); + }); + + it("should clear advanced settings when saving basic settings", async () => { + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + llm_model: "openai/gpt-4o", + llm_base_url: "https://api.openai.com/v1/chat/completions", + llm_api_key_set: true, + confirmation_mode: true, + }); + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + renderLlmSettingsScreen(); + + await screen.findByTestId("llm-settings-screen"); + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + await userEvent.click(advancedSwitch); + + const provider = screen.getByTestId("llm-provider-input"); + const model = screen.getByTestId("llm-model-input"); + + // select provider + await userEvent.click(provider); + const providerOption = screen.getByText("Anthropic"); + await userEvent.click(providerOption); + + // select model + await userEvent.click(model); + const modelOption = screen.getByText("claude-sonnet-4-20250514"); + await userEvent.click(modelOption); + + const submitButton = screen.getByTestId("submit-button"); + await userEvent.click(submitButton); + + expect(saveSettingsSpy).toHaveBeenCalledWith( + expect.objectContaining({ + llm_model: "anthropic/claude-sonnet-4-20250514", + llm_base_url: "", + confirmation_mode: false, + }), + ); + }); +}); + +describe("Status toasts", () => { + describe("Basic form", () => { + it("should call displaySuccessToast when the settings are saved", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + + const displaySuccessToastSpy = vi.spyOn( + ToastHandlers, + "displaySuccessToast", + ); + + renderLlmSettingsScreen(); + + // Toggle setting to change + const apiKeyInput = await screen.findByTestId("llm-api-key-input"); + await userEvent.type(apiKeyInput, "test-api-key"); + + const submit = await screen.findByTestId("submit-button"); + await userEvent.click(submit); + + expect(saveSettingsSpy).toHaveBeenCalled(); + await waitFor(() => expect(displaySuccessToastSpy).toHaveBeenCalled()); + }); + + it("should call displayErrorToast when the settings fail to save", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + + const displayErrorToastSpy = vi.spyOn(ToastHandlers, "displayErrorToast"); + + saveSettingsSpy.mockRejectedValue(new Error("Failed to save settings")); + + renderLlmSettingsScreen(); + + // Toggle setting to change + const apiKeyInput = await screen.findByTestId("llm-api-key-input"); + await userEvent.type(apiKeyInput, "test-api-key"); + + const submit = await screen.findByTestId("submit-button"); + await userEvent.click(submit); + + expect(saveSettingsSpy).toHaveBeenCalled(); + expect(displayErrorToastSpy).toHaveBeenCalled(); + }); + }); + + describe("Advanced form", () => { + it("should call displaySuccessToast when the settings are saved", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + + const displaySuccessToastSpy = vi.spyOn( + ToastHandlers, + "displaySuccessToast", + ); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + await userEvent.click(advancedSwitch); + await screen.findByTestId("llm-settings-form-advanced"); + + // Toggle setting to change + const apiKeyInput = await screen.findByTestId("llm-api-key-input"); + await userEvent.type(apiKeyInput, "test-api-key"); + + const submit = await screen.findByTestId("submit-button"); + await userEvent.click(submit); + + expect(saveSettingsSpy).toHaveBeenCalled(); + await waitFor(() => expect(displaySuccessToastSpy).toHaveBeenCalled()); + }); + + it("should call displayErrorToast when the settings fail to save", async () => { + const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings"); + + const displayErrorToastSpy = vi.spyOn(ToastHandlers, "displayErrorToast"); + + saveSettingsSpy.mockRejectedValue(new Error("Failed to save settings")); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + await userEvent.click(advancedSwitch); + await screen.findByTestId("llm-settings-form-advanced"); + + // Toggle setting to change + const apiKeyInput = await screen.findByTestId("llm-api-key-input"); + await userEvent.type(apiKeyInput, "test-api-key"); + + const submit = await screen.findByTestId("submit-button"); + await userEvent.click(submit); + + expect(saveSettingsSpy).toHaveBeenCalled(); + expect(displayErrorToastSpy).toHaveBeenCalled(); + }); + }); +}); + +describe("SaaS mode", () => { + it("should not render the runtime settings input in oss mode", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - only return mode + getConfigSpy.mockResolvedValue({ + APP_MODE: "oss", + }); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + await userEvent.click(advancedSwitch); + await screen.findByTestId("llm-settings-form-advanced"); + + const runtimeSettingsInput = screen.queryByTestId("runtime-settings-input"); + expect(runtimeSettingsInput).not.toBeInTheDocument(); + }); + + it("should render the runtime settings input in saas mode", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - only return mode + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + }); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + await userEvent.click(advancedSwitch); + await screen.findByTestId("llm-settings-form-advanced"); + + const runtimeSettingsInput = screen.queryByTestId("runtime-settings-input"); + expect(runtimeSettingsInput).toBeInTheDocument(); + }); + + it("should always render the runtime settings input as disabled", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - only return mode + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + }); + + renderLlmSettingsScreen(); + await screen.findByTestId("llm-settings-screen"); + + const advancedSwitch = screen.getByTestId("advanced-settings-switch"); + await userEvent.click(advancedSwitch); + await screen.findByTestId("llm-settings-form-advanced"); + + const runtimeSettingsInput = screen.queryByTestId("runtime-settings-input"); + expect(runtimeSettingsInput).toBeInTheDocument(); + expect(runtimeSettingsInput).toBeDisabled(); + }); +}); diff --git a/frontend/__tests__/routes/secrets-settings.test.tsx b/frontend/__tests__/routes/secrets-settings.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e32b0ed6a985dea41c47576c7e4747acbb169b1b --- /dev/null +++ b/frontend/__tests__/routes/secrets-settings.test.tsx @@ -0,0 +1,565 @@ +import { render, screen, waitFor, within } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import userEvent from "@testing-library/user-event"; +import { createRoutesStub, Outlet } from "react-router"; +import SecretsSettingsScreen from "#/routes/secrets-settings"; +import { SecretsService } from "#/api/secrets-service"; +import { GetSecretsResponse } from "#/api/secrets-service.types"; +import OpenHands from "#/api/open-hands"; +import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers"; + +const MOCK_GET_SECRETS_RESPONSE: GetSecretsResponse["custom_secrets"] = [ + { + name: "My_Secret_1", + description: "My first secret", + }, + { + name: "My_Secret_2", + description: "My second secret", + }, +]; + +const RouterStub = createRoutesStub([ + { + Component: () => , + path: "/settings", + children: [ + { + Component: SecretsSettingsScreen, + path: "/settings/secrets", + }, + { + Component: () =>
, + path: "/settings/git", + }, + ], + }, +]); + +const renderSecretsSettings = () => + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + +beforeEach(() => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - only return the config we need + getConfigSpy.mockResolvedValue({ + APP_MODE: "oss", + }); +}); + +describe("Content", () => { + it("should render the secrets settings screen", () => { + renderSecretsSettings(); + screen.getByTestId("secrets-settings-screen"); + }); + + it("should NOT render a button to connect with git if they havent already in oss", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + // @ts-expect-error - only return the config we need + getConfigSpy.mockResolvedValue({ + APP_MODE: "oss", + }); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: {}, + }); + + renderSecretsSettings(); + + expect(getConfigSpy).toHaveBeenCalled(); + await waitFor(() => expect(getSecretsSpy).toHaveBeenCalled()); + expect(screen.queryByTestId("connect-git-button")).not.toBeInTheDocument(); + }); + + it("should render a button to connect with git if they havent already in saas", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + const getSettingsSpy = vi.spyOn(OpenHands, "getSettings"); + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + // @ts-expect-error - only return the config we need + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + }); + getSettingsSpy.mockResolvedValue({ + ...MOCK_DEFAULT_USER_SETTINGS, + provider_tokens_set: {}, + }); + + renderSecretsSettings(); + + expect(getSecretsSpy).not.toHaveBeenCalled(); + await waitFor(() => + expect(screen.queryByTestId("add-secret-button")).not.toBeInTheDocument(), + ); + const button = await screen.findByTestId("connect-git-button"); + await userEvent.click(button); + + screen.getByTestId("git-settings-screen"); + }); + + it("should render a message if there are no existing secrets", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + getSecretsSpy.mockResolvedValue([]); + renderSecretsSettings(); + + await screen.findByTestId("no-secrets-message"); + }); + + it("should render existing secrets", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + getSecretsSpy.mockResolvedValue(MOCK_GET_SECRETS_RESPONSE); + renderSecretsSettings(); + + const secrets = await screen.findAllByTestId("secret-item"); + expect(secrets).toHaveLength(2); + expect(screen.queryByTestId("no-secrets-message")).not.toBeInTheDocument(); + }); +}); + +describe("Secret actions", () => { + it("should create a new secret", async () => { + const createSecretSpy = vi.spyOn(SecretsService, "createSecret"); + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + createSecretSpy.mockResolvedValue(true); + renderSecretsSettings(); + + // render form & hide items + expect(screen.queryByTestId("add-secret-form")).not.toBeInTheDocument(); + const button = await screen.findByTestId("add-secret-button"); + await userEvent.click(button); + + const secretForm = screen.getByTestId("add-secret-form"); + const secrets = screen.queryAllByTestId("secret-item"); + + expect(screen.queryByTestId("add-secret-button")).not.toBeInTheDocument(); + expect(secretForm).toBeInTheDocument(); + expect(secrets).toHaveLength(0); + + // enter details + const nameInput = within(secretForm).getByTestId("name-input"); + const valueInput = within(secretForm).getByTestId("value-input"); + const descriptionInput = + within(secretForm).getByTestId("description-input"); + + const submitButton = within(secretForm).getByTestId("submit-button"); + + vi.clearAllMocks(); // reset mocks to check for upcoming calls + + await userEvent.type(nameInput, "My_Custom_Secret"); + await userEvent.type(valueInput, "my-custom-secret-value"); + await userEvent.type(descriptionInput, "My custom secret description"); + + await userEvent.click(submitButton); + + // make POST request + expect(createSecretSpy).toHaveBeenCalledWith( + "My_Custom_Secret", + "my-custom-secret-value", + "My custom secret description", + ); + + // hide form & render items + expect(screen.queryByTestId("add-secret-form")).not.toBeInTheDocument(); + expect(getSecretsSpy).toHaveBeenCalled(); + }); + + it("should edit a secret", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + const updateSecretSpy = vi.spyOn(SecretsService, "updateSecret"); + getSecretsSpy.mockResolvedValue(MOCK_GET_SECRETS_RESPONSE); + updateSecretSpy.mockResolvedValue(true); + renderSecretsSettings(); + + // render edit button within a secret list item + const secrets = await screen.findAllByTestId("secret-item"); + const firstSecret = within(secrets[0]); + const editButton = firstSecret.getByTestId("edit-secret-button"); + + await userEvent.click(editButton); + + // render edit form + const editForm = screen.getByTestId("edit-secret-form"); + + expect(screen.queryByTestId("add-secret-button")).not.toBeInTheDocument(); + expect(editForm).toBeInTheDocument(); + expect(screen.queryAllByTestId("secret-item")).toHaveLength(0); + + // enter details + const nameInput = within(editForm).getByTestId("name-input"); + const descriptionInput = within(editForm).getByTestId("description-input"); + const submitButton = within(editForm).getByTestId("submit-button"); + + // should not show value input + const valueInput = within(editForm).queryByTestId("value-input"); + expect(valueInput).not.toBeInTheDocument(); + + expect(nameInput).toHaveValue("My_Secret_1"); + expect(descriptionInput).toHaveValue("My first secret"); + + await userEvent.clear(nameInput); + await userEvent.type(nameInput, "My_Edited_Secret"); + + await userEvent.clear(descriptionInput); + await userEvent.type(descriptionInput, "My edited secret description"); + + await userEvent.click(submitButton); + + // make POST request + expect(updateSecretSpy).toHaveBeenCalledWith( + "My_Secret_1", + "My_Edited_Secret", + "My edited secret description", + ); + + // hide form + expect(screen.queryByTestId("edit-secret-form")).not.toBeInTheDocument(); + + // optimistic update + const updatedSecrets = await screen.findAllByTestId("secret-item"); + expect(updatedSecrets).toHaveLength(2); + expect(updatedSecrets[0]).toHaveTextContent(/my_edited_secret/i); + }); + + it("should be able to cancel the create or edit form", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + getSecretsSpy.mockResolvedValue(MOCK_GET_SECRETS_RESPONSE); + renderSecretsSettings(); + + // render form & hide items + expect(screen.queryByTestId("add-secret-form")).not.toBeInTheDocument(); + const button = await screen.findByTestId("add-secret-button"); + await userEvent.click(button); + const secretForm = screen.getByTestId("add-secret-form"); + expect(secretForm).toBeInTheDocument(); + + // cancel button + const cancelButton = within(secretForm).getByTestId("cancel-button"); + await userEvent.click(cancelButton); + expect(screen.queryByTestId("add-secret-form")).not.toBeInTheDocument(); + expect(screen.queryByTestId("add-secret-button")).toBeInTheDocument(); + + // render edit button within a secret list item + const secrets = await screen.findAllByTestId("secret-item"); + const firstSecret = within(secrets[0]); + const editButton = firstSecret.getByTestId("edit-secret-button"); + await userEvent.click(editButton); + + // render edit form + const editForm = screen.getByTestId("edit-secret-form"); + expect(editForm).toBeInTheDocument(); + expect(screen.queryAllByTestId("secret-item")).toHaveLength(0); + + // cancel button + const cancelEditButton = within(editForm).getByTestId("cancel-button"); + await userEvent.click(cancelEditButton); + expect(screen.queryByTestId("edit-secret-form")).not.toBeInTheDocument(); + expect(screen.queryAllByTestId("secret-item")).toHaveLength(2); + }); + + it("should undo the optimistic update if the request fails", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + const updateSecretSpy = vi.spyOn(SecretsService, "updateSecret"); + getSecretsSpy.mockResolvedValue(MOCK_GET_SECRETS_RESPONSE); + updateSecretSpy.mockRejectedValue(new Error("Failed to update secret")); + renderSecretsSettings(); + + // render edit button within a secret list item + const secrets = await screen.findAllByTestId("secret-item"); + const firstSecret = within(secrets[0]); + const editButton = firstSecret.getByTestId("edit-secret-button"); + + await userEvent.click(editButton); + + // render edit form + const editForm = screen.getByTestId("edit-secret-form"); + + expect(editForm).toBeInTheDocument(); + expect(screen.queryAllByTestId("secret-item")).toHaveLength(0); + + // enter details + const nameInput = within(editForm).getByTestId("name-input"); + const submitButton = within(editForm).getByTestId("submit-button"); + + // should not show value input + const valueInput = within(editForm).queryByTestId("value-input"); + expect(valueInput).not.toBeInTheDocument(); + + await userEvent.clear(nameInput); + await userEvent.type(nameInput, "My_Edited_Secret"); + await userEvent.click(submitButton); + + // make POST request + expect(updateSecretSpy).toHaveBeenCalledWith( + "My_Secret_1", + "My_Edited_Secret", + "My first secret", + ); + + // hide form + expect(screen.queryByTestId("edit-secret-form")).not.toBeInTheDocument(); + + // no optimistic update + const updatedSecrets = await screen.findAllByTestId("secret-item"); + expect(updatedSecrets).toHaveLength(2); + expect(updatedSecrets[0]).not.toHaveTextContent(/my edited secret/i); + }); + + it("should remove the secret from the list after deletion", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + const deleteSecretSpy = vi.spyOn(SecretsService, "deleteSecret"); + getSecretsSpy.mockResolvedValue(MOCK_GET_SECRETS_RESPONSE); + deleteSecretSpy.mockResolvedValue(true); + renderSecretsSettings(); + + // render delete button within a secret list item + const secrets = await screen.findAllByTestId("secret-item"); + const secondSecret = within(secrets[1]); + const deleteButton = secondSecret.getByTestId("delete-secret-button"); + await userEvent.click(deleteButton); + + // confirmation modal + const confirmationModal = screen.getByTestId("confirmation-modal"); + const confirmButton = + within(confirmationModal).getByTestId("confirm-button"); + await userEvent.click(confirmButton); + + // make DELETE request + expect(deleteSecretSpy).toHaveBeenCalledWith("My_Secret_2"); + expect(screen.queryByTestId("confirmation-modal")).not.toBeInTheDocument(); + + // optimistic update + expect(screen.queryAllByTestId("secret-item")).toHaveLength(1); + expect(screen.queryByText("My_Secret_2")).not.toBeInTheDocument(); + }); + + it("should be able to cancel the delete confirmation modal", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + const deleteSecretSpy = vi.spyOn(SecretsService, "deleteSecret"); + getSecretsSpy.mockResolvedValue(MOCK_GET_SECRETS_RESPONSE); + deleteSecretSpy.mockResolvedValue(true); + renderSecretsSettings(); + + // render delete button within a secret list item + const secrets = await screen.findAllByTestId("secret-item"); + const secondSecret = within(secrets[1]); + const deleteButton = secondSecret.getByTestId("delete-secret-button"); + await userEvent.click(deleteButton); + + // confirmation modal + const confirmationModal = screen.getByTestId("confirmation-modal"); + const cancelButton = within(confirmationModal).getByTestId("cancel-button"); + await userEvent.click(cancelButton); + + // no DELETE request + expect(deleteSecretSpy).not.toHaveBeenCalled(); + expect(screen.queryByTestId("confirmation-modal")).not.toBeInTheDocument(); + expect(screen.queryAllByTestId("secret-item")).toHaveLength(2); + }); + + it("should revert the optimistic update if the request fails", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + const deleteSecretSpy = vi.spyOn(SecretsService, "deleteSecret"); + getSecretsSpy.mockResolvedValue(MOCK_GET_SECRETS_RESPONSE); + deleteSecretSpy.mockRejectedValue(new Error("Failed to delete secret")); + renderSecretsSettings(); + + // render delete button within a secret list item + const secrets = await screen.findAllByTestId("secret-item"); + const secondSecret = within(secrets[1]); + const deleteButton = secondSecret.getByTestId("delete-secret-button"); + await userEvent.click(deleteButton); + + // confirmation modal + const confirmationModal = screen.getByTestId("confirmation-modal"); + const confirmButton = + within(confirmationModal).getByTestId("confirm-button"); + await userEvent.click(confirmButton); + + // make DELETE request + expect(deleteSecretSpy).toHaveBeenCalledWith("My_Secret_2"); + expect(screen.queryByTestId("confirmation-modal")).not.toBeInTheDocument(); + + // optimistic update + expect(screen.queryAllByTestId("secret-item")).toHaveLength(2); + expect(screen.queryByText("My_Secret_2")).toBeInTheDocument(); + }); + + it("should hide the no items message when in form view", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + getSecretsSpy.mockResolvedValue([]); + renderSecretsSettings(); + + // render form & hide items + expect(screen.queryByTestId("no-secrets-message")).not.toBeInTheDocument(); + const button = await screen.findByTestId("add-secret-button"); + await userEvent.click(button); + + const secretForm = screen.getByTestId("add-secret-form"); + expect(secretForm).toBeInTheDocument(); + expect(screen.queryByTestId("no-secrets-message")).not.toBeInTheDocument(); + }); + + it("should not allow spaces in secret names", async () => { + const createSecretSpy = vi.spyOn(SecretsService, "createSecret"); + renderSecretsSettings(); + + // render form & hide items + expect(screen.queryByTestId("add-secret-form")).not.toBeInTheDocument(); + const button = await screen.findByTestId("add-secret-button"); + await userEvent.click(button); + + const secretForm = screen.getByTestId("add-secret-form"); + expect(secretForm).toBeInTheDocument(); + + // enter details + const nameInput = within(secretForm).getByTestId("name-input"); + const valueInput = within(secretForm).getByTestId("value-input"); + const submitButton = within(secretForm).getByTestId("submit-button"); + + await userEvent.type(nameInput, "My Custom Secret With Spaces"); + await userEvent.type(valueInput, "my-custom-secret-value"); + await userEvent.click(submitButton); + + // make POST request + expect(createSecretSpy).not.toHaveBeenCalled(); + + await userEvent.clear(nameInput); + await userEvent.type(nameInput, "MyCustomSecret"); + await userEvent.click(submitButton); + + expect(createSecretSpy).toHaveBeenCalledWith( + "MyCustomSecret", + "my-custom-secret-value", + undefined, + ); + }); + + it("should not allow existing secret names", async () => { + const createSecretSpy = vi.spyOn(SecretsService, "createSecret"); + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + getSecretsSpy.mockResolvedValue(MOCK_GET_SECRETS_RESPONSE.slice(0, 1)); + renderSecretsSettings(); + + // render form & hide items + expect(screen.queryByTestId("add-secret-form")).not.toBeInTheDocument(); + const button = await screen.findByTestId("add-secret-button"); + await userEvent.click(button); + + const secretForm = screen.getByTestId("add-secret-form"); + expect(secretForm).toBeInTheDocument(); + + // enter details + const nameInput = within(secretForm).getByTestId("name-input"); + const valueInput = within(secretForm).getByTestId("value-input"); + const submitButton = within(secretForm).getByTestId("submit-button"); + + await userEvent.type(nameInput, "My_Secret_1"); + await userEvent.type(valueInput, "my-custom-secret-value"); + await userEvent.click(submitButton); + + // make POST request + expect(createSecretSpy).not.toHaveBeenCalled(); + expect(screen.queryByText(/secret already exists/i)).toBeInTheDocument(); + + await userEvent.clear(nameInput); + await userEvent.type(nameInput, "My_Custom_Secret"); + + await userEvent.clear(valueInput); + await userEvent.type(valueInput, "my-custom-secret-value"); + + await userEvent.click(submitButton); + + expect(createSecretSpy).toHaveBeenCalledWith( + "My_Custom_Secret", + "my-custom-secret-value", + undefined, + ); + expect( + screen.queryByText("SECRETS$SECRET_VALUE_REQUIRED"), + ).not.toBeInTheDocument(); + }); + + it("should not submit whitespace secret names or values", async () => { + const createSecretSpy = vi.spyOn(SecretsService, "createSecret"); + renderSecretsSettings(); + + // render form & hide items + expect(screen.queryByTestId("add-secret-form")).not.toBeInTheDocument(); + const button = await screen.findByTestId("add-secret-button"); + await userEvent.click(button); + + const secretForm = screen.getByTestId("add-secret-form"); + expect(secretForm).toBeInTheDocument(); + + // enter details + const nameInput = within(secretForm).getByTestId("name-input"); + const valueInput = within(secretForm).getByTestId("value-input"); + const submitButton = within(secretForm).getByTestId("submit-button"); + + await userEvent.type(nameInput, " "); + await userEvent.type(valueInput, "my-custom-secret-value"); + await userEvent.click(submitButton); + + // make POST request + expect(createSecretSpy).not.toHaveBeenCalled(); + + await userEvent.clear(nameInput); + await userEvent.type(nameInput, "My_Custom_Secret"); + + await userEvent.clear(valueInput); + await userEvent.type(valueInput, " "); + + await userEvent.click(submitButton); + + expect(createSecretSpy).not.toHaveBeenCalled(); + expect( + screen.queryByText("SECRETS$SECRET_VALUE_REQUIRED"), + ).toBeInTheDocument(); + }); + + it("should not reset ipout values on an invalid submit", async () => { + const getSecretsSpy = vi.spyOn(SecretsService, "getSecrets"); + const createSecretSpy = vi.spyOn(SecretsService, "createSecret"); + getSecretsSpy.mockResolvedValue(MOCK_GET_SECRETS_RESPONSE); + + renderSecretsSettings(); + + // render form & hide items + expect(screen.queryByTestId("add-secret-form")).not.toBeInTheDocument(); + const button = await screen.findByTestId("add-secret-button"); + await userEvent.click(button); + + const secretForm = screen.getByTestId("add-secret-form"); + expect(secretForm).toBeInTheDocument(); + + // enter details + const nameInput = within(secretForm).getByTestId("name-input"); + const valueInput = within(secretForm).getByTestId("value-input"); + const submitButton = within(secretForm).getByTestId("submit-button"); + + await userEvent.type(nameInput, MOCK_GET_SECRETS_RESPONSE[0].name); + await userEvent.type(valueInput, "my-custom-secret-value"); + await userEvent.click(submitButton); + + // make POST request + expect(createSecretSpy).not.toHaveBeenCalled(); + expect(screen.queryByText(/secret already exists/i)).toBeInTheDocument(); + + expect(nameInput).toHaveValue(MOCK_GET_SECRETS_RESPONSE[0].name); + expect(valueInput).toHaveValue("my-custom-secret-value"); + }); +}); diff --git a/frontend/__tests__/routes/settings-with-payment.test.tsx b/frontend/__tests__/routes/settings-with-payment.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c1ebe316a9b164584b2f1f71f315e3fb0672acf1 --- /dev/null +++ b/frontend/__tests__/routes/settings-with-payment.test.tsx @@ -0,0 +1,137 @@ +import { screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { createRoutesStub } from "react-router"; +import { renderWithProviders } from "test-utils"; +import OpenHands from "#/api/open-hands"; +import SettingsScreen from "#/routes/settings"; +import { PaymentForm } from "#/components/features/payment/payment-form"; +import * as useSettingsModule from "#/hooks/query/use-settings"; + +// Mock the useSettings hook +vi.mock("#/hooks/query/use-settings", async () => { + const actual = await vi.importActual("#/hooks/query/use-settings"); + return { + ...actual, + useSettings: vi.fn().mockReturnValue({ + data: { + EMAIL_VERIFIED: true, // Mock email as verified to prevent redirection + }, + isLoading: false, + }), + }; +}); + +// Mock the i18next hook +vi.mock("react-i18next", async () => { + const actual = await vi.importActual("react-i18next"); + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + "SETTINGS$NAV_GIT": "Git", + "SETTINGS$NAV_APPLICATION": "Application", + "SETTINGS$NAV_CREDITS": "Credits", + "SETTINGS$NAV_API_KEYS": "API Keys", + "SETTINGS$NAV_LLM": "LLM", + "SETTINGS$NAV_USER": "User", + "SETTINGS$TITLE": "Settings" + }; + return translations[key] || key; + }, + i18n: { + changeLanguage: vi.fn(), + }, + }), + }; +}); + +describe("Settings Billing", () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + + const RoutesStub = createRoutesStub([ + { + Component: SettingsScreen, + path: "/settings", + children: [ + { + Component: () => , + path: "/settings/billing", + }, + { + Component: () =>
, + path: "/settings/git", + }, + { + Component: () =>
, + path: "/settings/user", + }, + ], + }, + ]); + + const renderSettingsScreen = () => + renderWithProviders(); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should not render the credits tab if OSS mode", async () => { + getConfigSpy.mockResolvedValue({ + APP_MODE: "oss", + GITHUB_CLIENT_ID: "123", + POSTHOG_CLIENT_KEY: "456", + FEATURE_FLAGS: { + ENABLE_BILLING: false, + HIDE_LLM_SETTINGS: false, + }, + }); + + renderSettingsScreen(); + + const navbar = await screen.findByTestId("settings-navbar"); + const credits = within(navbar).queryByText("Credits"); + expect(credits).not.toBeInTheDocument(); + }); + + it("should render the credits tab if SaaS mode and billing is enabled", async () => { + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + GITHUB_CLIENT_ID: "123", + POSTHOG_CLIENT_KEY: "456", + FEATURE_FLAGS: { + ENABLE_BILLING: true, + HIDE_LLM_SETTINGS: false, + }, + }); + + renderSettingsScreen(); + + const navbar = await screen.findByTestId("settings-navbar"); + within(navbar).getByText("Credits"); + }); + + it("should render the billing settings if clicking the credits item", async () => { + const user = userEvent.setup(); + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + GITHUB_CLIENT_ID: "123", + POSTHOG_CLIENT_KEY: "456", + FEATURE_FLAGS: { + ENABLE_BILLING: true, + HIDE_LLM_SETTINGS: false, + }, + }); + + renderSettingsScreen(); + + const navbar = await screen.findByTestId("settings-navbar"); + const credits = within(navbar).getByText("Credits"); + await user.click(credits); + + const billingSection = await screen.findByTestId("billing-settings"); + expect(billingSection).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/routes/settings.test.tsx b/frontend/__tests__/routes/settings.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..30ba6a13485123478ab0cc4eca18e66df6db0986 --- /dev/null +++ b/frontend/__tests__/routes/settings.test.tsx @@ -0,0 +1,163 @@ +import { render, screen, within } from "@testing-library/react"; +import { createRoutesStub } from "react-router"; +import { describe, expect, it, vi } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import SettingsScreen from "#/routes/settings"; +import OpenHands from "#/api/open-hands"; + +// Mock the i18next hook +vi.mock("react-i18next", async () => { + const actual = + await vi.importActual("react-i18next"); + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + SETTINGS$NAV_GIT: "Git", + SETTINGS$NAV_APPLICATION: "Application", + SETTINGS$NAV_CREDITS: "Credits", + SETTINGS$NAV_API_KEYS: "API Keys", + SETTINGS$NAV_LLM: "LLM", + SETTINGS$TITLE: "Settings", + }; + return translations[key] || key; + }, + i18n: { + changeLanguage: vi.fn(), + }, + }), + }; +}); + +describe("Settings Screen", () => { + const { handleLogoutMock } = vi.hoisted(() => ({ + handleLogoutMock: vi.fn(), + })); + vi.mock("#/hooks/use-app-logout", () => ({ + useAppLogout: vi.fn().mockReturnValue({ handleLogout: handleLogoutMock }), + })); + + const RouterStub = createRoutesStub([ + { + Component: SettingsScreen, + path: "/settings", + children: [ + { + Component: () =>
, + path: "/settings", + }, + { + Component: () =>
, + path: "/settings/git", + }, + { + Component: () =>
, + path: "/settings/app", + }, + { + Component: () =>
, + path: "/settings/credits", + }, + { + Component: () =>
, + path: "/settings/api-keys", + }, + ], + }, + ]); + + const renderSettingsScreen = (path = "/settings") => { + const queryClient = new QueryClient(); + return render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + }; + + it("should render the navbar", async () => { + const sectionsToInclude = ["llm", "git", "application", "secrets"]; + const sectionsToExclude = ["api keys", "credits"]; + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - only return app mode + getConfigSpy.mockResolvedValue({ + APP_MODE: "oss", + }); + + renderSettingsScreen(); + + const navbar = await screen.findByTestId("settings-navbar"); + sectionsToInclude.forEach((section) => { + const sectionElement = within(navbar).getByText(section, { + exact: false, // case insensitive + }); + expect(sectionElement).toBeInTheDocument(); + }); + sectionsToExclude.forEach((section) => { + const sectionElement = within(navbar).queryByText(section, { + exact: false, // case insensitive + }); + expect(sectionElement).not.toBeInTheDocument(); + }); + }); + + it("should render the saas navbar", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - only return app mode + getConfigSpy.mockResolvedValue({ + APP_MODE: "saas", + }); + const sectionsToInclude = [ + "git", + "application", + "credits", + "secrets", + "api keys", + ]; + const sectionsToExclude = ["llm"]; + + renderSettingsScreen(); + + const navbar = await screen.findByTestId("settings-navbar"); + sectionsToInclude.forEach((section) => { + const sectionElement = within(navbar).getByText(section, { + exact: false, // case insensitive + }); + expect(sectionElement).toBeInTheDocument(); + }); + sectionsToExclude.forEach((section) => { + const sectionElement = within(navbar).queryByText(section, { + exact: false, // case insensitive + }); + expect(sectionElement).not.toBeInTheDocument(); + }); + }); + + it("should not be able to access oss-restricted routes in oss", async () => { + const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); + // @ts-expect-error - only return app mode + getConfigSpy.mockResolvedValue({ + APP_MODE: "oss", + }); + + const { rerender } = renderSettingsScreen("/settings/credits"); + expect( + screen.queryByTestId("credits-settings-screen"), + ).not.toBeInTheDocument(); + + rerender(); + expect( + screen.queryByTestId("api-keys-settings-screen"), + ).not.toBeInTheDocument(); + rerender(); + expect( + screen.queryByTestId("billing-settings-screen"), + ).not.toBeInTheDocument(); + rerender(); + }); + + it.todo("should not be able to access saas-restricted routes in saas"); +}); diff --git a/frontend/__tests__/services/observations.test.tsx b/frontend/__tests__/services/observations.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4f43cfa16abbf81e8d42e3ce70f3622a4be277eb --- /dev/null +++ b/frontend/__tests__/services/observations.test.tsx @@ -0,0 +1,24 @@ +import { describe, it, vi, beforeEach, afterEach } from "vitest"; + +// Mock the store module +vi.mock("#/store", () => ({ + default: { + dispatch: vi.fn(), + }, +})); + +describe("handleObservationMessage", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it.todo("updates browser state when receiving a browse observation"); + + it.todo( + "updates browser state when receiving a browse_interactive observation", + ); +}); diff --git a/frontend/__tests__/utils/amount-is-valid.test.ts b/frontend/__tests__/utils/amount-is-valid.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a940f77e0ad589657609ed248ff8bacf60451fc --- /dev/null +++ b/frontend/__tests__/utils/amount-is-valid.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, test } from "vitest"; +import { amountIsValid } from "#/utils/amount-is-valid"; + +describe("amountIsValid", () => { + describe("fails", () => { + test("when the amount is negative", () => { + expect(amountIsValid("-5")).toBe(false); + expect(amountIsValid("-25")).toBe(false); + }); + + test("when the amount is zero", () => { + expect(amountIsValid("0")).toBe(false); + }); + + test("when an empty string is passed", () => { + expect(amountIsValid("")).toBe(false); + expect(amountIsValid(" ")).toBe(false); + }); + + test("when a non-numeric value is passed", () => { + expect(amountIsValid("abc")).toBe(false); + expect(amountIsValid("1abc")).toBe(false); + expect(amountIsValid("abc1")).toBe(false); + }); + + test("when an amount less than the minimum is passed", () => { + // test assumes the minimum is 10 + expect(amountIsValid("9")).toBe(false); + expect(amountIsValid("9.99")).toBe(false); + }); + + test("when an amount more than the maximum is passed", () => { + // test assumes the minimum is 25000 + expect(amountIsValid("25001")).toBe(false); + expect(amountIsValid("25000.01")).toBe(false); + }); + }); +}); diff --git a/frontend/__tests__/utils/check-hardcoded-strings.test.tsx b/frontend/__tests__/utils/check-hardcoded-strings.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..33995c0ddbcd51be8d653c0acdcf38353df91d20 --- /dev/null +++ b/frontend/__tests__/utils/check-hardcoded-strings.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from "@testing-library/react"; +import { test, expect, describe, vi } from "vitest"; +import { InteractiveChatBox } from "#/components/features/chat/interactive-chat-box"; +import { ChatInput } from "#/components/features/chat/chat-input"; + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +describe("Check for hardcoded English strings", () => { + test("InteractiveChatBox should not have hardcoded English strings", () => { + const { container } = render( + {}} onStop={() => {}} />, + ); + + // Get all text content + const text = container.textContent; + + // List of English strings that should be translated + const hardcodedStrings = [ + "What do you want to build?", + "Launch from Scratch", + "Read this" + ]; + + // Check each string + hardcodedStrings.forEach((str) => { + expect(text).not.toContain(str); + }); + }); + + test("ChatInput should use translation key for placeholder", () => { + render( {}} />); + screen.getByPlaceholderText("SUGGESTIONS$WHAT_TO_BUILD"); + }); +}); diff --git a/frontend/__tests__/utils/check-home-hardcoded-strings.test.tsx b/frontend/__tests__/utils/check-home-hardcoded-strings.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a9470fdd2cc5baca7ae46650f48b3f34c5c29bbf --- /dev/null +++ b/frontend/__tests__/utils/check-home-hardcoded-strings.test.tsx @@ -0,0 +1,42 @@ +import { render } from "@testing-library/react"; +import { test, expect, describe, vi } from "vitest"; +import { HomeHeader } from "#/components/features/home/home-header"; + +// Mock dependencies +vi.mock("#/hooks/mutation/use-create-conversation", () => ({ + useCreateConversation: () => ({ + mutate: vi.fn(), + isPending: false, + isSuccess: false, + }), +})); + +vi.mock("#/hooks/use-is-creating-conversation", () => ({ + useIsCreatingConversation: () => false, +})); + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +describe("Check for hardcoded English strings in Home components", () => { + test("HomeHeader should not have hardcoded English strings", () => { + const { container } = render(); + + // Get all text content + const text = container.textContent; + + // List of English strings that should be translated + const hardcodedStrings = [ + "Launch from Scratch", + "Read this", + ]; + + // Check each string + hardcodedStrings.forEach((str) => { + expect(text).not.toContain(str); + }); + }); +}); \ No newline at end of file diff --git a/frontend/__tests__/utils/convert-raw-providers-to-list.test.ts b/frontend/__tests__/utils/convert-raw-providers-to-list.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d2a756cbe749c29446678cacf75dea7159ecc047 --- /dev/null +++ b/frontend/__tests__/utils/convert-raw-providers-to-list.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; +import { Provider } from "#/types/settings"; +import { convertRawProvidersToList } from "#/utils/convert-raw-providers-to-list"; + +describe("convertRawProvidersToList", () => { + it("should convert raw provider tokens to a list of providers", () => { + const example1: Partial> | undefined = { + github: "test-token", + gitlab: "test-token", + }; + const example2: Partial> | undefined = { + github: "", + }; + const example3: Partial> | undefined = { + gitlab: null, + }; + + expect(convertRawProvidersToList(example1)).toEqual(["github", "gitlab"]); + expect(convertRawProvidersToList(example2)).toEqual(["github"]); + expect(convertRawProvidersToList(example3)).toEqual(["gitlab"]); + }); +}); diff --git a/frontend/__tests__/utils/error-handler.test.ts b/frontend/__tests__/utils/error-handler.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f1e91cae2bf1ce23ffe0e5c93a13bd7d3b286ca --- /dev/null +++ b/frontend/__tests__/utils/error-handler.test.ts @@ -0,0 +1,188 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import posthog from "posthog-js"; +import { + trackError, + showErrorToast, + showChatError, +} from "#/utils/error-handler"; +import * as Actions from "#/services/actions"; +import * as CustomToast from "#/utils/custom-toast-handlers"; + +vi.mock("posthog-js", () => ({ + default: { + captureException: vi.fn(), + }, +})); + +vi.mock("#/services/actions", () => ({ + handleStatusMessage: vi.fn(), +})); + +describe("Error Handler", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("trackError", () => { + it("should send error to PostHog with basic info", () => { + const error = { + message: "Test error", + source: "test", + }; + + trackError(error); + + expect(posthog.captureException).toHaveBeenCalledWith( + new Error("Test error"), + { + error_source: "test", + }, + ); + }); + + it("should include additional metadata in PostHog event", () => { + const error = { + message: "Test error", + source: "test", + metadata: { + extra: "info", + details: { foo: "bar" }, + }, + }; + + trackError(error); + + expect(posthog.captureException).toHaveBeenCalledWith( + new Error("Test error"), + { + error_source: "test", + extra: "info", + details: { foo: "bar" }, + }, + ); + }); + }); + + describe("showErrorToast", () => { + const errorToastSpy = vi.spyOn(CustomToast, "displayErrorToast"); + it("should log error and show toast", () => { + const error = { + message: "Toast error", + source: "toast-test", + }; + + showErrorToast(error); + + // Verify PostHog logging + expect(posthog.captureException).toHaveBeenCalledWith( + new Error("Toast error"), + { + error_source: "toast-test", + }, + ); + + // Verify toast was shown + expect(errorToastSpy).toHaveBeenCalled(); + }); + + it("should include metadata in PostHog event when showing toast", () => { + const error = { + message: "Toast error", + source: "toast-test", + metadata: { context: "testing" }, + }; + + showErrorToast(error); + + expect(posthog.captureException).toHaveBeenCalledWith( + new Error("Toast error"), + { + error_source: "toast-test", + context: "testing", + }, + ); + }); + + it("should log errors from different sources with appropriate metadata", () => { + // Test agent status error + showErrorToast({ + message: "Agent error", + source: "agent-status", + metadata: { id: "error.agent" }, + }); + + expect(posthog.captureException).toHaveBeenCalledWith( + new Error("Agent error"), + { + error_source: "agent-status", + id: "error.agent", + }, + ); + + showErrorToast({ + message: "Server error", + source: "server", + metadata: { error_code: 500, details: "Internal error" }, + }); + + expect(posthog.captureException).toHaveBeenCalledWith( + new Error("Server error"), + { + error_source: "server", + error_code: 500, + details: "Internal error", + }, + ); + }); + + it("should log feedback submission errors with conversation context", () => { + const error = new Error("Feedback submission failed"); + showErrorToast({ + message: error.message, + source: "feedback", + metadata: { conversationId: "123", error }, + }); + + expect(posthog.captureException).toHaveBeenCalledWith( + new Error("Feedback submission failed"), + { + error_source: "feedback", + conversationId: "123", + error, + }, + ); + }); + }); + + describe("showChatError", () => { + it("should log error and show chat error message", () => { + const error = { + message: "Chat error", + source: "chat-test", + msgId: "123", + }; + + showChatError(error); + + // Verify PostHog logging + expect(posthog.captureException).toHaveBeenCalledWith( + new Error("Chat error"), + { + error_source: "chat-test", + }, + ); + + // Verify error message was shown in chat + expect(Actions.handleStatusMessage).toHaveBeenCalledWith({ + type: "error", + message: "Chat error", + id: "123", + status_update: true, + }); + }); + }); +}); diff --git a/frontend/__tests__/utils/extract-model-and-provider.test.ts b/frontend/__tests__/utils/extract-model-and-provider.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..83d09cc1807063bec5f194aa9be0b1e8ed863f89 --- /dev/null +++ b/frontend/__tests__/utils/extract-model-and-provider.test.ts @@ -0,0 +1,98 @@ +import { describe, it, expect } from "vitest"; +import { extractModelAndProvider } from "../../src/utils/extract-model-and-provider"; + +describe("extractModelAndProvider", () => { + it("should work", () => { + expect(extractModelAndProvider("azure/ada")).toEqual({ + provider: "azure", + model: "ada", + separator: "/", + }); + + expect( + extractModelAndProvider("azure/standard/1024-x-1024/dall-e-2"), + ).toEqual({ + provider: "azure", + model: "standard/1024-x-1024/dall-e-2", + separator: "/", + }); + + expect(extractModelAndProvider("vertex_ai_beta/chat-bison")).toEqual({ + provider: "vertex_ai_beta", + model: "chat-bison", + separator: "/", + }); + + expect(extractModelAndProvider("cohere.command-r-v1:0")).toEqual({ + provider: "cohere", + model: "command-r-v1:0", + separator: ".", + }); + + expect( + extractModelAndProvider( + "cloudflare/@cf/mistral/mistral-7b-instruct-v0.1", + ), + ).toEqual({ + provider: "cloudflare", + model: "@cf/mistral/mistral-7b-instruct-v0.1", + separator: "/", + }); + + expect(extractModelAndProvider("together-ai-21.1b-41b")).toEqual({ + provider: "", + model: "together-ai-21.1b-41b", + separator: "", + }); + }); + + it("should add provider for popular models", () => { + expect(extractModelAndProvider("gpt-4o-mini")).toEqual({ + provider: "openai", + model: "gpt-4o-mini", + separator: "/", + }); + + expect(extractModelAndProvider("gpt-4o")).toEqual({ + provider: "openai", + model: "gpt-4o", + separator: "/", + }); + + expect(extractModelAndProvider("claude-3-5-sonnet-20240620")).toEqual({ + provider: "anthropic", + model: "claude-3-5-sonnet-20240620", + separator: "/", + }); + + expect(extractModelAndProvider("claude-3-7-sonnet-20250219")).toEqual({ + provider: "anthropic", + model: "claude-3-7-sonnet-20250219", + separator: "/", + }); + + expect(extractModelAndProvider("claude-sonnet-4-20250514")).toEqual({ + provider: "anthropic", + model: "claude-sonnet-4-20250514", + separator: "/", + }); + + expect(extractModelAndProvider("claude-opus-4-20250514")).toEqual({ + provider: "anthropic", + model: "claude-opus-4-20250514", + separator: "/", + }); + + expect(extractModelAndProvider("claude-3-haiku-20240307")).toEqual({ + provider: "anthropic", + model: "claude-3-haiku-20240307", + separator: "/", + }); + + expect(extractModelAndProvider("claude-2.1")).toEqual({ + provider: "anthropic", + model: "claude-2.1", + separator: "/", + }); + }); +}); diff --git a/frontend/__tests__/utils/extract-next-page-from-link.test.ts b/frontend/__tests__/utils/extract-next-page-from-link.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..a7541f95a0adfdcbd2c050c246a02832c7725eb7 --- /dev/null +++ b/frontend/__tests__/utils/extract-next-page-from-link.test.ts @@ -0,0 +1,13 @@ +import { expect, test } from "vitest"; +import { extractNextPageFromLink } from "#/utils/extract-next-page-from-link"; + +test("extractNextPageFromLink", () => { + const link = `; rel="prev", ; rel="next", ; rel="last", ; rel="first"`; + expect(extractNextPageFromLink(link)).toBe(4); + + const noNextLink = `; rel="prev", ; rel="first"`; + expect(extractNextPageFromLink(noNextLink)).toBeNull(); + + const extra = `; rel="next", ; rel="last"`; + expect(extractNextPageFromLink(extra)).toBe(2); +}); diff --git a/frontend/__tests__/utils/format-ms.test.ts b/frontend/__tests__/utils/format-ms.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..76b517b6284b14e442d37259c81e2e725a1faf10 --- /dev/null +++ b/frontend/__tests__/utils/format-ms.test.ts @@ -0,0 +1,9 @@ +import { test, expect } from "vitest"; +import { formatMs } from "../../src/utils/format-ms"; + +test("formatMs", () => { + expect(formatMs(1000)).toBe("00:01"); + expect(formatMs(1000 * 60)).toBe("01:00"); + expect(formatMs(1000 * 60 * 2.5)).toBe("02:30"); + expect(formatMs(1000 * 60 * 12)).toBe("12:00"); +}); diff --git a/frontend/__tests__/utils/format-time-delta.test.ts b/frontend/__tests__/utils/format-time-delta.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..496643a1187050c6908febfe57d3321a685c30e9 --- /dev/null +++ b/frontend/__tests__/utils/format-time-delta.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; +import { formatTimeDelta } from "#/utils/format-time-delta"; + +describe("formatTimeDelta", () => { + beforeEach(() => { + const now = new Date("2024-01-01T00:00:00Z"); + vi.useFakeTimers({ now }); + }); + + it("formats the yearly time correctly", () => { + const oneYearAgo = new Date("2023-01-01T00:00:00Z"); + expect(formatTimeDelta(oneYearAgo)).toBe("1y"); + + const twoYearsAgo = new Date("2022-01-01T00:00:00Z"); + expect(formatTimeDelta(twoYearsAgo)).toBe("2y"); + + const threeYearsAgo = new Date("2021-01-01T00:00:00Z"); + expect(formatTimeDelta(threeYearsAgo)).toBe("3y"); + }); + + it("formats the monthly time correctly", () => { + const oneMonthAgo = new Date("2023-12-01T00:00:00Z"); + expect(formatTimeDelta(oneMonthAgo)).toBe("1mo"); + + const twoMonthsAgo = new Date("2023-11-01T00:00:00Z"); + expect(formatTimeDelta(twoMonthsAgo)).toBe("2mo"); + + const threeMonthsAgo = new Date("2023-10-01T00:00:00Z"); + expect(formatTimeDelta(threeMonthsAgo)).toBe("3mo"); + }); + + it("formats the daily time correctly", () => { + const oneDayAgo = new Date("2023-12-31T00:00:00Z"); + expect(formatTimeDelta(oneDayAgo)).toBe("1d"); + + const twoDaysAgo = new Date("2023-12-30T00:00:00Z"); + expect(formatTimeDelta(twoDaysAgo)).toBe("2d"); + + const threeDaysAgo = new Date("2023-12-29T00:00:00Z"); + expect(formatTimeDelta(threeDaysAgo)).toBe("3d"); + }); + + it("formats the hourly time correctly", () => { + const oneHourAgo = new Date("2023-12-31T23:00:00Z"); + expect(formatTimeDelta(oneHourAgo)).toBe("1h"); + + const twoHoursAgo = new Date("2023-12-31T22:00:00Z"); + expect(formatTimeDelta(twoHoursAgo)).toBe("2h"); + + const threeHoursAgo = new Date("2023-12-31T21:00:00Z"); + expect(formatTimeDelta(threeHoursAgo)).toBe("3h"); + }); + + it("formats the minute time correctly", () => { + const oneMinuteAgo = new Date("2023-12-31T23:59:00Z"); + expect(formatTimeDelta(oneMinuteAgo)).toBe("1m"); + + const twoMinutesAgo = new Date("2023-12-31T23:58:00Z"); + expect(formatTimeDelta(twoMinutesAgo)).toBe("2m"); + + const threeMinutesAgo = new Date("2023-12-31T23:57:00Z"); + expect(formatTimeDelta(threeMinutesAgo)).toBe("3m"); + }); + + it("formats the second time correctly", () => { + const oneSecondAgo = new Date("2023-12-31T23:59:59Z"); + expect(formatTimeDelta(oneSecondAgo)).toBe("1s"); + + const twoSecondsAgo = new Date("2023-12-31T23:59:58Z"); + expect(formatTimeDelta(twoSecondsAgo)).toBe("2s"); + + const threeSecondsAgo = new Date("2023-12-31T23:59:57Z"); + expect(formatTimeDelta(threeSecondsAgo)).toBe("3s"); + }); +}); diff --git a/frontend/__tests__/utils/group-suggested-tasks.test.ts b/frontend/__tests__/utils/group-suggested-tasks.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2848120bbf4ac0e48b782595e82194767466ce9 --- /dev/null +++ b/frontend/__tests__/utils/group-suggested-tasks.test.ts @@ -0,0 +1,101 @@ +import { expect, test } from "vitest"; +import { + SuggestedTask, + SuggestedTaskGroup, +} from "#/components/features/home/tasks/task.types"; +import { groupSuggestedTasks } from "#/utils/group-suggested-tasks"; + +const rawTasks: SuggestedTask[] = [ + { + issue_number: 1, + repo: "repo1", + title: "Task 1", + task_type: "MERGE_CONFLICTS", + git_provider: "github", + }, + { + issue_number: 2, + repo: "repo1", + title: "Task 2", + task_type: "FAILING_CHECKS", + git_provider: "github", + }, + { + issue_number: 3, + repo: "repo2", + title: "Task 3", + task_type: "UNRESOLVED_COMMENTS", + git_provider: "github", + }, + { + issue_number: 4, + repo: "repo2", + title: "Task 4", + task_type: "OPEN_ISSUE", + git_provider: "github", + }, + { + issue_number: 5, + repo: "repo3", + title: "Task 5", + task_type: "FAILING_CHECKS", + git_provider: "github", + }, +]; + +const groupedTasks: SuggestedTaskGroup[] = [ + { + title: "repo1", + tasks: [ + { + issue_number: 1, + repo: "repo1", + title: "Task 1", + task_type: "MERGE_CONFLICTS", + git_provider: "github", + }, + { + issue_number: 2, + repo: "repo1", + title: "Task 2", + task_type: "FAILING_CHECKS", + git_provider: "github", + }, + ], + }, + { + title: "repo2", + tasks: [ + { + issue_number: 3, + repo: "repo2", + title: "Task 3", + task_type: "UNRESOLVED_COMMENTS", + git_provider: "github", + }, + { + issue_number: 4, + repo: "repo2", + title: "Task 4", + task_type: "OPEN_ISSUE", + git_provider: "github", + }, + ], + }, + { + title: "repo3", + tasks: [ + { + issue_number: 5, + repo: "repo3", + title: "Task 5", + task_type: "FAILING_CHECKS", + git_provider: "github", + }, + ], + }, +]; + +test("groupSuggestedTasks", () => { + expect(groupSuggestedTasks(rawTasks)).toEqual(groupedTasks); +}); diff --git a/frontend/__tests__/utils/handle-capture-consent.test.ts b/frontend/__tests__/utils/handle-capture-consent.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b337424a7ae4ed83da0a5a79aef6d1ffd830726 --- /dev/null +++ b/frontend/__tests__/utils/handle-capture-consent.test.ts @@ -0,0 +1,44 @@ +import posthog from "posthog-js"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { handleCaptureConsent } from "#/utils/handle-capture-consent"; + +describe("handleCaptureConsent", () => { + const optInSpy = vi.spyOn(posthog, "opt_in_capturing"); + const optOutSpy = vi.spyOn(posthog, "opt_out_capturing"); + const hasOptedInSpy = vi.spyOn(posthog, "has_opted_in_capturing"); + const hasOptedOutSpy = vi.spyOn(posthog, "has_opted_out_capturing"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should opt out of of capturing", () => { + handleCaptureConsent(false); + + expect(optOutSpy).toHaveBeenCalled(); + expect(optInSpy).not.toHaveBeenCalled(); + }); + + it("should opt in to capturing if the user consents", () => { + handleCaptureConsent(true); + + expect(optInSpy).toHaveBeenCalled(); + expect(optOutSpy).not.toHaveBeenCalled(); + }); + + it("should not opt in to capturing if the user is already opted in", () => { + hasOptedInSpy.mockReturnValueOnce(true); + handleCaptureConsent(true); + + expect(optInSpy).not.toHaveBeenCalled(); + expect(optOutSpy).not.toHaveBeenCalled(); + }); + + it("should not opt out of capturing if the user is already opted out", () => { + hasOptedOutSpy.mockReturnValueOnce(true); + handleCaptureConsent(false); + + expect(optOutSpy).not.toHaveBeenCalled(); + expect(optInSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/utils/has-advanced-settings-set.test.ts b/frontend/__tests__/utils/has-advanced-settings-set.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..49eba25b509fbe2f33a2dab67b9eadf904b6b4fd --- /dev/null +++ b/frontend/__tests__/utils/has-advanced-settings-set.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it, test } from "vitest"; +import { hasAdvancedSettingsSet } from "#/utils/has-advanced-settings-set"; +import { DEFAULT_SETTINGS } from "#/services/settings"; + +describe("hasAdvancedSettingsSet", () => { + it("should return false by default", () => { + expect(hasAdvancedSettingsSet(DEFAULT_SETTINGS)).toBe(false); + }); + + it("should return false if an empty object", () => { + expect(hasAdvancedSettingsSet({})).toBe(false); + }); + + describe("should be true if", () => { + test("LLM_BASE_URL is set", () => { + expect( + hasAdvancedSettingsSet({ + ...DEFAULT_SETTINGS, + LLM_BASE_URL: "test", + }), + ).toBe(true); + }); + + test("AGENT is not default value", () => { + expect( + hasAdvancedSettingsSet({ + ...DEFAULT_SETTINGS, + AGENT: "test", + }), + ).toBe(true); + }); + + test("CONFIRMATION_MODE is true", () => { + expect( + hasAdvancedSettingsSet({ + ...DEFAULT_SETTINGS, + CONFIRMATION_MODE: true, + }), + ).toBe(true); + }); + + test("SECURITY_ANALYZER is set", () => { + expect( + hasAdvancedSettingsSet({ + ...DEFAULT_SETTINGS, + SECURITY_ANALYZER: "test", + }), + ).toBe(true); + }); + }); +}); diff --git a/frontend/__tests__/utils/i18n-test-utils.tsx b/frontend/__tests__/utils/i18n-test-utils.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7153540a1953bbb87c24c32353bb3ee3227d335b --- /dev/null +++ b/frontend/__tests__/utils/i18n-test-utils.tsx @@ -0,0 +1,29 @@ +import { ReactNode } from "react"; +import { I18nextProvider } from "react-i18next"; + +const mockI18n = { + language: "ja", + t: (key: string) => { + const translations: Record = { + "SUGGESTIONS$TODO_APP": "ToDoリストアプリを開発する", + "LANDING$BUILD_APP_BUTTON": "プルリクエストを表示するアプリを開発する", + "SUGGESTIONS$HACKER_NEWS": "Hacker Newsのトップ記事を表示するbashスクリプトを作成する", + "LANDING$TITLE": "一緒に開発を始めましょう!", + "OPEN_IN_VSCODE": "VS Codeで開く", + "INCREASE_TEST_COVERAGE": "テストカバレッジを向上", + "AUTO_MERGE_PRS": "PRを自動マージ", + "FIX_README": "READMEを修正", + "CLEAN_DEPENDENCIES": "依存関係を整理" + }; + return translations[key] || key; + }, + exists: () => true, + changeLanguage: () => new Promise(() => {}), + use: () => mockI18n, +}; + +export function I18nTestProvider({ children }: { children: ReactNode }) { + return ( + {children} + ); +} diff --git a/frontend/__tests__/utils/is-custom-model.test.ts b/frontend/__tests__/utils/is-custom-model.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..1da5667920ae87b216154f51d9fb675eb5befad4 --- /dev/null +++ b/frontend/__tests__/utils/is-custom-model.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; +import { isCustomModel } from "#/utils/is-custom-model"; + +describe("isCustomModel", () => { + const models = ["anthropic/claude-3.5", "openai/gpt-3.5-turbo", "gpt-4o"]; + + it("should return false by default", () => { + expect(isCustomModel(models, "")).toBe(false); + }); + + it("should be true if it is a custom model", () => { + expect(isCustomModel(models, "some/model")).toBe(true); + }); + + it("should be false if it is not a custom model", () => { + expect(isCustomModel(models, "anthropic/claude-3.5")).toBe(false); + expect(isCustomModel(models, "openai/gpt-3.5-turbo")).toBe(false); + expect(isCustomModel(models, "openai/gpt-4o")).toBe(false); + }); +}); diff --git a/frontend/__tests__/utils/is-number.test.ts b/frontend/__tests__/utils/is-number.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9291080357d0ba7a9e2ff24ba606a66bcde36b64 --- /dev/null +++ b/frontend/__tests__/utils/is-number.test.ts @@ -0,0 +1,9 @@ +import { test, expect } from "vitest"; +import { isNumber } from "../../src/utils/is-number"; + +test("isNumber", () => { + expect(isNumber(1)).toBe(true); + expect(isNumber(0)).toBe(true); + expect(isNumber("3")).toBe(true); + expect(isNumber("0")).toBe(true); +}); diff --git a/frontend/__tests__/utils/map-provider.test.ts b/frontend/__tests__/utils/map-provider.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f5753a8256aeeaa32019e88a1463cb995c6d8d7 --- /dev/null +++ b/frontend/__tests__/utils/map-provider.test.ts @@ -0,0 +1,27 @@ +import { test, expect } from "vitest"; +import { mapProvider } from "../../src/utils/map-provider"; + +test("mapProvider", () => { + expect(mapProvider("azure")).toBe("Azure"); + expect(mapProvider("azure_ai")).toBe("Azure AI Studio"); + expect(mapProvider("vertex_ai")).toBe("VertexAI"); + expect(mapProvider("palm")).toBe("PaLM"); + expect(mapProvider("gemini")).toBe("Gemini"); + expect(mapProvider("anthropic")).toBe("Anthropic"); + expect(mapProvider("sagemaker")).toBe("AWS SageMaker"); + expect(mapProvider("bedrock")).toBe("AWS Bedrock"); + expect(mapProvider("mistral")).toBe("Mistral AI"); + expect(mapProvider("anyscale")).toBe("Anyscale"); + expect(mapProvider("databricks")).toBe("Databricks"); + expect(mapProvider("ollama")).toBe("Ollama"); + expect(mapProvider("perlexity")).toBe("Perplexity AI"); + expect(mapProvider("friendliai")).toBe("FriendliAI"); + expect(mapProvider("groq")).toBe("Groq"); + expect(mapProvider("fireworks_ai")).toBe("Fireworks AI"); + expect(mapProvider("cloudflare")).toBe("Cloudflare Workers AI"); + expect(mapProvider("deepinfra")).toBe("DeepInfra"); + expect(mapProvider("ai21")).toBe("AI21"); + expect(mapProvider("replicate")).toBe("Replicate"); + expect(mapProvider("voyage")).toBe("Voyage AI"); + expect(mapProvider("openrouter")).toBe("OpenRouter"); +}); diff --git a/frontend/__tests__/utils/organize-models-and-providers.test.ts b/frontend/__tests__/utils/organize-models-and-providers.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..2abb732e7b5af244633d20b085c28e04a6318016 --- /dev/null +++ b/frontend/__tests__/utils/organize-models-and-providers.test.ts @@ -0,0 +1,65 @@ +import { expect, test } from "vitest"; +import { organizeModelsAndProviders } from "../../src/utils/organize-models-and-providers"; + +test("organizeModelsAndProviders", () => { + const models = [ + "azure/ada", + "azure/gpt-35-turbo", + "azure/gpt-3-turbo", + "azure/standard/1024-x-1024/dall-e-2", + "vertex_ai_beta/chat-bison", + "vertex_ai_beta/chat-bison-32k", + "sagemaker/meta-textgeneration-llama-2-13b", + "cohere.command-r-v1:0", + "cloudflare/@cf/mistral/mistral-7b-instruct-v0.1", + "gpt-4o", + "together-ai-21.1b-41b", + "gpt-4o-mini", + "anthropic/claude-3-5-sonnet-20241022", + "claude-3-haiku-20240307", + "claude-2", + "claude-2.1", + "anthropic.unsafe-claude-2.1", + ]; + + const object = organizeModelsAndProviders(models); + + expect(object).toEqual({ + azure: { + separator: "/", + models: [ + "ada", + "gpt-35-turbo", + "gpt-3-turbo", + "standard/1024-x-1024/dall-e-2", + ], + }, + vertex_ai_beta: { + separator: "/", + models: ["chat-bison", "chat-bison-32k"], + }, + sagemaker: { separator: "/", models: ["meta-textgeneration-llama-2-13b"] }, + cohere: { separator: ".", models: ["command-r-v1:0"] }, + cloudflare: { + separator: "/", + models: ["@cf/mistral/mistral-7b-instruct-v0.1"], + }, + openai: { + separator: "/", + models: ["gpt-4o", "gpt-4o-mini"], + }, + anthropic: { + separator: "/", + models: [ + "claude-3-5-sonnet-20241022", + "claude-3-haiku-20240307", + "claude-2", + "claude-2.1", + ], + }, + other: { + separator: "", + models: ["together-ai-21.1b-41b"], + }, + }); +}); diff --git a/frontend/__tests__/utils/parse-github-url.test.ts b/frontend/__tests__/utils/parse-github-url.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..95dbee363ef2a6db20d4cbdfffa87279d2962429 --- /dev/null +++ b/frontend/__tests__/utils/parse-github-url.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from "vitest"; +import { parseGithubUrl } from "../../src/utils/parse-github-url"; + +test("parseGithubUrl", () => { + expect( + parseGithubUrl("https://github.com/alexreardon/tiny-invariant"), + ).toEqual(["alexreardon", "tiny-invariant"]); + + expect(parseGithubUrl("https://github.com/All-Hands-AI/OpenHands")).toEqual([ + "All-Hands-AI", + "OpenHands", + ]); + + expect(parseGithubUrl("https://github.com/All-Hands-AI/")).toEqual([ + "All-Hands-AI", + "", + ]); + + expect(parseGithubUrl("https://github.com/")).toEqual([]); +}); diff --git a/frontend/__tests__/utils/parse-terminal-output.test.ts b/frontend/__tests__/utils/parse-terminal-output.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..681757b2220af2ba5a0d9a22dccdbb77b80daaf1 --- /dev/null +++ b/frontend/__tests__/utils/parse-terminal-output.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from "vitest"; +import { parseTerminalOutput } from "../../src/utils/parse-terminal-output"; + +describe("parseTerminalOutput", () => { + it("should parse the command, env, and symbol", () => { + const raw = + "web_scraper.py\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@659478cb008c:/workspace $ "; + + const parsed = parseTerminalOutput(raw); + expect(parsed).toBe("web_scraper.py"); + }); + + it("should parse even if there is no output", () => { + const raw = + "[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@659478cb008c:/workspace $ "; + + const parsed = parseTerminalOutput(raw); + expect(parsed).toBe(""); + }); + + it("should return the string if it doesn't match the regex", () => { + const raw = "web_scraper.py"; + const parsed = parseTerminalOutput(raw); + expect(parsed).toBe("web_scraper.py"); + }); +}); diff --git a/frontend/__tests__/utils/utils.test.ts b/frontend/__tests__/utils/utils.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..7dc9df025419189a957fce73ebf0e3cbecbf27b9 --- /dev/null +++ b/frontend/__tests__/utils/utils.test.ts @@ -0,0 +1,25 @@ +import { test, expect } from "vitest"; +import { + formatTimestamp, + getExtension, + removeApiKey, +} from "../../src/utils/utils"; + +test("removeApiKey", () => { + const data = [{ args: { LLM_API_KEY: "key", LANGUAGE: "en" } }]; + expect(removeApiKey(data)).toEqual([{ args: { LANGUAGE: "en" } }]); +}); + +test("getExtension", () => { + expect(getExtension("main.go")).toBe("go"); + expect(getExtension("get-extension.test.ts")).toBe("ts"); + expect(getExtension("directory")).toBe(""); +}); + +test("formatTimestamp", () => { + const morningDate = new Date("2021-10-10T10:10:10.000").toISOString(); + expect(formatTimestamp(morningDate)).toBe("10/10/2021, 10:10:10"); + + const eveningDate = new Date("2021-10-10T22:10:10.000").toISOString(); + expect(formatTimestamp(eveningDate)).toBe("10/10/2021, 22:10:10"); +}); diff --git a/frontend/global.d.ts b/frontend/global.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3ad7483eaa39289b074bab320da1f7daa356eb5 --- /dev/null +++ b/frontend/global.d.ts @@ -0,0 +1,4 @@ +interface Window { + __APP_MODE__?: "saas" | "oss"; + __GITHUB_CLIENT_ID__?: string | null; +} diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000000000000000000000000000000000000..3e25fa66d771101a34f7498d8f05b064053d59f1 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + OpenHands + + + +
+ + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..ab017e446fd2197cb657e2095ce9f4482f2c4f2b --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,18488 @@ +{ + "name": "openhands-frontend", + "version": "0.41.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "openhands-frontend", + "version": "0.41.0", + "dependencies": { + "@heroui/react": "2.7.8", + "@microlink/react-json-view": "^1.26.2", + "@monaco-editor/react": "^4.7.0-rc.0", + "@react-router/node": "^7.6.1", + "@react-router/serve": "^7.6.1", + "@react-types/shared": "^3.29.1", + "@reduxjs/toolkit": "^2.8.2", + "@stripe/react-stripe-js": "^3.7.0", + "@stripe/stripe-js": "^7.3.0", + "@tanstack/react-query": "^5.77.2", + "@vitejs/plugin-react": "^4.4.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.4.0", + "axios": "^1.9.0", + "clsx": "^2.1.1", + "eslint-config-airbnb-typescript": "^18.0.0", + "framer-motion": "^12.14.0", + "i18next": "^25.2.1", + "i18next-browser-languagedetector": "^8.1.0", + "i18next-http-backend": "^3.0.2", + "isbot": "^5.1.28", + "jose": "^6.0.11", + "lucide-react": "^0.511.0", + "monaco-editor": "^0.52.2", + "posthog-js": "^1.245.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-highlight": "^0.15.0", + "react-hot-toast": "^2.5.1", + "react-i18next": "^15.5.1", + "react-icons": "^5.5.0", + "react-markdown": "^10.1.0", + "react-redux": "^9.2.0", + "react-router": "^7.6.1", + "react-syntax-highlighter": "^15.6.1", + "react-textarea-autosize": "^8.5.9", + "remark-gfm": "^4.0.1", + "sirv-cli": "^3.0.1", + "socket.io-client": "^4.8.1", + "tailwind-merge": "^3.3.0", + "vite": "^6.3.5", + "web-vitals": "^5.0.1", + "ws": "^8.18.2" + }, + "devDependencies": { + "@babel/parser": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.0", + "@mswjs/socket.io-binding": "^0.1.1", + "@playwright/test": "^1.52.0", + "@react-router/dev": "^7.6.1", + "@tailwindcss/typography": "^0.5.16", + "@tanstack/eslint-plugin-query": "^5.78.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.1", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.15.21", + "@types/react": "^19.1.5", + "@types/react-dom": "^19.1.5", + "@types/react-highlight": "^0.12.8", + "@types/react-syntax-highlighter": "^15.5.13", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@vitest/coverage-v8": "^3.1.4", + "autoprefixer": "^10.4.21", + "cross-env": "^7.0.3", + "eslint": "^8.57.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^18.0.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-unused-imports": "^4.1.4", + "husky": "^9.1.7", + "jsdom": "^26.1.0", + "lint-staged": "^16.0.0", + "msw": "^2.6.6", + "postcss": "^8.5.2", + "prettier": "^3.5.3", + "stripe": "^18.1.1", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "vite-plugin-svgr": "^4.2.0", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.0.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz", + "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", + "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz", + "integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.3", + "@babel/parser": "^7.27.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.3", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", + "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.3", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz", + "integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz", + "integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.3.tgz", + "integrity": "sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz", + "integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cookie": "^0.7.2" + } + }, + "node_modules/@bundled-es-modules/cookie/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "statuses": "^2.0.1" + } + }, + "node_modules/@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + } + }, + "node_modules/@bundled-es-modules/tough-cookie/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@bundled-es-modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz", + "integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.1", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz", + "integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/icu-skeleton-parser": "1.8.14", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz", + "integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz", + "integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@heroui/accordion": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@heroui/accordion/-/accordion-2.2.16.tgz", + "integrity": "sha512-YjWTUx4hSRD6cV9hZxF9bEn/MStgPV2v4CCz66DdtGrxE527PANjk2oq4rkCS0ZnqfCw4ruFUuRS0HpnY5k/5w==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/divider": "2.2.13", + "@heroui/dom-animation": "2.1.8", + "@heroui/framer-utils": "2.1.15", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-aria-accordion": "2.2.11", + "@react-aria/button": "3.13.0", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "@react-stately/tree": "3.8.9", + "@react-types/accordion": "3.0.0-alpha.26", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/accordion/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/alert": { + "version": "2.2.19", + "resolved": "https://registry.npmjs.org/@heroui/alert/-/alert-2.2.19.tgz", + "integrity": "sha512-Qou8JxQy75rce9L4RA50zAJbEDVaQOHV1McWm0gJPBpKzDeN9sFSSyRy7UgKSDTQc5s3TVfQMZYrq2txY2HQlQ==", + "license": "MIT", + "dependencies": { + "@heroui/button": "2.2.19", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@react-aria/utils": "3.28.2", + "@react-stately/utils": "3.10.6" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/aria-utils": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@heroui/aria-utils/-/aria-utils-2.2.16.tgz", + "integrity": "sha512-d4MuOSpn95RgxJloLc9mDfo162Z0/YtErr6CgXbIWNQfZL8AfBxUMCuhYS1KCUwBV6usitMf2XIW4zGEGtMkXA==", + "license": "MIT", + "dependencies": { + "@heroui/react-rsc-utils": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/system": "2.4.15", + "@react-aria/utils": "3.28.2", + "@react-stately/collections": "3.12.3", + "@react-stately/overlays": "3.6.15", + "@react-types/overlays": "3.8.14", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/aria-utils/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/autocomplete": { + "version": "2.3.20", + "resolved": "https://registry.npmjs.org/@heroui/autocomplete/-/autocomplete-2.3.20.tgz", + "integrity": "sha512-QlZ3AjGt/hXW0sJxK+x7XU+PNLam5HU1wYuaC9Q6TXabvh/2BcFncvCi0l3OShh5GYuiP2BTP3Ynb3pdd1+DAA==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/button": "2.2.19", + "@heroui/form": "2.1.18", + "@heroui/input": "2.4.19", + "@heroui/listbox": "2.3.18", + "@heroui/popover": "2.3.19", + "@heroui/react-utils": "2.1.10", + "@heroui/scroll-shadow": "2.3.13", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/spinner": "2.2.16", + "@heroui/use-aria-button": "2.2.13", + "@heroui/use-safe-layout-effect": "2.1.7", + "@react-aria/combobox": "3.12.2", + "@react-aria/focus": "3.20.2", + "@react-aria/i18n": "3.12.8", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "@react-aria/visually-hidden": "3.8.22", + "@react-stately/combobox": "3.10.4", + "@react-types/combobox": "3.13.4", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/autocomplete/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/avatar": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@heroui/avatar/-/avatar-2.2.15.tgz", + "integrity": "sha512-8o5+PciEH/uCqlaJDmc06sHAYLFoTenQfpIzWTh4Z/ced3u3xT74ZKf/+q4DnezaA5uJA+nc3+LF3wWli6/o/g==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-image": "2.1.9", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/badge": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/badge/-/badge-2.2.12.tgz", + "integrity": "sha512-JVvsmgHzvNDHMSW0/51LaikjTIxm59dU7Bvgp6bN5MuWgMvdhVcrrBskyy98uk7B4i8yYEfzfKBOPU3apZGAug==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/breadcrumbs": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@heroui/breadcrumbs/-/breadcrumbs-2.2.15.tgz", + "integrity": "sha512-86/WSR21CRPiurb6OLiEPpQeZNPGWyNdpen3tpwT/4nC1U/9nOAw+Gt8uB8dO9Xze6wR4d1yqAIuSPGgVL7OPA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@react-aria/breadcrumbs": "3.5.23", + "@react-aria/focus": "3.20.2", + "@react-aria/utils": "3.28.2", + "@react-types/breadcrumbs": "3.7.12", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/breadcrumbs/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/button": { + "version": "2.2.19", + "resolved": "https://registry.npmjs.org/@heroui/button/-/button-2.2.19.tgz", + "integrity": "sha512-9vpTYyGzadcLa2Toy1K0Aoa6hno2kH5S+Sc9Ruliim0MdoqXtdsD2i1Ywpgf2xp6bD6bTHsfb1uuspAYJRdxJA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/ripple": "2.2.14", + "@heroui/shared-utils": "2.1.9", + "@heroui/spinner": "2.2.16", + "@heroui/use-aria-button": "2.2.13", + "@react-aria/button": "3.13.0", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "@react-types/button": "3.12.0", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/button/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/calendar": { + "version": "2.2.19", + "resolved": "https://registry.npmjs.org/@heroui/calendar/-/calendar-2.2.19.tgz", + "integrity": "sha512-q9bSjSWa/NlJGnb/7n18bXXZbn2/oPAAnpgieLorUh/0XeW9mGgasa6OA3VC6q+GfA6MHNpdhe4MBN9jc5fwlA==", + "license": "MIT", + "dependencies": { + "@heroui/button": "2.2.19", + "@heroui/dom-animation": "2.1.8", + "@heroui/framer-utils": "2.1.15", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-aria-button": "2.2.13", + "@internationalized/date": "3.8.0", + "@react-aria/calendar": "3.8.0", + "@react-aria/focus": "3.20.2", + "@react-aria/i18n": "3.12.8", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "@react-aria/visually-hidden": "3.8.22", + "@react-stately/calendar": "3.8.0", + "@react-stately/utils": "3.10.6", + "@react-types/button": "3.12.0", + "@react-types/calendar": "3.7.0", + "@react-types/shared": "3.29.0", + "@types/lodash.debounce": "^4.0.7", + "scroll-into-view-if-needed": "3.0.10" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/calendar/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/card": { + "version": "2.2.18", + "resolved": "https://registry.npmjs.org/@heroui/card/-/card-2.2.18.tgz", + "integrity": "sha512-fgvOfmeEz3ri9ft2P45ycclsC0AeJxQnZey2JuF6G8ou7IOKYEO0najuW6PDs9h50tY5TLWl1fU5S7WMUzjEpw==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/ripple": "2.2.14", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-aria-button": "2.2.13", + "@react-aria/button": "3.13.0", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/card/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/checkbox": { + "version": "2.3.18", + "resolved": "https://registry.npmjs.org/@heroui/checkbox/-/checkbox-2.3.18.tgz", + "integrity": "sha512-hTVCN2A4+lPt+JmsBzgVS5YNEyEu8NvdUDJ01NA3DNpIjAxV6RmiVl6HnRxECHt7xCzqk5inun/W38NOos756g==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.18", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-callback-ref": "2.1.7", + "@heroui/use-safe-layout-effect": "2.1.7", + "@react-aria/checkbox": "3.15.4", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "@react-aria/visually-hidden": "3.8.22", + "@react-stately/checkbox": "3.6.13", + "@react-stately/toggle": "3.8.3", + "@react-types/checkbox": "3.9.3", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.3", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/checkbox/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/chip": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@heroui/chip/-/chip-2.2.15.tgz", + "integrity": "sha512-c5omTTjpkydwN9L9LcA3ibxRfWhxYMjJkJwkDZkCY5T+FKItv0iRo2PX6+k13UOhQe+G03zwCJxgkQvAUZHU0A==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "@react-types/checkbox": "3.9.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/code": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@heroui/code/-/code-2.2.14.tgz", + "integrity": "sha512-mFHgSXi1XzP+B59QBpWk/6NOc2A8wToPWDEaPOJO65nNUZ0S7FHhZTUNVPxHRn3wy8RPclPUpLFicGggT5inQw==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/system-rsc": "2.3.13" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/date-input": { + "version": "2.3.18", + "resolved": "https://registry.npmjs.org/@heroui/date-input/-/date-input-2.3.18.tgz", + "integrity": "sha512-G9RTgvu0L3DyqrmlqeaFN+xWcZaLgfAVJi8hh3tyP1K6VtlSi1+NfioeJ47HZabFy7balmQBioCK/Qg2iZqf3Q==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.18", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@internationalized/date": "3.8.0", + "@react-aria/datepicker": "3.14.2", + "@react-aria/i18n": "3.12.8", + "@react-aria/utils": "3.28.2", + "@react-stately/datepicker": "3.14.0", + "@react-types/datepicker": "3.12.0", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.9", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/date-input/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/date-picker": { + "version": "2.3.19", + "resolved": "https://registry.npmjs.org/@heroui/date-picker/-/date-picker-2.3.19.tgz", + "integrity": "sha512-v+mpWcO9XIknLbVFSdIgVQhFHjinO/ysmsh1lWtXN70GLcc959ip493dWaENx/9VdNFqt4XiB/0d16BBDghsrw==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/button": "2.2.19", + "@heroui/calendar": "2.2.19", + "@heroui/date-input": "2.3.18", + "@heroui/form": "2.1.18", + "@heroui/popover": "2.3.19", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@internationalized/date": "3.8.0", + "@react-aria/datepicker": "3.14.2", + "@react-aria/i18n": "3.12.8", + "@react-aria/utils": "3.28.2", + "@react-stately/datepicker": "3.14.0", + "@react-stately/overlays": "3.6.15", + "@react-stately/utils": "3.10.6", + "@react-types/datepicker": "3.12.0", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.9", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/date-picker/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/divider": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/@heroui/divider/-/divider-2.2.13.tgz", + "integrity": "sha512-axoFh+eAdlmEPgu8RAbfEhRop/Bld/VuhCr7r7N/CBhCHTOz0H8ja/keYQAZr8Nnxn5s/Lx0NwMuPT0SZEi23A==", + "license": "MIT", + "dependencies": { + "@heroui/react-rsc-utils": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/system-rsc": "2.3.13", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/divider/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/dom-animation": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@heroui/dom-animation/-/dom-animation-2.1.8.tgz", + "integrity": "sha512-88PwAmkF+lodZisF1OB3CuwNs+1sTB5eAfGvXZGUCO/rNZvGIL4KxmxuDM2odb0MJYklMU39+aqCEg/U+x2tEA==", + "license": "MIT", + "peerDependencies": { + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1" + } + }, + "node_modules/@heroui/drawer": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@heroui/drawer/-/drawer-2.2.16.tgz", + "integrity": "sha512-g3kquuqvHF15KY2jStlEEE9cUpnxRyvrasyQQtVLjxfJwBPosl9Yp6vS6z+sYBhvgTZc5r66LpEOREumrSxvSQ==", + "license": "MIT", + "dependencies": { + "@heroui/framer-utils": "2.1.15", + "@heroui/modal": "2.2.16", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/dropdown": { + "version": "2.3.19", + "resolved": "https://registry.npmjs.org/@heroui/dropdown/-/dropdown-2.3.19.tgz", + "integrity": "sha512-smSnLNM9s1f/l+0Z1J6nn6wcvfLCz/GRLRJLw09qnoUb+9pGWxsA1uZNRopB7oZOrD5VafGHR6bXy5iV7mfReQ==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/menu": "2.2.18", + "@heroui/popover": "2.3.19", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@react-aria/focus": "3.20.2", + "@react-aria/menu": "3.18.2", + "@react-aria/utils": "3.28.2", + "@react-stately/menu": "3.9.3", + "@react-types/menu": "3.10.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/form": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@heroui/form/-/form-2.1.18.tgz", + "integrity": "sha512-cBlliX6uiIUHDMF/bf5u1JhaA1+ddJHfmDPGfdl25c0mSWbyJqWJ0f1N2KZfrOf8kBkXdWCDXgK5sr3h9n22xg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/system": "2.4.15", + "@heroui/theme": "2.4.15", + "@react-aria/utils": "3.28.2", + "@react-stately/form": "3.1.3", + "@react-types/form": "3.7.11", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@heroui/form/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/framer-utils": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@heroui/framer-utils/-/framer-utils-2.1.15.tgz", + "integrity": "sha512-SH6hIz0OrhJrx284Gnp1EpCnNL8Dkt3XFmtHogNsE9ggRwMLy1xKIqyVni0V4ZmUe1DNGKAW9ywHV3onp3pFfg==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.9", + "@heroui/system": "2.4.15", + "@heroui/use-measure": "2.1.7" + }, + "peerDependencies": { + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/image": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/image/-/image-2.2.12.tgz", + "integrity": "sha512-WJmdp86ibq0XJzi64a/n/c5xEDHNvBD5VU7hinyasRLQBa159Hw4Mab7sueFVBX6ELWj/MIyRb9GK8wz9n3Pwg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-image": "2.1.9" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/input": { + "version": "2.4.19", + "resolved": "https://registry.npmjs.org/@heroui/input/-/input-2.4.19.tgz", + "integrity": "sha512-hKkdMNYHoHpwW/wxdbcWW6M9lLzSTQkSC1FwSfmx3Ug399bX9aLbwNj6R7oOpqqilZjbdnVOWDTzQ9B1vu1Sqg==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.18", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-safe-layout-effect": "2.1.7", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/textfield": "3.17.2", + "@react-aria/utils": "3.28.2", + "@react-stately/utils": "3.10.6", + "@react-types/shared": "3.29.0", + "@react-types/textfield": "3.12.1", + "react-textarea-autosize": "^8.5.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.12", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/input-otp": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@heroui/input-otp/-/input-otp-2.1.18.tgz", + "integrity": "sha512-JMqQf4j0tx+OkDyN69KKMIHbxsFvbF5ov5WkwDP1h77eD0Wr89FzJz7nUMIpruEwrFtuCT/QnmQuHcBEIwC0sA==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.18", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@react-aria/focus": "3.20.2", + "@react-aria/form": "3.0.15", + "@react-aria/utils": "3.28.2", + "@react-stately/form": "3.1.3", + "@react-stately/utils": "3.10.6", + "@react-types/textfield": "3.12.1", + "input-otp": "1.4.1" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.13", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@heroui/input/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/kbd": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@heroui/kbd/-/kbd-2.2.15.tgz", + "integrity": "sha512-cx/PIi+3Hb3EKF3OdejClgOFXLWiUFM7iLZ4rmbFzY9PJiIkPUQCLLhljjCpJmcbRfYHY/UXwUszsIhm/aoSvA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/system-rsc": "2.3.13", + "@react-aria/utils": "3.28.2" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/link": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@heroui/link/-/link-2.2.16.tgz", + "integrity": "sha512-k/F4bQptv0HAWu9MbePpIa8XvPPDXYxw603HlyFK7qqeGM9daXK3ZgEhE/sd2cnatxa9pYuaiimh7ZxMEsCvtQ==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-aria-link": "2.2.14", + "@react-aria/focus": "3.20.2", + "@react-aria/link": "3.8.0", + "@react-aria/utils": "3.28.2", + "@react-types/link": "3.6.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/listbox": { + "version": "2.3.18", + "resolved": "https://registry.npmjs.org/@heroui/listbox/-/listbox-2.3.18.tgz", + "integrity": "sha512-CQRp6Lsyq5iqSSTitFmwoBjWff8nEyqLW17yA3UDZNQfUX2g8F/tPfFmDiRQzKykpQTOZ1MhT0SietQHfyv8Eg==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/divider": "2.2.13", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-is-mobile": "2.2.9", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/listbox": "3.14.3", + "@react-aria/utils": "3.28.2", + "@react-stately/list": "3.12.1", + "@react-types/menu": "3.10.0", + "@react-types/shared": "3.29.0", + "@tanstack/react-virtual": "3.11.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/listbox/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/menu": { + "version": "2.2.18", + "resolved": "https://registry.npmjs.org/@heroui/menu/-/menu-2.2.18.tgz", + "integrity": "sha512-O1i1yuiv34jW8nv8rHGIfwXC+1V6ZxTUu3nblR/TjgB8qClf4WV1LtmkDPweQ5rcR3w8+LKGTGMXh/4tfxDD8A==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/divider": "2.2.13", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-is-mobile": "2.2.9", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/menu": "3.18.2", + "@react-aria/utils": "3.28.2", + "@react-stately/menu": "3.9.3", + "@react-stately/tree": "3.8.9", + "@react-types/menu": "3.10.0", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/menu/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/modal": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@heroui/modal/-/modal-2.2.16.tgz", + "integrity": "sha512-H4Apuvs6ohZTweRe2atRtJQp1nI9HSZVMKRgdn8kIqYBP4rZBu3dTPvnqRKzI4cpdQrsAr4J3xJ36Yt/sn0Rpw==", + "license": "MIT", + "dependencies": { + "@heroui/dom-animation": "2.1.8", + "@heroui/framer-utils": "2.1.15", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-aria-button": "2.2.13", + "@heroui/use-aria-modal-overlay": "2.2.12", + "@heroui/use-disclosure": "2.2.11", + "@heroui/use-draggable": "2.1.11", + "@react-aria/dialog": "3.5.24", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/overlays": "3.27.0", + "@react-aria/utils": "3.28.2", + "@react-stately/overlays": "3.6.15", + "@react-types/overlays": "3.8.14" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/navbar": { + "version": "2.2.17", + "resolved": "https://registry.npmjs.org/@heroui/navbar/-/navbar-2.2.17.tgz", + "integrity": "sha512-+8iH0arqTSAs64Pnx8yI5nrfpK/kmkD7vR+WlfEy9rwJ1cuWjesvOttadfG2TnoQO3FJ+Wm4GEvwgmgeXMmpnQ==", + "license": "MIT", + "dependencies": { + "@heroui/dom-animation": "2.1.8", + "@heroui/framer-utils": "2.1.15", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-scroll-position": "2.1.7", + "@react-aria/button": "3.13.0", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/overlays": "3.27.0", + "@react-aria/utils": "3.28.2", + "@react-stately/toggle": "3.8.3", + "@react-stately/utils": "3.10.6" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/number-input": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@heroui/number-input/-/number-input-2.0.9.tgz", + "integrity": "sha512-UKmr8V6gNjdFDFgdhvU9fVWrChwXdNkg3H2Jh1NiBAjvnAOiZZ8rJsSRxR8gR4bM81Hdo+V6NKATlMYcOWSXZA==", + "license": "MIT", + "dependencies": { + "@heroui/button": "2.2.19", + "@heroui/form": "2.1.18", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-safe-layout-effect": "2.1.7", + "@react-aria/focus": "3.20.2", + "@react-aria/i18n": "3.12.8", + "@react-aria/interactions": "3.25.0", + "@react-aria/numberfield": "3.11.13", + "@react-aria/utils": "3.28.2", + "@react-stately/numberfield": "3.9.11", + "@react-stately/utils": "3.10.6", + "@react-types/button": "3.12.0", + "@react-types/numberfield": "3.8.10", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.9", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/number-input/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/pagination": { + "version": "2.2.17", + "resolved": "https://registry.npmjs.org/@heroui/pagination/-/pagination-2.2.17.tgz", + "integrity": "sha512-UL1MRw2zNsvc5gTe9m3ZN12Fiq5NlDKvpXus0EL471QnsZSOc6SsmjVHrLTWs+AmWiEfiQ2ICKjrlR/JYF+ZGQ==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-intersection-observer": "2.2.11", + "@heroui/use-pagination": "2.2.12", + "@react-aria/focus": "3.20.2", + "@react-aria/i18n": "3.12.8", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "scroll-into-view-if-needed": "3.0.10" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/popover": { + "version": "2.3.19", + "resolved": "https://registry.npmjs.org/@heroui/popover/-/popover-2.3.19.tgz", + "integrity": "sha512-I6IkBea61BKyvJ3NVBSvXMb4HgQwXXBmtag6L+AsGakJ5X+10v5oAuFHYEhhnZ9Uwh3ckqw0dMzQzzh5P4VE6A==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/button": "2.2.19", + "@heroui/dom-animation": "2.1.8", + "@heroui/framer-utils": "2.1.15", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-aria-button": "2.2.13", + "@heroui/use-safe-layout-effect": "2.1.7", + "@react-aria/dialog": "3.5.24", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/overlays": "3.27.0", + "@react-aria/utils": "3.28.2", + "@react-stately/overlays": "3.6.15", + "@react-types/button": "3.12.0", + "@react-types/overlays": "3.8.14" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/progress": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@heroui/progress/-/progress-2.2.15.tgz", + "integrity": "sha512-nWZCw4EAuBZ7hrmmgC1bSzhg2wJScQeop4erUIM59UHFUSYhVbW7GG6Q5wBn+lEkEi/Sn3Tm7OOFDlUa7bRdXA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-is-mounted": "2.1.7", + "@react-aria/i18n": "3.12.8", + "@react-aria/progress": "3.4.22", + "@react-aria/utils": "3.28.2", + "@react-types/progress": "3.5.11" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/radio": { + "version": "2.3.18", + "resolved": "https://registry.npmjs.org/@heroui/radio/-/radio-2.3.18.tgz", + "integrity": "sha512-QCyEFa6eBhkqouf+h4Tk5Xnm6kn8slEwD5c8ol/Se2P/iDWxnXhy4mKiHZq6qhPIgxYqnY4cwNeaR/iz+42XXw==", + "license": "MIT", + "dependencies": { + "@heroui/form": "2.1.18", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/radio": "3.11.2", + "@react-aria/utils": "3.28.2", + "@react-aria/visually-hidden": "3.8.22", + "@react-stately/radio": "3.10.12", + "@react-types/radio": "3.8.8", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.3", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/radio/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/react": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@heroui/react/-/react-2.7.8.tgz", + "integrity": "sha512-ywtnEcf64mj5Q6nwjv2atG+Y6BVSlm8G+QZkGKBeQnMZfKi2oBAsNNEMoC9XW4wWp/nB4t5pJPp28oJaTZcofA==", + "license": "MIT", + "dependencies": { + "@heroui/accordion": "2.2.16", + "@heroui/alert": "2.2.19", + "@heroui/autocomplete": "2.3.20", + "@heroui/avatar": "2.2.15", + "@heroui/badge": "2.2.12", + "@heroui/breadcrumbs": "2.2.15", + "@heroui/button": "2.2.19", + "@heroui/calendar": "2.2.19", + "@heroui/card": "2.2.18", + "@heroui/checkbox": "2.3.18", + "@heroui/chip": "2.2.15", + "@heroui/code": "2.2.14", + "@heroui/date-input": "2.3.18", + "@heroui/date-picker": "2.3.19", + "@heroui/divider": "2.2.13", + "@heroui/drawer": "2.2.16", + "@heroui/dropdown": "2.3.19", + "@heroui/form": "2.1.18", + "@heroui/framer-utils": "2.1.15", + "@heroui/image": "2.2.12", + "@heroui/input": "2.4.19", + "@heroui/input-otp": "2.1.18", + "@heroui/kbd": "2.2.15", + "@heroui/link": "2.2.16", + "@heroui/listbox": "2.3.18", + "@heroui/menu": "2.2.18", + "@heroui/modal": "2.2.16", + "@heroui/navbar": "2.2.17", + "@heroui/number-input": "2.0.9", + "@heroui/pagination": "2.2.17", + "@heroui/popover": "2.3.19", + "@heroui/progress": "2.2.15", + "@heroui/radio": "2.3.18", + "@heroui/ripple": "2.2.14", + "@heroui/scroll-shadow": "2.3.13", + "@heroui/select": "2.4.19", + "@heroui/skeleton": "2.2.12", + "@heroui/slider": "2.4.16", + "@heroui/snippet": "2.2.20", + "@heroui/spacer": "2.2.14", + "@heroui/spinner": "2.2.16", + "@heroui/switch": "2.2.17", + "@heroui/system": "2.4.15", + "@heroui/table": "2.2.18", + "@heroui/tabs": "2.2.16", + "@heroui/theme": "2.4.15", + "@heroui/toast": "2.0.9", + "@heroui/tooltip": "2.2.16", + "@heroui/user": "2.2.15", + "@react-aria/visually-hidden": "3.8.22" + }, + "peerDependencies": { + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/react-rsc-utils": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/react-rsc-utils/-/react-rsc-utils-2.1.7.tgz", + "integrity": "sha512-NYKKOLs+KHA8v0+PxkkhVXxTD0WNvC4QMlMjUVshzpWhjnOHIrtXjAtqO6XezWmiKNKY76FAjnMZP+Be5+j5uw==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/react-utils": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@heroui/react-utils/-/react-utils-2.1.10.tgz", + "integrity": "sha512-Wj3BSQnNFrDzDnN44vYEwTScMpdbylbZwO8UxIY02AoQCBD5QW7Wf0r2FVlrsrjPjMOVeogwlVvCBYvZz5hHnQ==", + "license": "MIT", + "dependencies": { + "@heroui/react-rsc-utils": "2.1.7", + "@heroui/shared-utils": "2.1.9" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/ripple": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@heroui/ripple/-/ripple-2.2.14.tgz", + "integrity": "sha512-ZwPFoNJgLRwiY1TQc5FJenSsJZrdOYP80VWRcmXn0uvMjiv674Rjviji1QEpONA0gvvSxYnptB/ere1oi15NUg==", + "license": "MIT", + "dependencies": { + "@heroui/dom-animation": "2.1.8", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/scroll-shadow": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@heroui/scroll-shadow/-/scroll-shadow-2.3.13.tgz", + "integrity": "sha512-RfYfVewf6UR4vr4sIPI2NaNoyK5lLgJwdWNGufE1Km7INelXf3BVdVKLW/Qlq/cES+B4TV3gq5Nto8aen3R1Sg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-data-scroll-overflow": "2.2.10" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/select": { + "version": "2.4.19", + "resolved": "https://registry.npmjs.org/@heroui/select/-/select-2.4.19.tgz", + "integrity": "sha512-lEeDyA9QvcQSblHbrqmHhY1V1LJjMmVa6gQ5hxHRfaFcxhQFmp+ORinSsuRVojITRoQ9bz/hTNX+tnrSqyHr/w==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/form": "2.1.18", + "@heroui/listbox": "2.3.18", + "@heroui/popover": "2.3.19", + "@heroui/react-utils": "2.1.10", + "@heroui/scroll-shadow": "2.3.13", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/spinner": "2.2.16", + "@heroui/use-aria-button": "2.2.13", + "@heroui/use-aria-multiselect": "2.4.12", + "@heroui/use-safe-layout-effect": "2.1.7", + "@react-aria/focus": "3.20.2", + "@react-aria/form": "3.0.15", + "@react-aria/interactions": "3.25.0", + "@react-aria/overlays": "3.27.0", + "@react-aria/utils": "3.28.2", + "@react-aria/visually-hidden": "3.8.22", + "@react-types/shared": "3.29.0", + "@tanstack/react-virtual": "3.11.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.12", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/select/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/shared-icons": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/shared-icons/-/shared-icons-2.1.7.tgz", + "integrity": "sha512-uJ8MKVR6tWWhFqTjyzeuJabLVMvwENX2aCWLAAPcJedKcPEEmxgE8y3CbY7vRRPEJENXOoeAgmcVWdVgPYeRIw==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/shared-utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@heroui/shared-utils/-/shared-utils-2.1.9.tgz", + "integrity": "sha512-mM/Ep914cYMbw3T/b6+6loYhuNfzDaph76mzw/oIS05gw1Dhp9luCziSiIhqDGgzYck2d74oWTZlahyCsxf47w==", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/@heroui/skeleton": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/skeleton/-/skeleton-2.2.12.tgz", + "integrity": "sha512-HlRKMVLgMAfe9wX7BPhTN84Xu+SdJWCtmxLzBWUZVNpLZdjnu2lLOcbkzwo+84tSjsxbLP4tqBW8hdJnxTQVVA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/slider": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@heroui/slider/-/slider-2.4.16.tgz", + "integrity": "sha512-KbPtHoOcVYZRXWG+LZgZe8mMO067F9eOiYcrKs5sO5nkEx0MgRlM5VeagC32R86P/fT+hHdL8fUGmatRDZ267Q==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/tooltip": "2.2.16", + "@react-aria/focus": "3.20.2", + "@react-aria/i18n": "3.12.8", + "@react-aria/interactions": "3.25.0", + "@react-aria/slider": "3.7.18", + "@react-aria/utils": "3.28.2", + "@react-aria/visually-hidden": "3.8.22", + "@react-stately/slider": "3.6.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/snippet": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/@heroui/snippet/-/snippet-2.2.20.tgz", + "integrity": "sha512-/vSPkL8V6aQK/i0Ipr8bIBZifTF0g0Kq7DAq0QPfKZNqVWE0rhZyndvn1XU+KevGHybN9WDh6LsYqglxlDIm/A==", + "license": "MIT", + "dependencies": { + "@heroui/button": "2.2.19", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/tooltip": "2.2.16", + "@heroui/use-clipboard": "2.1.8", + "@react-aria/focus": "3.20.2", + "@react-aria/utils": "3.28.2" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/spacer": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@heroui/spacer/-/spacer-2.2.14.tgz", + "integrity": "sha512-wiksYhtYH+RIhoMJPdQtWFltw9TF5QKqOujecNmRUORlOsPGAPEHUnzVTV8D7qpk4nJaDB/BNdlN40NaPvWEjg==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/system-rsc": "2.3.13" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/spinner": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@heroui/spinner/-/spinner-2.2.16.tgz", + "integrity": "sha512-yC6OWWiDuXK+NiGpUcAnrmDyBwvWHYw5nzVkUPZ+3TpDpVg9pM7xKSSgf7Xk2C1jgI2diAXbEnCRMVJ87s/zfQ==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/system": "2.4.15", + "@heroui/system-rsc": "2.3.13" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/switch": { + "version": "2.2.17", + "resolved": "https://registry.npmjs.org/@heroui/switch/-/switch-2.2.17.tgz", + "integrity": "sha512-32JfQpT39WDkcWDAHvxhrQ0hXSjLEBEZSWvbRZKrdmB9SPGq6F8fs+wAA5OINoa+MJEBZVHjLcNoRFl5uXQtog==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-safe-layout-effect": "2.1.7", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/switch": "3.7.2", + "@react-aria/utils": "3.28.2", + "@react-aria/visually-hidden": "3.8.22", + "@react-stately/toggle": "3.8.3", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.3", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/switch/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/system": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@heroui/system/-/system-2.4.15.tgz", + "integrity": "sha512-+QUHscs2RTk5yOFEQXNlQa478P7PTD02ZGP/RTNCviR4E9ZTUifdjfsKA7D4L79S7L8Mkvbz5E2Ruz2ZF0R/IA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/system-rsc": "2.3.13", + "@internationalized/date": "3.8.0", + "@react-aria/i18n": "3.12.8", + "@react-aria/overlays": "3.27.0", + "@react-aria/utils": "3.28.2", + "@react-stately/utils": "3.10.6", + "@react-types/datepicker": "3.12.0" + }, + "peerDependencies": { + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/system-rsc": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@heroui/system-rsc/-/system-rsc-2.3.13.tgz", + "integrity": "sha512-zLBrDKCoM4o039t3JdfYZAOlHmn4RzI6gxU+Tw8XJIfvUzpGSvR2seY2XJBbKOonmTpILlnw16ZvHF+KG+nN0w==", + "license": "MIT", + "dependencies": { + "@react-types/shared": "3.29.0", + "clsx": "^1.2.1" + }, + "peerDependencies": { + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/system-rsc/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/system-rsc/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@heroui/table": { + "version": "2.2.18", + "resolved": "https://registry.npmjs.org/@heroui/table/-/table-2.2.18.tgz", + "integrity": "sha512-4KmYMUq77bc6kY4zr5AZoFm4xzML8zAA505q3kUwlKcpiSbbYxebRUJtbf/UE3FY2NlXAiIuUiL7cvKPmOuVnw==", + "license": "MIT", + "dependencies": { + "@heroui/checkbox": "2.3.18", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/spacer": "2.2.14", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/table": "3.17.2", + "@react-aria/utils": "3.28.2", + "@react-aria/visually-hidden": "3.8.22", + "@react-stately/table": "3.14.1", + "@react-stately/virtualizer": "4.3.2", + "@react-types/grid": "3.3.1", + "@react-types/table": "3.12.0", + "@tanstack/react-virtual": "3.11.3" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/tabs": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@heroui/tabs/-/tabs-2.2.16.tgz", + "integrity": "sha512-TlbgjuF+5SI11p1NlbFuvZ6EkNNHJY2UWRR5UV1EOZawllgpg+mP0BMDID8/r7p/1VcV6abAi+3/0kQRRArh8A==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/framer-utils": "2.1.15", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-is-mounted": "2.1.7", + "@heroui/use-update-effect": "2.1.7", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/tabs": "3.10.2", + "@react-aria/utils": "3.28.2", + "@react-stately/tabs": "3.8.1", + "@react-types/shared": "3.29.0", + "@react-types/tabs": "3.3.14", + "scroll-into-view-if-needed": "3.0.10" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/tabs/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/theme": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@heroui/theme/-/theme-2.4.15.tgz", + "integrity": "sha512-cP1N9Rqj5wzsKLpEzNdJQRjX2g9AuCZbRNaIuIGnztqmmGtP3Yykt1RzeQ4ukCdSDjk/PmV8XneTu8OC8Cs8HA==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.9", + "clsx": "^1.2.1", + "color": "^4.2.3", + "color2k": "^2.0.3", + "deepmerge": "4.3.1", + "flat": "^5.0.2", + "tailwind-merge": "2.5.4", + "tailwind-variants": "0.3.0" + }, + "peerDependencies": { + "tailwindcss": ">=3.4.0" + } + }, + "node_modules/@heroui/theme/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@heroui/theme/node_modules/tailwind-merge": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", + "integrity": "sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/@heroui/toast": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@heroui/toast/-/toast-2.0.9.tgz", + "integrity": "sha512-V/x7bkRRS5BabF3Oe4sJWiKygkGtN9/mwFw0phJwx7PYV2Q6WuOvOvq+Zbt8bEz21j58glg4u+eLFBChNPYn7A==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/shared-icons": "2.1.7", + "@heroui/shared-utils": "2.1.9", + "@heroui/spinner": "2.2.16", + "@heroui/use-is-mobile": "2.2.9", + "@react-aria/interactions": "3.25.0", + "@react-aria/toast": "3.0.2", + "@react-aria/utils": "3.28.2", + "@react-stately/toast": "3.1.0", + "@react-stately/utils": "3.10.6" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.10", + "@heroui/theme": ">=2.4.12", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/tooltip": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@heroui/tooltip/-/tooltip-2.2.16.tgz", + "integrity": "sha512-pdQZTW04P+Ol6fr6ZfCHDVT+BRksx0n2kGJskMpEYKS0Q4Dk1AKmbVxfHYrT7yOQFQTTmTFJzkbbFMLFgg/Wrg==", + "license": "MIT", + "dependencies": { + "@heroui/aria-utils": "2.2.16", + "@heroui/dom-animation": "2.1.8", + "@heroui/framer-utils": "2.1.15", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@heroui/use-safe-layout-effect": "2.1.7", + "@react-aria/interactions": "3.25.0", + "@react-aria/overlays": "3.27.0", + "@react-aria/tooltip": "3.8.2", + "@react-aria/utils": "3.28.2", + "@react-stately/tooltip": "3.5.3", + "@react-types/overlays": "3.8.14", + "@react-types/tooltip": "3.4.16" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "framer-motion": ">=11.5.6 || >=12.0.0-alpha.1", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-accordion": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-accordion/-/use-aria-accordion-2.2.11.tgz", + "integrity": "sha512-E3FSS0QdppE7rnlkhvZD2LZDtfqbhkblFC+kMnqcaYsM1fhbdygtyZWrCDdxGku+g37fXxxa3dbPgFBoocTxQw==", + "license": "MIT", + "dependencies": { + "@react-aria/button": "3.13.0", + "@react-aria/focus": "3.20.2", + "@react-aria/selection": "3.24.0", + "@react-aria/utils": "3.28.2", + "@react-stately/tree": "3.8.9", + "@react-types/accordion": "3.0.0-alpha.26", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-accordion/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-button": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-button/-/use-aria-button-2.2.13.tgz", + "integrity": "sha512-gYgoaLxF4X8EnKH5HINrujiJlUtyakKRaeUpfohCrCDL/VEHAwi6+wJVC1AvE1gOfFx5db8+2TUw71IaSgUNGA==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.9", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "@react-types/button": "3.12.0", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-button/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-link": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-link/-/use-aria-link-2.2.14.tgz", + "integrity": "sha512-93IPT2+JKoSMqFbU90zVG0wpjAT40v2MjXIxyV0ziUJZSBaK1KNh1gZlUD9FGl4s6CLIT01reOpkRCp6fBnBvw==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.9", + "@react-aria/focus": "3.20.2", + "@react-aria/interactions": "3.25.0", + "@react-aria/utils": "3.28.2", + "@react-types/link": "3.6.0", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-link/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-modal-overlay": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-modal-overlay/-/use-aria-modal-overlay-2.2.12.tgz", + "integrity": "sha512-AWSy2QnX4RHUisH3kFQ708+9YWKa4mZsTzd+Vvh0rpSvgJdU0JW0/15aNj662QtzP4JLn5uLHtqbMbN71ulKzQ==", + "license": "MIT", + "dependencies": { + "@react-aria/overlays": "3.27.0", + "@react-aria/utils": "3.28.2", + "@react-stately/overlays": "3.6.15", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-modal-overlay/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-aria-multiselect": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@heroui/use-aria-multiselect/-/use-aria-multiselect-2.4.12.tgz", + "integrity": "sha512-clGuf5HKOUFM9dj18ZtI0nOsO1md/IHDHaCJyA2I8NgceVNSodK0ZQgR4GRRf4v2y11OzVIYHz6327Xfv4Hvjw==", + "license": "MIT", + "dependencies": { + "@react-aria/i18n": "3.12.8", + "@react-aria/interactions": "3.25.0", + "@react-aria/label": "3.7.17", + "@react-aria/listbox": "3.14.3", + "@react-aria/menu": "3.18.2", + "@react-aria/selection": "3.24.0", + "@react-aria/utils": "3.28.2", + "@react-stately/form": "3.1.3", + "@react-stately/list": "3.12.1", + "@react-stately/menu": "3.9.3", + "@react-types/button": "3.12.0", + "@react-types/overlays": "3.8.14", + "@react-types/select": "3.9.11", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-aria-multiselect/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-callback-ref": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/use-callback-ref/-/use-callback-ref-2.1.7.tgz", + "integrity": "sha512-AKMb+zV8um9y7gnsPgmVPm5WRx0oJc/3XU+banr8qla27+3HhnQZVqk3nlSHIplkseQzMRt3xHj5RPnwKbs71w==", + "license": "MIT", + "dependencies": { + "@heroui/use-safe-layout-effect": "2.1.7" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-clipboard": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@heroui/use-clipboard/-/use-clipboard-2.1.8.tgz", + "integrity": "sha512-itT5PCoMRoa6rjV51Z9wxeDQpSYMZj2sDFYrM7anGFO/4CAsQ/NfQoPwl5+kX0guqCcCGMqgFnNzNyQuNNsPtg==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-data-scroll-overflow": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@heroui/use-data-scroll-overflow/-/use-data-scroll-overflow-2.2.10.tgz", + "integrity": "sha512-Lza9S7ZWhY3PliahSgDRubrpeT7gnySH67GSTrGQMzYggTDMo2I1Pky7ZaHUnHHYB9Y7WHryB26ayWBOgRtZUQ==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.9" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-disclosure": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/use-disclosure/-/use-disclosure-2.2.11.tgz", + "integrity": "sha512-ARZAKoAURaeD+9PlZarlLqQtSx6cUkrO9m6CVRC8lzVKS1jWvT7u+ZfoLF7fS2m1AmONLBPnjREW5oupAluS/w==", + "license": "MIT", + "dependencies": { + "@heroui/use-callback-ref": "2.1.7", + "@react-aria/utils": "3.28.2", + "@react-stately/utils": "3.10.6" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-draggable": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@heroui/use-draggable/-/use-draggable-2.1.11.tgz", + "integrity": "sha512-Oi0JwC8F3cCfpPY5c6UpEGsC0cJW3vZ8rwyn0RuTKV7DjaU52YARS56KqJk0udli4R1fjtwrTNuye3TJcS+0ww==", + "license": "MIT", + "dependencies": { + "@react-aria/interactions": "3.25.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-image": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@heroui/use-image/-/use-image-2.1.9.tgz", + "integrity": "sha512-rHfPv4PkRN6mUG3eoBZBi8P8FnM37Kb/lOUM5M5kWtPMRpdfpgDxGQjf24K2lwSQM5xVG1H8WlF1Wipcd0kpmA==", + "license": "MIT", + "dependencies": { + "@heroui/react-utils": "2.1.10", + "@heroui/use-safe-layout-effect": "2.1.7" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-intersection-observer": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@heroui/use-intersection-observer/-/use-intersection-observer-2.2.11.tgz", + "integrity": "sha512-QcS1H1zVw8keoHSlT7cxmTuCCMk260/1gmpMM8zVAs0nF8tVL8xylsI1chHSIxZvsL1SNOPC4J++eUeG8QHEEQ==", + "license": "MIT", + "dependencies": { + "@react-aria/interactions": "3.25.0", + "@react-aria/ssr": "3.9.8", + "@react-aria/utils": "3.28.2", + "@react-types/shared": "3.29.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-intersection-observer/node_modules/@react-types/shared": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", + "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@heroui/use-is-mobile": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@heroui/use-is-mobile/-/use-is-mobile-2.2.9.tgz", + "integrity": "sha512-UVc9wKK3kg2bIAQPaKuCA53qd1Snrd8yxIf/dtbh3PqYjqoyN7c1hUFZxe9ZW8Vb3AovquWDnPYbx4vjdzcQiQ==", + "license": "MIT", + "dependencies": { + "@react-aria/ssr": "3.9.8" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-is-mounted": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/use-is-mounted/-/use-is-mounted-2.1.7.tgz", + "integrity": "sha512-Msf4eWWUEDofPmvaFfS4azftO9rIuKyiagxsYE73PSMcdB+7+PJSMTY5ZTM3cf/lwUJzy1FQvyTiCKx0RQ5neA==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-measure": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/use-measure/-/use-measure-2.1.7.tgz", + "integrity": "sha512-H586tr/bOH08MAufeiT35E1QmF8SPQy5Ghmat1Bb+vh/6KZ5S0K0o95BE2to7sXE9UCJWa7nDFuizXAGbveSiA==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-pagination": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@heroui/use-pagination/-/use-pagination-2.2.12.tgz", + "integrity": "sha512-tbVad95Z4ECbfagZMU2bg4ofMdHAmA7gA3qtUXPvwDcUZqCxvVm+5RiGUPF0wVHWTRTguntJO5vmGQBInUbeuw==", + "license": "MIT", + "dependencies": { + "@heroui/shared-utils": "2.1.9", + "@react-aria/i18n": "3.12.8" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-safe-layout-effect": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/use-safe-layout-effect/-/use-safe-layout-effect-2.1.7.tgz", + "integrity": "sha512-ZiMc+nVjcE5aArC4PEmnLHSJj0WgAXq3udr7FZaosP/jrRdn5VPcfF9z9cIGNJD6MkZp+YP0XGslrIFKZww0Hw==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-scroll-position": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/use-scroll-position/-/use-scroll-position-2.1.7.tgz", + "integrity": "sha512-c91Elycrq51nhpWqFIEBy04P+KBJjnEz4u1+1c7txnjs/k0FOD5EBD8+Jf8GJbh4WYp5N936XFvCcE7gB1C9JQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/use-update-effect": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@heroui/use-update-effect/-/use-update-effect-2.1.7.tgz", + "integrity": "sha512-G7Crf4vdJh2bwyQQ5+dN+IfvtHpRNkNlEXVDE87Kb15fJ7Rnokt3webnogBreZ9l7SbHpEGvx5sZPsgUHgrTMg==", + "license": "MIT", + "peerDependencies": { + "react": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@heroui/user": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@heroui/user/-/user-2.2.15.tgz", + "integrity": "sha512-0v9IYY+NEct3RN7yAoAx75baX2Tmww7oa6qcMrEgI6y0/8OKXwDwqSc1Cb8VAAwTotpWv46Ek09JNwAx+uJLNA==", + "license": "MIT", + "dependencies": { + "@heroui/avatar": "2.2.15", + "@heroui/react-utils": "2.1.10", + "@heroui/shared-utils": "2.1.9", + "@react-aria/focus": "3.20.2", + "@react-aria/utils": "3.28.2" + }, + "peerDependencies": { + "@heroui/system": ">=2.4.7", + "@heroui/theme": ">=2.4.6", + "react": ">=18 || >=19.0.0-rc.0", + "react-dom": ">=18 || >=19.0.0-rc.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.12.tgz", + "integrity": "sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.13", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz", + "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@inquirer/core/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/core/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", + "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", + "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@internationalized/date": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.0.tgz", + "integrity": "sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/message": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.7.tgz", + "integrity": "sha512-gLQlhEW4iO7DEFPf/U7IrIdA3UyLGS0opeqouaFwlMObLUzwexRjbygONHDVbC9G9oFLXsLyGKYkJwqXw/QADg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "intl-messageformat": "^10.1.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.2.tgz", + "integrity": "sha512-E5QTOlMg9wo5OrKdHD6edo1JJlIoOsylh0+mbf0evi1tHJwMZfJSaBpGtnJV9N7w3jeiioox9EG/EWRWPh82vg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.6.tgz", + "integrity": "sha512-LR2lnM4urJta5/wYJVV7m8qk5DrMZmLRTuFhbQO5b9/sKLHgty6unQy1Li4+Su2DWydmB4aZdS5uxBRXIq2aAw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@microlink/react-json-view": { + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/@microlink/react-json-view/-/react-json-view-1.26.2.tgz", + "integrity": "sha512-NamaHDT21njvbg2RZQq+rnu+owlPyj5lnUdVH5ZtChfTX+75QD2EGnccB1gs0De42jdPj77UQHYLr7d4J46IYA==", + "license": "MIT", + "dependencies": { + "react-base16-styling": "~0.9.0", + "react-lifecycles-compat": "~3.0.4", + "react-textarea-autosize": "~8.5.7" + }, + "engines": { + "node": ">=17" + }, + "peerDependencies": { + "react": ">= 15", + "react-dom": ">= 15" + } + }, + "node_modules/@mjackson/node-fetch-server": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mjackson/node-fetch-server/-/node-fetch-server-0.2.0.tgz", + "integrity": "sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==", + "license": "MIT" + }, + "node_modules/@monaco-editor/loader": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", + "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.37.6", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.6.tgz", + "integrity": "sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@mswjs/socket.io-binding": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@mswjs/socket.io-binding/-/socket.io-binding-0.1.1.tgz", + "integrity": "sha512-mtFDHC5XMeti43toe3HBynD4uBxvUA2GfJVC6TDfhOQlH+G2hf5znNTSa75A30XdWL0P6aNqUKpcNo6L0Wop+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mswjs/interceptors": "^0.37.1", + "engine.io-parser": "^5.2.3", + "socket.io-parser": "^4.2.4" + }, + "peerDependencies": { + "@mswjs/interceptors": "*" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/git": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", + "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^4.1.0", + "glob": "^10.2.2", + "hosted-git-info": "^6.1.1", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", + "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@playwright/test": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", + "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT" + }, + "node_modules/@react-aria/breadcrumbs": { + "version": "3.5.23", + "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.23.tgz", + "integrity": "sha512-4uLxuAgPfXds8sBc/Cg0ml7LKWzK+YTwHL7xclhQUkPO32rzlHDl+BJ5cyWhvZgGUf8JJXbXhD5VlJJzbbl8Xg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.8", + "@react-aria/link": "^3.8.0", + "@react-aria/utils": "^3.28.2", + "@react-types/breadcrumbs": "^3.7.12", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/button": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.13.0.tgz", + "integrity": "sha512-BEcTQb7Q8ZrAtn0scPDv/ErZoGC1FI0sLk0UTPGskuh/RV9ZZGFbuSWTqOwV8w5CS6VMvPjH6vaE8hS7sb5DIw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.0", + "@react-aria/toolbar": "3.0.0-beta.15", + "@react-aria/utils": "^3.28.2", + "@react-stately/toggle": "^3.8.3", + "@react-types/button": "^3.12.0", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/calendar": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.8.0.tgz", + "integrity": "sha512-9vms/fWjJPZkJcMxciwWWOjGy/Q0nqI6FV0pYbMZbqepkzglEaVd98kl506r/4hLhWKwLdTfqCgbntRecj8jBg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.8.0", + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/live-announcer": "^3.4.2", + "@react-aria/utils": "^3.28.2", + "@react-stately/calendar": "^3.8.0", + "@react-types/button": "^3.12.0", + "@react-types/calendar": "^3.7.0", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/checkbox": { + "version": "3.15.4", + "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.15.4.tgz", + "integrity": "sha512-ZkDJFs2EfMBXVIpBSo4ouB+NXyr2LRgZNp2x8/v+7n3aTmMU8j2PzT+Ra2geTQbC0glMP7UrSg4qZblqrxEBcQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/form": "^3.0.15", + "@react-aria/interactions": "^3.25.0", + "@react-aria/label": "^3.7.17", + "@react-aria/toggle": "^3.11.2", + "@react-aria/utils": "^3.28.2", + "@react-stately/checkbox": "^3.6.13", + "@react-stately/form": "^3.1.3", + "@react-stately/toggle": "^3.8.3", + "@react-types/checkbox": "^3.9.3", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/combobox": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.12.2.tgz", + "integrity": "sha512-EgddiF8VnAjB4EynJERPn4IoDMUabI8GiKOQZ6Ar3MlRWxQnUfxPpZwXs8qWR3dPCzYUt2PhBinhBMjyR1yRIw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.2", + "@react-aria/i18n": "^3.12.8", + "@react-aria/listbox": "^3.14.3", + "@react-aria/live-announcer": "^3.4.2", + "@react-aria/menu": "^3.18.2", + "@react-aria/overlays": "^3.27.0", + "@react-aria/selection": "^3.24.0", + "@react-aria/textfield": "^3.17.2", + "@react-aria/utils": "^3.28.2", + "@react-stately/collections": "^3.12.3", + "@react-stately/combobox": "^3.10.4", + "@react-stately/form": "^3.1.3", + "@react-types/button": "^3.12.0", + "@react-types/combobox": "^3.13.4", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/datepicker": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.14.2.tgz", + "integrity": "sha512-O7fdzcqIJ7i/+8SGYvx4tloTZgK4Ws8OChdbFcd2rZoRPqxM50M6J+Ota8hTet2wIhojUXnM3x2na3EvoucBXA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.8.0", + "@internationalized/number": "^3.6.1", + "@internationalized/string": "^3.2.6", + "@react-aria/focus": "^3.20.2", + "@react-aria/form": "^3.0.15", + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/label": "^3.7.17", + "@react-aria/spinbutton": "^3.6.14", + "@react-aria/utils": "^3.28.2", + "@react-stately/datepicker": "^3.14.0", + "@react-stately/form": "^3.1.3", + "@react-types/button": "^3.12.0", + "@react-types/calendar": "^3.7.0", + "@react-types/datepicker": "^3.12.0", + "@react-types/dialog": "^3.5.17", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/dialog": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.24.tgz", + "integrity": "sha512-tw0WH89gVpHMI5KUQhuzRE+IYCc9clRfDvCppuXNueKDrZmrQKbeoU6d0b5WYRsBur2+d7ErtvpLzHVqE1HzfA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.0", + "@react-aria/overlays": "^3.27.0", + "@react-aria/utils": "^3.28.2", + "@react-types/dialog": "^3.5.17", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/focus": { + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.2.tgz", + "integrity": "sha512-Q3rouk/rzoF/3TuH6FzoAIKrl+kzZi9LHmr8S5EqLAOyP9TXIKG34x2j42dZsAhrw7TbF9gA8tBKwnCNH4ZV+Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.0", + "@react-aria/utils": "^3.28.2", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/form": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.15.tgz", + "integrity": "sha512-kk8AnLz+EOgnn3sTaXYmtw+YzVDc1of/+xAkuOupQi6zQFnNRjc99JlDbKHoUZ39urMl+8lsp/1b9VPPhNrBNw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.0", + "@react-aria/utils": "^3.28.2", + "@react-stately/form": "^3.1.3", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.14.0.tgz", + "integrity": "sha512-/tJB7xnSruORJ8tlFHja4SfL8/EW5v4cBLiyD5z48m7IdG33jXR8Cv4Pi5uQqs8zKdnpqZ1wDG3GQxNDwZavpg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.3", + "@react-aria/i18n": "^3.12.9", + "@react-aria/interactions": "^3.25.1", + "@react-aria/live-announcer": "^3.4.2", + "@react-aria/selection": "^3.24.1", + "@react-aria/utils": "^3.29.0", + "@react-stately/collections": "^3.12.4", + "@react-stately/grid": "^3.11.2", + "@react-stately/selection": "^3.20.2", + "@react-types/checkbox": "^3.9.4", + "@react-types/grid": "^3.3.2", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@internationalized/date": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.1.tgz", + "integrity": "sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/focus": { + "version": "3.20.3", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.3.tgz", + "integrity": "sha512-rR5uZUMSY4xLHmpK/I8bP1V6vUNHFo33gTvrvNUsAKKqvMfa7R2nu5A6v97dr5g6tVH6xzpdkPsOJCWh90H2cw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.1", + "@react-aria/utils": "^3.29.0", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/i18n": { + "version": "3.12.9", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.9.tgz", + "integrity": "sha512-Fim0FLfY05kcpIILdOtqcw58c3sksvmVY8kICSwKCuSek4wYfwJdU28p/sRptw4adJhqN8Cbssvkf/J8zL2GgA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.8.1", + "@internationalized/message": "^3.1.7", + "@internationalized/number": "^3.6.2", + "@internationalized/string": "^3.2.6", + "@react-aria/ssr": "^3.9.8", + "@react-aria/utils": "^3.29.0", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/interactions": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.1.tgz", + "integrity": "sha512-ntLrlgqkmZupbbjekz3fE/n3eQH2vhncx8gUp0+N+GttKWevx7jos11JUBjnJwb1RSOPgRUFcrluOqBp0VgcfQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.8", + "@react-aria/utils": "^3.29.0", + "@react-stately/flags": "^3.1.1", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/selection": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.24.1.tgz", + "integrity": "sha512-nHUksgjg92iHgseH9L+krk9rX19xGJLTDeobKBX7eoAXQMqQjefu+oDwT0VYdI/qqNURNELE/KPZIVLC4PB81w==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.3", + "@react-aria/i18n": "^3.12.9", + "@react-aria/interactions": "^3.25.1", + "@react-aria/utils": "^3.29.0", + "@react-stately/selection": "^3.20.2", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-aria/utils": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.29.0.tgz", + "integrity": "sha512-jSOrZimCuT1iKNVlhjIxDkAhgF7HSp3pqyT6qjg/ZoA0wfqCi/okmrMPiWSAKBnkgX93N8GYTLT3CIEO6WZe9Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.8", + "@react-stately/flags": "^3.1.1", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-stately/collections": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.4.tgz", + "integrity": "sha512-H+47fRkwYX2/BdSA+NLTzbR+8QclZXyBgC7tHH3dzljyxNimhrMDnbmk520nvGCebNf3nuxtFHq9iVTLpazSVA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-types/checkbox": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.9.4.tgz", + "integrity": "sha512-fU3Q1Nw+zbXKm68ba8V7cQzpiX0rIiAUKrBTl2BK97QiTlGBDvMCf4TfEuaNoGbJq+gx+X3n/3yr6c3IAb0ZIg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid/node_modules/@react-types/grid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.2.tgz", + "integrity": "sha512-NwfydUbPc1zVi/Rp7+oRN2+vE1xMokc2J+nr0VcHwFGt1bR1psakHu45Pk/t763BDvPr/A3xIHc1rk3eWEhxJw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/i18n": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.8.tgz", + "integrity": "sha512-V/Nau9WuwTwxfFffQL4URyKyY2HhRlu9zmzkF2Hw/j5KmEQemD+9jfaLueG2CJu85lYL06JrZXUdnhZgKnqMkA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.8.0", + "@internationalized/message": "^3.1.7", + "@internationalized/number": "^3.6.1", + "@internationalized/string": "^3.2.6", + "@react-aria/ssr": "^3.9.8", + "@react-aria/utils": "^3.28.2", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.0.tgz", + "integrity": "sha512-GgIsDLlO8rDU/nFn6DfsbP9rfnzhm8QFjZkB9K9+r+MTSCn7bMntiWQgMM+5O6BiA8d7C7x4zuN4bZtc0RBdXQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.8", + "@react-aria/utils": "^3.28.2", + "@react-stately/flags": "^3.1.1", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/label": { + "version": "3.7.17", + "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.7.17.tgz", + "integrity": "sha512-Fz7IC2LQT2Y/sAoV+gFEXoULtkznzmK2MmeTv5shTNjeTxzB1BhQbD4wyCypi7eGsnD/9Zy+8viULCsIUbvjWw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.28.2", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/landmark": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-aria/landmark/-/landmark-3.0.3.tgz", + "integrity": "sha512-mcmHijInDZZY3W9r0SeRuXsHW8Km9rBWKB3eoBz+PVuyJYMuabhQ2mUB5xTbqbnV++Srr7j/59g+Lbw5gAN4lw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.29.0", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/landmark/node_modules/@react-aria/utils": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.29.0.tgz", + "integrity": "sha512-jSOrZimCuT1iKNVlhjIxDkAhgF7HSp3pqyT6qjg/ZoA0wfqCi/okmrMPiWSAKBnkgX93N8GYTLT3CIEO6WZe9Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.8", + "@react-stately/flags": "^3.1.1", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/link": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.8.0.tgz", + "integrity": "sha512-gpDD6t3FqtFR9QjSIKNpmSR3tS4JG2anVKx2wixuRDHO6Ddexxv4SBzsE1+230p+FlFGjftFa2lEgQ7RNjZrmA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.0", + "@react-aria/utils": "^3.28.2", + "@react-types/link": "^3.6.0", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/listbox": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.14.3.tgz", + "integrity": "sha512-wzelam1KENUvKjsTq8gfrOW2/iab8SyIaSXfFvGmWW82XlDTlW+oQeA39tvOZktMVGspr+xp8FySY09rtz6UXw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.0", + "@react-aria/label": "^3.7.17", + "@react-aria/selection": "^3.24.0", + "@react-aria/utils": "^3.28.2", + "@react-stately/collections": "^3.12.3", + "@react-stately/list": "^3.12.1", + "@react-types/listbox": "^3.6.0", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/live-announcer": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.4.2.tgz", + "integrity": "sha512-6+yNF9ZrZ4YJ60Oxy2gKI4/xy6WUv1iePDCFJkgpNVuOEYi8W8czff8ctXu/RPB25OJx5v2sCw9VirRogTo2zA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/menu": { + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.18.2.tgz", + "integrity": "sha512-90k+Ke1bhFWhR2zuRI6OwKWQrCpOD99n+9jhG96JZJZlNo5lB+5kS+ufG1LRv5GBnCug0ciLQmPMAfguVsCjEQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.2", + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/overlays": "^3.27.0", + "@react-aria/selection": "^3.24.0", + "@react-aria/utils": "^3.28.2", + "@react-stately/collections": "^3.12.3", + "@react-stately/menu": "^3.9.3", + "@react-stately/selection": "^3.20.1", + "@react-stately/tree": "^3.8.9", + "@react-types/button": "^3.12.0", + "@react-types/menu": "^3.10.0", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/numberfield": { + "version": "3.11.13", + "resolved": "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.11.13.tgz", + "integrity": "sha512-F73BVdIRV8VvKl0omhGaf0E7mdJ7pdPjDP3wYNf410t55BXPxmndItUKpGfxSbl8k6ZYLvQyOqkD6oWSfZXpZw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/spinbutton": "^3.6.14", + "@react-aria/textfield": "^3.17.2", + "@react-aria/utils": "^3.28.2", + "@react-stately/form": "^3.1.3", + "@react-stately/numberfield": "^3.9.11", + "@react-types/button": "^3.12.0", + "@react-types/numberfield": "^3.8.10", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/overlays": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.27.0.tgz", + "integrity": "sha512-2vZVgL7FrloN5Rh8sAhadGADJbuWg69DdSJB3fd2/h5VvcEhnIfNPu9Ma5XmdkApDoTboIEsKZ4QLYwRl98w6w==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.2", + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/ssr": "^3.9.8", + "@react-aria/utils": "^3.28.2", + "@react-aria/visually-hidden": "^3.8.22", + "@react-stately/overlays": "^3.6.15", + "@react-types/button": "^3.12.0", + "@react-types/overlays": "^3.8.14", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/progress": { + "version": "3.4.22", + "resolved": "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.22.tgz", + "integrity": "sha512-wK2hath4C9HKgmjCH+iSrAs86sUKqqsYKbEKk9/Rj9rzXqHyaEK9EG0YZDnSjd8kX+N9hYcs5MfJl6AZMH4juQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.8", + "@react-aria/label": "^3.7.17", + "@react-aria/utils": "^3.28.2", + "@react-types/progress": "^3.5.11", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/radio": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.11.2.tgz", + "integrity": "sha512-6AFJHXMewJBgHNhqkN1qjgwwx6kmagwYD+3Z+hNK1UHTsKe1Uud5/IF7gPFCqlZeKxA+Lvn9gWiqJrQbtD2+wg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.2", + "@react-aria/form": "^3.0.15", + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/label": "^3.7.17", + "@react-aria/utils": "^3.28.2", + "@react-stately/radio": "^3.10.12", + "@react-types/radio": "^3.8.8", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/selection": { + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.24.0.tgz", + "integrity": "sha512-RfGXVc04zz41NVIW89/a3quURZ4LT/GJLkiajQK2VjhisidPdrAWkcfjjWJj0n+tm5gPWbi9Rs5R/Rc8mrvq8Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.2", + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/utils": "^3.28.2", + "@react-stately/selection": "^3.20.1", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/slider": { + "version": "3.7.18", + "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.18.tgz", + "integrity": "sha512-GBVv5Rpvj/6JH2LnF1zVAhBmxGiuq7R8Ekqyr5kBrCc2ToF3PrTjfGc/mlh0eEtbj+NvAcnlgTx1/qosYt1sGw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/label": "^3.7.17", + "@react-aria/utils": "^3.28.2", + "@react-stately/slider": "^3.6.3", + "@react-types/shared": "^3.29.0", + "@react-types/slider": "^3.7.10", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton": { + "version": "3.6.15", + "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.15.tgz", + "integrity": "sha512-dVKaRgrSU2utxCd4kqAA8BPrC1PVI1eiJ8gvlVbg25LbwK4dg1WPXQUK+80TbrJc9mOEooPiJvzw59IoQLMNRg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.9", + "@react-aria/live-announcer": "^3.4.2", + "@react-aria/utils": "^3.29.0", + "@react-types/button": "^3.12.1", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@internationalized/date": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.1.tgz", + "integrity": "sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-aria/i18n": { + "version": "3.12.9", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.9.tgz", + "integrity": "sha512-Fim0FLfY05kcpIILdOtqcw58c3sksvmVY8kICSwKCuSek4wYfwJdU28p/sRptw4adJhqN8Cbssvkf/J8zL2GgA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.8.1", + "@internationalized/message": "^3.1.7", + "@internationalized/number": "^3.6.2", + "@internationalized/string": "^3.2.6", + "@react-aria/ssr": "^3.9.8", + "@react-aria/utils": "^3.29.0", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-aria/utils": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.29.0.tgz", + "integrity": "sha512-jSOrZimCuT1iKNVlhjIxDkAhgF7HSp3pqyT6qjg/ZoA0wfqCi/okmrMPiWSAKBnkgX93N8GYTLT3CIEO6WZe9Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.8", + "@react-stately/flags": "^3.1.1", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton/node_modules/@react-types/button": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.12.1.tgz", + "integrity": "sha512-z87stl4llWTi4C5qhUK1PKcEsG59uF/ZQpkRhMzX0KfgXobJY6yiIrry2xrpnlTPIVST6K1+kARhhSDOZ8zhLw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.8", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.8.tgz", + "integrity": "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/switch": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.7.2.tgz", + "integrity": "sha512-vaREbp1gFjv+jEMXoXpNK7JYFO/jhwnSYAwEINNWnwf54IGeHvTPaB2NwolYSFvP4HAj8TKYbGFUSz7RKLhLgw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/toggle": "^3.11.2", + "@react-stately/toggle": "^3.8.3", + "@react-types/shared": "^3.29.0", + "@react-types/switch": "^3.5.10", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/table": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/@react-aria/table/-/table-3.17.2.tgz", + "integrity": "sha512-wsF3JqiAKcol1sfeNqTxyzH6+nxu0sAfyuh+XQfp1tvSGx15NifYeNKovNX4EPpUVkAI7jL5Le+eYeYYGELfnw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.2", + "@react-aria/grid": "^3.13.0", + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/live-announcer": "^3.4.2", + "@react-aria/utils": "^3.28.2", + "@react-aria/visually-hidden": "^3.8.22", + "@react-stately/collections": "^3.12.3", + "@react-stately/flags": "^3.1.1", + "@react-stately/table": "^3.14.1", + "@react-types/checkbox": "^3.9.3", + "@react-types/grid": "^3.3.1", + "@react-types/shared": "^3.29.0", + "@react-types/table": "^3.12.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/tabs": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.10.2.tgz", + "integrity": "sha512-rpEgh//Gnew3le49tQVFOQ6ZyacJdaNUDXHt0ocguXb+2UrKtH54M8oIAE7E8KaB1puQlFXRs+Rjlr1rOlmjEQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.2", + "@react-aria/i18n": "^3.12.8", + "@react-aria/selection": "^3.24.0", + "@react-aria/utils": "^3.28.2", + "@react-stately/tabs": "^3.8.1", + "@react-types/shared": "^3.29.0", + "@react-types/tabs": "^3.3.14", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/textfield": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.17.2.tgz", + "integrity": "sha512-4KINB0HueYUHUgvi/ThTP27hu4Mv5ujG55pH3dmSRD4Olu/MRy1m/Psq72o8LTf4bTOM9ZP1rKccUg6xfaMidA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/form": "^3.0.15", + "@react-aria/interactions": "^3.25.0", + "@react-aria/label": "^3.7.17", + "@react-aria/utils": "^3.28.2", + "@react-stately/form": "^3.1.3", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.0", + "@react-types/textfield": "^3.12.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@react-aria/toast/-/toast-3.0.2.tgz", + "integrity": "sha512-iaiHDE1CKYM3BbNEp3A2Ed8YAlpXUGyY6vesKISdHEZ2lJ7r+1hbcFoTNdG8HfbB8Lz5vw8Wd2o+ZmQ2tnDY9Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.8", + "@react-aria/interactions": "^3.25.0", + "@react-aria/landmark": "^3.0.2", + "@react-aria/utils": "^3.28.2", + "@react-stately/toast": "^3.1.0", + "@react-types/button": "^3.12.0", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.11.3.tgz", + "integrity": "sha512-S6ShToNR6TukRJh8qDdyl9b2Bcsx43eurUB5USANn4ycPov8+bIxQnxiknjssZx7jD8vX4jruuNh7BjFbNsGFw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.1", + "@react-aria/utils": "^3.29.0", + "@react-stately/toggle": "^3.8.4", + "@react-types/checkbox": "^3.9.4", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-aria/interactions": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.1.tgz", + "integrity": "sha512-ntLrlgqkmZupbbjekz3fE/n3eQH2vhncx8gUp0+N+GttKWevx7jos11JUBjnJwb1RSOPgRUFcrluOqBp0VgcfQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.8", + "@react-aria/utils": "^3.29.0", + "@react-stately/flags": "^3.1.1", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-aria/utils": { + "version": "3.29.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.29.0.tgz", + "integrity": "sha512-jSOrZimCuT1iKNVlhjIxDkAhgF7HSp3pqyT6qjg/ZoA0wfqCi/okmrMPiWSAKBnkgX93N8GYTLT3CIEO6WZe9Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.8", + "@react-stately/flags": "^3.1.1", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-stately/toggle": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.8.4.tgz", + "integrity": "sha512-JbKoXhkJ5P5nCrNXChMos3yNqkIeGXPDEMS/dfkHlsjQYxJfylRm4j/nWoDXxxkUmfkvXcNEMofMn9iO1+H0DQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.6", + "@react-types/checkbox": "^3.9.4", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle/node_modules/@react-types/checkbox": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.9.4.tgz", + "integrity": "sha512-fU3Q1Nw+zbXKm68ba8V7cQzpiX0rIiAUKrBTl2BK97QiTlGBDvMCf4TfEuaNoGbJq+gx+X3n/3yr6c3IAb0ZIg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toolbar": { + "version": "3.0.0-beta.15", + "resolved": "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.15.tgz", + "integrity": "sha512-PNGpNIKIsCW8rxI9XXSADlLrSpikILJKKECyTRw9KwvXDRc44pezvdjGHCNinQcKsQoy5BtkK5cTSAyVqzzTXQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.20.2", + "@react-aria/i18n": "^3.12.8", + "@react-aria/utils": "^3.28.2", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/tooltip": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.8.2.tgz", + "integrity": "sha512-ctVTgh1LXvmr1ve3ehAWfvlJR7nHYZeqhl/g1qnA+983LQtc1IF9MraCs92g0m7KpBwCihuA+aYwTPsUHfKfXg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.0", + "@react-aria/utils": "^3.28.2", + "@react-stately/tooltip": "^3.5.3", + "@react-types/shared": "^3.29.0", + "@react-types/tooltip": "^3.4.16", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.28.2", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.28.2.tgz", + "integrity": "sha512-J8CcLbvnQgiBn54eeEvQQbIOfBF3A1QizxMw9P4cl9MkeR03ug7RnjTIdJY/n2p7t59kLeAB3tqiczhcj+Oi5w==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.8", + "@react-stately/flags": "^3.1.1", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/visually-hidden": { + "version": "3.8.22", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.22.tgz", + "integrity": "sha512-EO3R8YTKZ7HkLl9k1Y2uBKYBgpJagth4/4W7mfpJZE24A3fQnCP8zx1sweXiAm0mirR4J6tNaK7Ia8ssP5TpOw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.0", + "@react-aria/utils": "^3.28.2", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-router/dev": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.6.1.tgz", + "integrity": "sha512-E4pzxViSQ1Z4EPUz1p47ldm+qIbzfFbJtbXvxi+KSidpftf/ttjr+DtLEiTEdIqZTYv8trBASRtV6C5hn9GZQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.8", + "@babel/generator": "^7.21.5", + "@babel/parser": "^7.21.8", + "@babel/plugin-syntax-decorators": "^7.22.10", + "@babel/plugin-syntax-jsx": "^7.21.4", + "@babel/preset-typescript": "^7.21.5", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.22.5", + "@npmcli/package-json": "^4.0.1", + "@react-router/node": "7.6.1", + "arg": "^5.0.1", + "babel-dead-code-elimination": "^1.0.6", + "chokidar": "^4.0.0", + "dedent": "^1.5.3", + "es-module-lexer": "^1.3.1", + "exit-hook": "2.2.1", + "fs-extra": "^10.0.0", + "jsesc": "3.0.2", + "lodash": "^4.17.21", + "pathe": "^1.1.2", + "picocolors": "^1.1.1", + "prettier": "^2.7.1", + "react-refresh": "^0.14.0", + "semver": "^7.3.7", + "set-cookie-parser": "^2.6.0", + "valibot": "^0.41.0", + "vite-node": "3.0.0-beta.2" + }, + "bin": { + "react-router": "bin.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@react-router/serve": "^7.6.1", + "react-router": "^7.6.1", + "typescript": "^5.1.0", + "vite": "^5.1.0 || ^6.0.0", + "wrangler": "^3.28.2 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@react-router/serve": { + "optional": true + }, + "typescript": { + "optional": true + }, + "wrangler": { + "optional": true + } + } + }, + "node_modules/@react-router/dev/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-router/dev/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/@react-router/express": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.6.1.tgz", + "integrity": "sha512-cdmz6MhmzzMSXWP3xyVYVgpPdGcsKGREyNW99yEun2kOMzcY4R/64lgTECPmtODKhPSbuFRmQvUp80xPL7NT2Q==", + "license": "MIT", + "dependencies": { + "@react-router/node": "7.6.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "express": "^4.17.1 || ^5", + "react-router": "7.6.1", + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@react-router/node": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.6.1.tgz", + "integrity": "sha512-RZ9IatEarjF1GSHV+OUHNaRKtfp27UXP2J8dVdax6K/UXHc45k3t9Zp6splqT88wob4CUt4loDQw/7srNvsQhQ==", + "license": "MIT", + "dependencies": { + "@mjackson/node-fetch-server": "^0.2.0", + "source-map-support": "^0.5.21", + "stream-slice": "^0.1.2", + "undici": "^6.19.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react-router": "7.6.1", + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@react-router/serve": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.6.1.tgz", + "integrity": "sha512-Pk0gV7URA9cuLxDpoQ/Qt27gptdaHepMZYdHRgUzFYZjBfoSeSlm9iU6BV71QOTUtOkQW94U4V4RjD57DQu7gg==", + "license": "MIT", + "dependencies": { + "@react-router/express": "7.6.1", + "@react-router/node": "7.6.1", + "compression": "^1.7.4", + "express": "^4.19.2", + "get-port": "5.1.1", + "morgan": "^1.10.0", + "source-map-support": "^0.5.21" + }, + "bin": { + "react-router-serve": "bin.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react-router": "7.6.1" + } + }, + "node_modules/@react-stately/calendar": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.8.0.tgz", + "integrity": "sha512-YAuJiR9EtVThX91gU2ay/6YgPe0LvZWEssu4BS0Atnwk5cAo32gvF5FMta9ztH1LIULdZFaypU/C1mvnayMf+Q==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.8.0", + "@react-stately/utils": "^3.10.6", + "@react-types/calendar": "^3.7.0", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/checkbox": { + "version": "3.6.13", + "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.13.tgz", + "integrity": "sha512-b8+bkOhobzuJ5bAA16JpYg1tM973eNXD3U4h/8+dckLndKHRjIwPvrL25tzKN7NcQp2LKVCauFesgI+Z+/2FJg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/form": "^3.1.3", + "@react-stately/utils": "^3.10.6", + "@react-types/checkbox": "^3.9.3", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/collections": { + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.3.tgz", + "integrity": "sha512-QfSBME2QWDjUw/RmmUjrYl/j1iCYcYCIDsgZda1OeRtt63R11k0aqmmwrDRwCsA+Sv+D5QgkOp4KK+CokTzoVQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/combobox": { + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.10.4.tgz", + "integrity": "sha512-sgujLhukIGKskLDrOL4SAbO7WOgLsD7gSdjRQZ0f/e8bWMmUOWEp22T+X1hMMcuVRkRdXlEF1kH2/E6BVanXYw==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.3", + "@react-stately/form": "^3.1.3", + "@react-stately/list": "^3.12.1", + "@react-stately/overlays": "^3.6.15", + "@react-stately/select": "^3.6.12", + "@react-stately/utils": "^3.10.6", + "@react-types/combobox": "^3.13.4", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/datepicker": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.14.0.tgz", + "integrity": "sha512-JSkQfKW0+WpPQyOOeRPBLwXkVfpTUwgZJDnHBCud5kEuQiFFyeAIbL57RNXc4AX2pzY3piQa6OHnjDGTfqClxQ==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.8.0", + "@internationalized/string": "^3.2.6", + "@react-stately/form": "^3.1.3", + "@react-stately/overlays": "^3.6.15", + "@react-stately/utils": "^3.10.6", + "@react-types/datepicker": "^3.12.0", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.1.tgz", + "integrity": "sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/form": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.1.3.tgz", + "integrity": "sha512-Jisgm0facSS3sAzHfSgshoCo3LxfO0wmQj98MOBCGXyVL+MSwx2ilb38eXIyBCzHJzJnPRTLaK/E4T49aph47A==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/grid": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.11.2.tgz", + "integrity": "sha512-P0vfK5B1NW8glYD6QMrR2X/7UMXx2J8v48QIQV6KgLZjFbyXhzRb+MY0BoIy4tUfJL0yQU2GKbKKVSUIQxbv0g==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.4", + "@react-stately/selection": "^3.20.2", + "@react-types/grid": "^3.3.2", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/grid/node_modules/@react-stately/collections": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.4.tgz", + "integrity": "sha512-H+47fRkwYX2/BdSA+NLTzbR+8QclZXyBgC7tHH3dzljyxNimhrMDnbmk520nvGCebNf3nuxtFHq9iVTLpazSVA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/grid/node_modules/@react-types/grid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.2.tgz", + "integrity": "sha512-NwfydUbPc1zVi/Rp7+oRN2+vE1xMokc2J+nr0VcHwFGt1bR1psakHu45Pk/t763BDvPr/A3xIHc1rk3eWEhxJw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/list": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.12.1.tgz", + "integrity": "sha512-N+YCInNZ2OpY0WUNvJWUTyFHtzE5yBtZ9DI4EHJDvm61+jmZ2s3HszOfa7j+7VOKq78VW3m5laqsQNWvMrLFrQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.3", + "@react-stately/selection": "^3.20.1", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/menu": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.9.3.tgz", + "integrity": "sha512-9x1sTX3Xq2Q3mJUHV+YN9MR36qNzgn8eBSLa40eaFDaOOtoJ+V10m7OriUfpjey7WzLBpq00Sfda54/PbQHZ0g==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/overlays": "^3.6.15", + "@react-types/menu": "^3.10.0", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/numberfield": { + "version": "3.9.11", + "resolved": "https://registry.npmjs.org/@react-stately/numberfield/-/numberfield-3.9.11.tgz", + "integrity": "sha512-gAFSZIHnZsgIWVPgGRUUpfW6zM7TCV5oS1SCY90ay5nrS7JCXurQbMrWJLOWHTdM5iSeYMgoyt68OK5KD0KHMw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/number": "^3.6.1", + "@react-stately/form": "^3.1.3", + "@react-stately/utils": "^3.10.6", + "@react-types/numberfield": "^3.8.10", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/overlays": { + "version": "3.6.15", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.15.tgz", + "integrity": "sha512-LBaGpXuI+SSd5HSGzyGJA0Gy09V2tl2G/r0lllTYqwt0RDZR6p7IrhdGVXZm6vI0oWEnih7yLC32krkVQrffgQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.6", + "@react-types/overlays": "^3.8.14", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/radio": { + "version": "3.10.12", + "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.12.tgz", + "integrity": "sha512-hFH45CXVa7uyXeTYQy7LGR0SnmGnNRx7XnEXS25w4Ch6BpH8m8SAbhKXqysgcmsE3xrhRas7P9zWw7wI24G28Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/form": "^3.1.3", + "@react-stately/utils": "^3.10.6", + "@react-types/radio": "^3.8.8", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select": { + "version": "3.6.13", + "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.6.13.tgz", + "integrity": "sha512-saZo67CreQZPdmqvz9+P6N4kjohpwdVncH98qBi0Q2FvxGAMnpJQgx97rtfDvnSziST5Yx1JnMI4kSSndbtFwg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/form": "^3.1.4", + "@react-stately/list": "^3.12.2", + "@react-stately/overlays": "^3.6.16", + "@react-types/select": "^3.9.12", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/collections": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.4.tgz", + "integrity": "sha512-H+47fRkwYX2/BdSA+NLTzbR+8QclZXyBgC7tHH3dzljyxNimhrMDnbmk520nvGCebNf3nuxtFHq9iVTLpazSVA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/form": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.1.4.tgz", + "integrity": "sha512-A6GOaZ9oEIo5/XOE+JT9Z8OBt0osIOfes4EcIxGS1C9ght/Smg0gNcIJ2/Wle8qmro4RoJcza2yJ+EglVOuE0w==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/list": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.12.2.tgz", + "integrity": "sha512-XPGvdPidOV4hnpmaUNc4C/1jX7ZhBwmAI9p6bEXDA3du3XrWess6MWcaQvPxXbrZ6ZX8/OyOC2wp7ixJoJRGyA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.4", + "@react-stately/selection": "^3.20.2", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-stately/overlays": { + "version": "3.6.16", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.16.tgz", + "integrity": "sha512-+Ve/TBlUNg3otVC4ZfCq1a8q8FwC7xNebWkVOCGviTqiYodPCGqBwR9Z1xonuFLF/HuQYqALHHTOZtxceU+nVQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.6", + "@react-types/overlays": "^3.8.15", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-types/overlays": { + "version": "3.8.15", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.15.tgz", + "integrity": "sha512-ppDfezvVYOJDHLZmTSmIXajxAo30l2a1jjy4G65uBYy8J8kTZU7mcfQql5Pii1TwybcNMsayf2WtPItiWmJnOA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select/node_modules/@react-types/select": { + "version": "3.9.12", + "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.12.tgz", + "integrity": "sha512-qo+9JS1kfMxuibmSmMp0faGKbeVftYnSk1f7Rh5PKi4tzMe3C0A9IAr27hUOfWeJMBOdetaoTpYmoXW6+CgW3g==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/selection": { + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.20.2.tgz", + "integrity": "sha512-Fw6nnG+VKMsncsY4SNxGYOhnHojVFzFv+Uhy6P39QBp6AXtSaRKMg2VR4MPxQ7XgOjHh5ZuSvCY1RwocweqjwQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.4", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/selection/node_modules/@react-stately/collections": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.4.tgz", + "integrity": "sha512-H+47fRkwYX2/BdSA+NLTzbR+8QclZXyBgC7tHH3dzljyxNimhrMDnbmk520nvGCebNf3nuxtFHq9iVTLpazSVA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/slider": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.6.3.tgz", + "integrity": "sha512-755X1jhpRD1bqf/5Ax1xuSpZbnG/0EEHGOowH28FLYKy5+1l4QVDGPFYxLB9KzXPdRAr9EF0j2kRhH2d8MCksQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.0", + "@react-types/slider": "^3.7.10", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/table": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@react-stately/table/-/table-3.14.1.tgz", + "integrity": "sha512-7P5h4YBAv3B/7BGq/kln+xSKgJCSq4xjt4HmJA7ZkGnEksUPUokBNQdWwZsy3lX/mwunaaKR9x/YNIu7yXB02g==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.3", + "@react-stately/flags": "^3.1.1", + "@react-stately/grid": "^3.11.1", + "@react-stately/selection": "^3.20.1", + "@react-stately/utils": "^3.10.6", + "@react-types/grid": "^3.3.1", + "@react-types/shared": "^3.29.0", + "@react-types/table": "^3.12.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tabs": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.8.1.tgz", + "integrity": "sha512-1TBbt2BXbemstb/gEYw/NVt3esi5WvgWQW5Z7G8nDzLkpnMHOZXueoUkMxsdm0vhE8p0M9fsJQCMXKvCG3JzJg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/list": "^3.12.1", + "@react-types/shared": "^3.29.0", + "@react-types/tabs": "^3.3.14", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/toast": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@react-stately/toast/-/toast-3.1.0.tgz", + "integrity": "sha512-9W2+evz+EARrjkR1QPLlOL5lcNpVo6PjMAIygRSaCPJ6ftQAZ6B+7xTFGPFabWh83gwXQDUgoSwC3/vosvxZaQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/toggle": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.8.3.tgz", + "integrity": "sha512-4T2V3P1RK4zEFz4vJjUXUXyB0g4Slm6stE6Ry20fzDWjltuW42cD2lmrd7ccTO/CXFmHLECcXQLD4GEbOj0epA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.6", + "@react-types/checkbox": "^3.9.3", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tooltip": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.5.3.tgz", + "integrity": "sha512-btfy/gQ3Eccudx//4HkyQ+CRr3vxbLs74HYHthaoJ9GZbRj/3XDzfUM2X16zRoqTZVrIz/AkUj7AfGfsitU5nQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/overlays": "^3.6.15", + "@react-types/tooltip": "^3.4.16", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tree": { + "version": "3.8.9", + "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.8.9.tgz", + "integrity": "sha512-j/LLI9UvbqcfOdl2v9m3gET3etUxoQzv3XdryNAbSkg0jTx8/13Fgi/Xp98bUcNLfynfeGW5P/fieU71sMkGog==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.3", + "@react-stately/selection": "^3.20.1", + "@react-stately/utils": "^3.10.6", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.6", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.6.tgz", + "integrity": "sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/virtualizer": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-4.3.2.tgz", + "integrity": "sha512-KxR0s6IBqUD2TfDM3mAOtiTZLb1zOwcuCeUOvCKNqzEdFhh7nEJPrG33mgJn64S4kM11c0AsPwBlxISqdvCXJg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.28.2", + "@react-types/shared": "^3.29.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/accordion": { + "version": "3.0.0-alpha.26", + "resolved": "https://registry.npmjs.org/@react-types/accordion/-/accordion-3.0.0-alpha.26.tgz", + "integrity": "sha512-OXf/kXcD2vFlEnkcZy/GG+a/1xO9BN7Uh3/5/Ceuj9z2E/WwD55YwU3GFM5zzkZ4+DMkdowHnZX37XnmbyD3Mg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.27.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/breadcrumbs": { + "version": "3.7.12", + "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.12.tgz", + "integrity": "sha512-+LvGEADlv11mLQjxEAZriptSYJJTP+2OIFEKx0z9mmpp+8jTlEHFhAnRVaE6I9QCxcDB5F6q/olfizSwOPOMIg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/link": "^3.6.0", + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/button": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.12.0.tgz", + "integrity": "sha512-YrASNa+RqGQpzJcxNAahzNuTYVID1OE6HCorrEOXIyGS3EGogHsQmFs9OyThXnGHq6q4rLlA806/jWbP9uZdxA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/calendar": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.7.0.tgz", + "integrity": "sha512-RiEfX2ZTcvfRktQc5obOJtNTgW+UwjNOUW5yf9CLCNOSM07e0w5jtC1ewsOZZbcctMrMCljjL8niGWiBv1wQ1Q==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.8.0", + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/checkbox": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.9.3.tgz", + "integrity": "sha512-h6wmK7CraKHKE6L13Ut+CtnjRktbMRhkCSorv7eg82M6p4PDhZ7mfDSh13IlGR4sryT8Ka+aOjOU+EvMrKiduA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/combobox": { + "version": "3.13.4", + "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.13.4.tgz", + "integrity": "sha512-4mX7eZ/Bv3YWzEzLEZAF/TfKM+I+SCsvnm/cHqOJq3jEE8aVU1ql4Q1+3+SvciX3pfFIfeKlu9S3oYKRT5WIgg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/datepicker": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.12.0.tgz", + "integrity": "sha512-dw/xflOdQPQ3uEABaBrZRTvjsMRu5/VZjRx9ygc64sX2N7HKIt+foMPXKJ+1jhtki2p4gigNVjcnJndJHoj9SA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.8.0", + "@react-types/calendar": "^3.7.0", + "@react-types/overlays": "^3.8.14", + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/dialog": { + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.18.tgz", + "integrity": "sha512-g18CzT5xmiX/numpS6MrOGEGln8Xp9rr+zO70Dg+jM4GBOjXZp3BeclYQr9uisxGaj2uFLnORv9gNMMKxLNF6A==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.8.15", + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/dialog/node_modules/@react-types/overlays": { + "version": "3.8.15", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.15.tgz", + "integrity": "sha512-ppDfezvVYOJDHLZmTSmIXajxAo30l2a1jjy4G65uBYy8J8kTZU7mcfQql5Pii1TwybcNMsayf2WtPItiWmJnOA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/form": { + "version": "3.7.11", + "resolved": "https://registry.npmjs.org/@react-types/form/-/form-3.7.11.tgz", + "integrity": "sha512-umqy2Kvg3ooJi+Wqun95tKbKN51gtNt9s7OFLdwCtfWa6GvHFOixSjqAvZbo+m5qC3X/1kMIz3Dg698l0/+oLQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/grid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.1.tgz", + "integrity": "sha512-bPDckheJiHSIzSeSkLqrO6rXRLWvciFJr9rpCjq/+wBj6HsLh2iMpkB/SqmRHTGpPlJvlu0b7AlxK1FYE0QSKA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/link": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.6.0.tgz", + "integrity": "sha512-BQ5Tktb+fUxvtqksAJZuP8Z/bpmnQ/Y/zgwxfU0OKmIWkKMUsXY+e0GBVxwFxeh39D77stpVxRsTl7NQrjgtSw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/listbox": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.7.0.tgz", + "integrity": "sha512-26Lp0Gou502VJLDSrIpMg7LQuVHznxzyuSY/zzyNX9eopukXvHn682u90fwDqgmZz7dzxUOWtuwDea+bp/UjtA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/menu": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.10.0.tgz", + "integrity": "sha512-DKMqEmUmarVCK0jblNkSlzSH53AAsxWCX9RaKZeP9EnRs2/l1oZRuiQVHlOQRgYwEigAXa2TrwcX4nnxZ+U36Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.8.14", + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/numberfield": { + "version": "3.8.10", + "resolved": "https://registry.npmjs.org/@react-types/numberfield/-/numberfield-3.8.10.tgz", + "integrity": "sha512-mdb4lMC4skO8Eqd0GeU4lJgDTEvqIhtINB5WCzLVZFrFVuxgWDoU5otsu0lbWhCnUA7XWQxupGI//TC1LLppjQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/overlays": { + "version": "3.8.14", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.14.tgz", + "integrity": "sha512-XJS67KHYhdMvPNHXNGdmc85gE+29QT5TwC58V4kxxHVtQh9fYzEEPzIV8K84XWSz04rRGe3fjDgRNbcqBektWQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/progress": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.11.tgz", + "integrity": "sha512-CysuMld/lycOckrnlvrlsVoJysDPeBnUYBChwtqwiv4ZNRXos+wgAL1ows6dl7Nr57/FH5B4v5gf9AHEo7jUvw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/radio": { + "version": "3.8.8", + "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.8.8.tgz", + "integrity": "sha512-QfAIp+0CnRSnoRTJVXUEPi+9AvFvRzWLIKEnE9OmgXjuvJCU3QNiwd8NWjNeE+94QBEVvAZQcqGU+44q5poxNg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/select": { + "version": "3.9.11", + "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.11.tgz", + "integrity": "sha512-uEpQCgDlrq/5fW05FgNEsqsqpvZVKfHQO9Mp7OTqGtm4UBNAbcQ6hOV7MJwQCS25Lu2luzOYdgqDUN8eAATJVQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.1.tgz", + "integrity": "sha512-KtM+cDf2CXoUX439rfEhbnEdAgFZX20UP2A35ypNIawR7/PFFPjQDWyA2EnClCcW/dLWJDEPX2U8+EJff8xqmQ==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/slider": { + "version": "3.7.11", + "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.7.11.tgz", + "integrity": "sha512-uNhNLhVrt/2teXBOJSoZXyXg308A72qe1HOmlGdJcnh8iXA35y5ZHzeK1P6ZOJ37Aeh7bYGm3/UdURmFgSlW7w==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/switch": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.11.tgz", + "integrity": "sha512-PJbZHwlE98OSuLzI6b1ei6Qa+FaiwlCRH3tOTdx/wPSdqmD3mRWEn7E9ftM6FC8hnxl/LrGLszQMT62yEQp5vQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/table": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.12.0.tgz", + "integrity": "sha512-dmTzjCYwHf2HBOeTa/CEL177Aox0f0mkeLF5nQw/2z6SBolfmYoAwVTPxTaYFVu4MkEJxQTz9AuAsJvCbRJbhg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/grid": "^3.3.1", + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/tabs": { + "version": "3.3.14", + "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.14.tgz", + "integrity": "sha512-/uKsA7L2dctKU0JEaBWerlX+3BoXpKUFr3kHpRUoH66DSGvAo34vZ7kv/BHMZifJenIbF04GhDBsGp1zjrQKBg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/textfield": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.12.1.tgz", + "integrity": "sha512-6YTAMCKjEGuXg0A4bZA77j5QJ1a6yFviMUWsCIL6Dxq5K3TklzVsbAduSbHomPPuvkNTBSW4+TUJrVSnoTjMNA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/tooltip": { + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.16.tgz", + "integrity": "sha512-XEyKeqR3YxqJcR0cpigLGEBeRTEzrB0cu++IaADdqXJ8dBzS6s8y9EgR5UvKZmX1CQOBvMfXyYkj7nmJ039fOw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.8.14", + "@react-types/shared": "^3.29.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz", + "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@stripe/react-stripe-js": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.7.0.tgz", + "integrity": "sha512-PYls/2S9l0FF+2n0wHaEJsEU8x7CmBagiH7zYOsxbBlLIHEsqUIQ4MlIAbV9Zg6xwT8jlYdlRIyBTHmO3yM7kQ==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "@stripe/stripe-js": ">=1.44.1 <8.0.0", + "react": ">=16.8.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@stripe/stripe-js": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.3.0.tgz", + "integrity": "sha512-xnCyFIEI5SQnQrKkCxVj7nS5fWTZap+zuIGzmmxLMdlmgahFJaihK4zogqE8YyKKTLtrp/EldkEijSgtXsRVDg==", + "license": "MIT", + "engines": { + "node": ">=12.16" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", + "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.78.0", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.78.0.tgz", + "integrity": "sha512-hYkhWr3UP0CkAsn/phBVR98UQawbw8CmTSgWtdgEBUjI60/GBaEIkpgi/Bp/2I8eIDK4+vdY7ac6jZx+GR+hEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.18.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.77.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.77.2.tgz", + "integrity": "sha512-1lqJwPsR6GX6nZFw06erRt518O19tWU6Q+x0fJUygl4lxHCYF2nhzBPwLKk2NPjYOrpR0K567hxPc5K++xDe9Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.77.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.77.2.tgz", + "integrity": "sha512-BRHxWdy1mHmgAcYA/qy2IPLylT81oebLgkm9K85viN2Qol/Vq48t1dzDFeDIVQjTWDV96AmqsLNPlH5HjyKCxA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.77.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.3.tgz", + "integrity": "sha512-vCU+OTylXN3hdC8RKg68tPlBPjjxtzon7Ys46MgrSLE+JhSjSTPvoQifV6DQJeJmA8Q3KT6CphJbejupx85vFw==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.11.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.3.tgz", + "integrity": "sha512-v2mrNSnMwnPJtcVqNvV0c5roGCBqeogN8jDtgtuHCphdwBasOZ17x8UV8qpHUh+u0MLfX43c0uUHKje0s+Zb0w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/base16": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz", + "integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A==", + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", + "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", + "license": "MIT" + }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.15.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", + "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz", + "integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", + "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-highlight": { + "version": "0.12.8", + "resolved": "https://registry.npmjs.org/@types/react-highlight/-/react-highlight-0.12.8.tgz", + "integrity": "sha512-V7O7zwXUw8WSPd//YUO8sz489J/EeobJljASGhP0rClrvq+1Y1qWEpToGu+Pp7YuChxhAXSgkLkrOYpZX5A62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", + "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", + "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", + "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", + "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", + "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", + "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.32.1", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", + "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.10", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@rolldown/pluginutils": "1.0.0-beta.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.4.tgz", + "integrity": "sha512-G4p6OtioySL+hPV7Y6JHlhpsODbJzt1ndwHAFkyk6vVjpK03PFsKnauZIzcd0PrK4zAbc5lc+jeZ+eNGiMA+iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.1.4", + "vitest": "3.1.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz", + "integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz", + "integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz", + "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz", + "integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/snapshot": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz", + "integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/spy": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz", + "integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.4", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@xterm/addon-fit": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", + "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-dead-code-elimination": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.10.tgz", + "integrity": "sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==", + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", + "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true, + "license": "MIT" + }, + "node_modules/console-clear": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz", + "integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", + "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz", + "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.1.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", + "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.158", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.158.tgz", + "integrity": "sha512-9vcp2xHhkvraY6AHw2WMi+GDSLPX42qe2xjYaVoZqFRJiOcilVQFq9mZmpuHEQpzlgGDelKlV7ZiGcmMsc8WxQ==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-ex/node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-abstract": { + "version": "1.23.10", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz", + "integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-18.0.0.tgz", + "integrity": "sha512-oc+Lxzgzsu8FQyFVa4QFaVKiitTYiiW3frB9KYW5OWdPrqFc7FzxgB20hP4cHMlr+MBzGcLl3jnCOVOydL9mIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", + "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz", + "integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-unused-imports": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz", + "integrity": "sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.15.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.15.0.tgz", + "integrity": "sha512-XKg/LnKExdLGugZrDILV7jZjI599785lDIJZLxMiiIFidCsy0a4R2ZEf+Izm67zyOuJgQYTHOmodi7igQsw3vg==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.15.0", + "motion-utils": "^12.12.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/graphql": { + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hastscript/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/hastscript/node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hastscript/node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hastscript/node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, + "node_modules/hosted-git-info": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", + "integrity": "sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/i18next": { + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz", + "integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.1.0.tgz", + "integrity": "sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", + "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/input-otp": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.1.tgz", + "integrity": "sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/intl-messageformat": { + "version": "10.7.16", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz", + "integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.2", + "tslib": "^2.8.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbot": { + "version": "5.1.28", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.28.tgz", + "integrity": "sha512-qrOp4g3xj8YNse4biorv6O5ZShwsJM0trsoda4y7j/Su7ZtTTfVXFzbKkpgcSoDrHS8FcTuUwcU04YimZlZOxw==", + "license": "Unlicense", + "engines": { + "node": ">=18" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jose": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.11.tgz", + "integrity": "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.0.0.tgz", + "integrity": "sha512-sUCprePs6/rbx4vKC60Hez6X10HPkpDJaGcy3D1NdwR7g1RcNkWL8q9mJMreOqmHBTs+1sNFp+wOiX9fr+hoOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "lilconfig": "^3.1.3", + "listr2": "^8.3.3", + "micromatch": "^4.0.8", + "nano-spawn": "^1.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.18" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/local-access": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/local-access/-/local-access-1.1.0.tgz", + "integrity": "sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.511.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz", + "integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/monaco-editor": { + "version": "0.52.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", + "license": "MIT" + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/motion-dom": { + "version": "12.15.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.15.0.tgz", + "integrity": "sha512-D2ldJgor+2vdcrDtKJw48k3OddXiZN1dDLLWrS8kiHzQdYVruh0IoTwbJBslrnTXIPgFED7PBN2Zbwl7rNqnhA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.12.1" + } + }, + "node_modules/motion-utils": { + "version": "12.12.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz", + "integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==", + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/msw": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.8.4.tgz", + "integrity": "sha512-GLU8gx0o7RBG/3x/eTnnLd5S5ZInxXRRRMN8GJwaPZ4jpJTxzQfWGvwr90e8L5dkKJnz+gT4gQYCprLy/c4kVw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.1", + "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.37.0", + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.26.1", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nano-spawn": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", + "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz", + "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/posthog-js": { + "version": "1.247.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.247.0.tgz", + "integrity": "sha512-ml7QRfNbXhEHTS/g0HML2Vfs/goZELyFoKlmraJZ9nwyoQq9l4Q/8TLV/JLIlr7/6Io/sSDAwM6xBbJ/8pVBzQ==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "core-js": "^3.38.1", + "fflate": "^0.4.8", + "preact": "^10.19.3", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@rrweb/types": "2.0.0-alpha.17", + "rrweb-snapshot": "2.0.0-alpha.17" + }, + "peerDependenciesMeta": { + "@rrweb/types": { + "optional": true + }, + "rrweb-snapshot": { + "optional": true + } + } + }, + "node_modules/posthog-js/node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "license": "Apache-2.0" + }, + "node_modules/preact": { + "version": "10.26.7", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.7.tgz", + "integrity": "sha512-43xS+QYc1X1IPbw03faSgY6I6OYWcLrJRv3hU0+qMOfh/XCHcP0MX2CVjNARYR2cC/guu975sta4OcjlczxD7g==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-base16-styling": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz", + "integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "@types/base16": "^1.0.2", + "@types/lodash": "^4.14.178", + "base16": "^1.0.0", + "color": "^3.2.1", + "csstype": "^3.0.10", + "lodash.curry": "^4.1.1" + } + }, + "node_modules/react-base16-styling/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/react-base16-styling/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/react-base16-styling/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-highlight": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/react-highlight/-/react-highlight-0.15.0.tgz", + "integrity": "sha512-5uV/b/N4Z421GSVVe05fz+OfTsJtFzx/fJBdafZyw4LS70XjIZwgEx3Lrkfc01W/RzZ2Dtfb0DApoaJFAIKBtA==", + "license": "MIT", + "dependencies": { + "highlight.js": "^10.5.0" + } + }, + "node_modules/react-hot-toast": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", + "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-i18next": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz", + "integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.1.tgz", + "integrity": "sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-syntax-highlighter": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", + "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", + "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz", + "integrity": "sha512-t44QCeDKAPf1mtQH3fYpWz8IM/DyvHLjs8wUvvwMYxk5moOqCzrMSxK6HQVD0QVmVjXFavoFIPRVrMuJPKAvtg==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semiver": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz", + "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sirv-cli": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-3.0.1.tgz", + "integrity": "sha512-ICXaF2u6IQhLZ0EXF6nqUF4YODfSQSt+mGykt4qqO5rY+oIiwdg7B8w2PVDBJlQulaS2a3J8666CUoDoAuCGvg==", + "license": "MIT", + "dependencies": { + "console-clear": "^1.1.0", + "get-port": "^5.1.1", + "kleur": "^4.1.4", + "local-access": "^1.0.1", + "sade": "^1.6.0", + "semiver": "^1.0.0", + "sirv": "^3.0.0", + "tinydate": "^1.0.0" + }, + "bin": { + "sirv": "bin.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-slice": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", + "integrity": "sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==", + "license": "MIT" + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stripe": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.1.1.tgz", + "integrity": "sha512-hlF0ripc2nJrihpsJZQDl3xirS7tpdpS7DlmSNLEDRW8j7Qr215y5DHOI3+aEY/lq6PG8y4GR1RZPtEoIoAs/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + }, + "peerDependencies": { + "@types/node": ">=12.x.x" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", + "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tailwind-merge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz", + "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.0.tgz", + "integrity": "sha512-ho2k5kn+LB1fT5XdNS3Clb96zieWxbStE9wNLK7D0AV64kdZMaYzAKo0fWl6fXLPY99ffF9oBJnIj5escEl/8A==", + "license": "MIT", + "dependencies": { + "tailwind-merge": "^2.5.4" + }, + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, + "node_modules/tailwind-variants/node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinydate": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz", + "integrity": "sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/valibot": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.41.0.tgz", + "integrity": "sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.0.0-beta.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.0-beta.2.tgz", + "integrity": "sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-plugin-svgr": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz", + "integrity": "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.3", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": ">=2.6.0" + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz", + "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.1.4", + "@vitest/mocker": "3.1.4", + "@vitest/pretty-format": "^3.1.4", + "@vitest/runner": "3.1.4", + "@vitest/snapshot": "3.1.4", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.13", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.1.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.1.4", + "@vitest/ui": "3.1.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/vite-node": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz", + "integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/web-vitals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.0.1.tgz", + "integrity": "sha512-BsULPWaCKAAtNntUz0aJq1cu1wyuWmDzf4N6vYNMbYA6zzQAf2pzCYbyClf+Ui2MI54bt225AwugXIfL1W+Syg==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000000000000000000000000000000000000..1eccb8a2f5ba47f1e44d0c37d6b521c1a78b09e4 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,136 @@ +{ + "name": "openhands-frontend", + "version": "0.41.0", + "private": true, + "type": "module", + "engines": { + "node": ">=20.0.0" + }, + "dependencies": { + "@heroui/react": "2.7.8", + "@microlink/react-json-view": "^1.26.2", + "@monaco-editor/react": "^4.7.0-rc.0", + "@react-router/node": "^7.6.1", + "@react-router/serve": "^7.6.1", + "@react-types/shared": "^3.29.1", + "@reduxjs/toolkit": "^2.8.2", + "@stripe/react-stripe-js": "^3.7.0", + "@stripe/stripe-js": "^7.3.0", + "@tanstack/react-query": "^5.77.2", + "@vitejs/plugin-react": "^4.4.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.4.0", + "axios": "^1.9.0", + "clsx": "^2.1.1", + "eslint-config-airbnb-typescript": "^18.0.0", + "framer-motion": "^12.14.0", + "i18next": "^25.2.1", + "i18next-browser-languagedetector": "^8.1.0", + "i18next-http-backend": "^3.0.2", + "isbot": "^5.1.28", + "jose": "^6.0.11", + "lucide-react": "^0.511.0", + "monaco-editor": "^0.52.2", + "posthog-js": "^1.245.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-highlight": "^0.15.0", + "react-hot-toast": "^2.5.1", + "react-i18next": "^15.5.1", + "react-icons": "^5.5.0", + "react-markdown": "^10.1.0", + "react-redux": "^9.2.0", + "react-router": "^7.6.1", + "react-syntax-highlighter": "^15.6.1", + "react-textarea-autosize": "^8.5.9", + "remark-gfm": "^4.0.1", + "sirv-cli": "^3.0.1", + "socket.io-client": "^4.8.1", + "tailwind-merge": "^3.3.0", + "vite": "^6.3.5", + "web-vitals": "^5.0.1", + "ws": "^8.18.2" + }, + "scripts": { + "dev": "npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev", + "dev:mock": "npm run make-i18n && cross-env VITE_MOCK_API=true VITE_MOCK_SAAS=false react-router dev", + "dev:mock:saas": "npm run make-i18n && cross-env VITE_MOCK_API=true VITE_MOCK_SAAS=true react-router dev", + "build": "npm run make-i18n && react-router build", + "start": "npx sirv-cli build/ --single", + "test": "vitest run", + "test:e2e": "playwright test", + "test:coverage": "npm run make-i18n && vitest run --coverage", + "dev_wsl": "VITE_WATCH_USE_POLLING=true vite", + "preview": "vite preview", + "make-i18n": "node scripts/make-i18n-translations.cjs", + "prelint": "npm run make-i18n", + "lint": "npm run typecheck && eslint src --ext .ts,.tsx,.js && prettier --check src/**/*.{ts,tsx}", + "lint:fix": "eslint src --ext .ts,.tsx,.js --fix && prettier --write src/**/*.{ts,tsx}", + "prepare": "cd .. && husky frontend/.husky", + "typecheck": "react-router typegen && tsc", + "check-unlocalized-strings": "node scripts/check-unlocalized-strings.cjs", + "check-translation-completeness": "node scripts/check-translation-completeness.cjs" + }, + "lint-staged": { + "src/**/*.{ts,tsx,js}": [ + "eslint --fix", + "prettier --write" + ] + }, + "devDependencies": { + "@babel/parser": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.0", + "@mswjs/socket.io-binding": "^0.1.1", + "@playwright/test": "^1.52.0", + "@react-router/dev": "^7.6.1", + "@tailwindcss/typography": "^0.5.16", + "@tanstack/eslint-plugin-query": "^5.78.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.1", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.15.21", + "@types/react": "^19.1.5", + "@types/react-dom": "^19.1.5", + "@types/react-highlight": "^0.12.8", + "@types/react-syntax-highlighter": "^15.5.13", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@vitest/coverage-v8": "^3.1.4", + "autoprefixer": "^10.4.21", + "cross-env": "^7.0.3", + "eslint": "^8.57.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^18.0.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-unused-imports": "^4.1.4", + "husky": "^9.1.7", + "jsdom": "^26.1.0", + "lint-staged": "^16.0.0", + "msw": "^2.6.6", + "postcss": "^8.5.2", + "prettier": "^3.5.3", + "stripe": "^18.1.1", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "vite-plugin-svgr": "^4.2.0", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.0.2" + }, + "packageManager": "npm@10.5.0", + "volta": { + "node": "18.20.1" + }, + "msw": { + "workerDirectory": [ + "public" + ] + } +} diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..cfbc10779e1429068ac9801c837129f0cd17a81b --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,79 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:3001/", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: "npm run dev:mock -- --port 3001", + url: "http://localhost:3001/", + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000000000000000000000000000000000000..2e7af2b7f1a6f391da1631d93968a9d487ba977d --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/android-chrome-192x192.png b/frontend/public/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..23f4f4fd244886f6a87510e8ba9858235f3c39a6 Binary files /dev/null and b/frontend/public/android-chrome-192x192.png differ diff --git a/frontend/public/android-chrome-512x512.png b/frontend/public/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..1fe76e41968d9bae4dd80d1889b072b08dc98da1 Binary files /dev/null and b/frontend/public/android-chrome-512x512.png differ diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d6146fed3240c26a40ca177ffe3825ab951fbe8f Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/beep.wav b/frontend/public/beep.wav new file mode 100644 index 0000000000000000000000000000000000000000..c2364f1145b32180525b95d7e9b0fb96ca164e0c Binary files /dev/null and b/frontend/public/beep.wav differ diff --git a/frontend/public/browserconfig.xml b/frontend/public/browserconfig.xml new file mode 100644 index 0000000000000000000000000000000000000000..b3930d0f047184047cb81d620436d91653438b8b --- /dev/null +++ b/frontend/public/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/frontend/public/favicon-16x16.png b/frontend/public/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..5db772fa150cca9a0d9839b40ae0157e87c51998 Binary files /dev/null and b/frontend/public/favicon-16x16.png differ diff --git a/frontend/public/favicon-32x32.png b/frontend/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..bb75b8b65f8b7aaf02eec2c211e3e9a561f2a52d Binary files /dev/null and b/frontend/public/favicon-32x32.png differ diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..680e72b56f78e3aad4721e0bbfce4ba34d8bea23 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/public/mockServiceWorker.js b/frontend/public/mockServiceWorker.js new file mode 100644 index 0000000000000000000000000000000000000000..0c8e0df6af868e600244ed62dd87ff057c92b55f --- /dev/null +++ b/frontend/public/mockServiceWorker.js @@ -0,0 +1,307 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.8.4' +const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/frontend/public/mstile-150x150.png b/frontend/public/mstile-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..bdf3ed471b3abd51648295cda04c82ddd3187d06 Binary files /dev/null and b/frontend/public/mstile-150x150.png differ diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 0000000000000000000000000000000000000000..e9e57dc4d41b9b46e05112e9f45b7ea6ac0ba15e --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/frontend/public/safari-pinned-tab.svg b/frontend/public/safari-pinned-tab.svg new file mode 100644 index 0000000000000000000000000000000000000000..fb271c3449cb1fd7bd961248e30de0b3cf1c1016 --- /dev/null +++ b/frontend/public/safari-pinned-tab.svg @@ -0,0 +1,32 @@ + + safari-pinned-tab-svg + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/site.webmanifest b/frontend/public/site.webmanifest new file mode 100644 index 0000000000000000000000000000000000000000..b20abb7cbb2903c45280ba3540710669aeb63163 --- /dev/null +++ b/frontend/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/frontend/react-router.config.ts b/frontend/react-router.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..216284fa71032a736c12c395064a5af7d19551be --- /dev/null +++ b/frontend/react-router.config.ts @@ -0,0 +1,35 @@ +import type { Config } from "@react-router/dev/config"; + +/** + * This script is used to unpack the client directory from the frontend build directory. + * Remix SPA mode builds the client directory into the build directory. This function + * moves the contents of the client directory to the build directory and then removes the + * client directory. + * + * This script is used in the buildEnd function of the Vite config. + */ +const unpackClientDirectory = async () => { + const fs = await import("fs"); + const path = await import("path"); + + const buildDir = path.resolve(__dirname, "build"); + const clientDir = path.resolve(buildDir, "client"); + + const files = await fs.promises.readdir(clientDir); + await Promise.all( + files.map((file) => + fs.promises.rename( + path.resolve(clientDir, file), + path.resolve(buildDir, file), + ), + ), + ); + + await fs.promises.rmdir(clientDir); +}; + +export default { + appDirectory: "src", + buildEnd: unpackClientDirectory, + ssr: false, +} satisfies Config; diff --git a/frontend/scripts/check-translation-completeness.cjs b/frontend/scripts/check-translation-completeness.cjs new file mode 100644 index 0000000000000000000000000000000000000000..e855e0e763aa0d78ffb7fd9a6346fbfefb6eedc6 --- /dev/null +++ b/frontend/scripts/check-translation-completeness.cjs @@ -0,0 +1,88 @@ +#!/usr/bin/env node + +/** + * Pre-commit hook script to check for translation completeness + * This script ensures that all translation keys have entries for all supported languages + */ + +const fs = require('fs'); +const path = require('path'); + +// Load the translation file +const translationJsonPath = path.join(__dirname, '../src/i18n/translation.json'); +const translationJson = require(translationJsonPath); + +// Load the available languages from the i18n index file +const i18nIndexPath = path.join(__dirname, '../src/i18n/index.ts'); +const i18nIndexContent = fs.readFileSync(i18nIndexPath, 'utf8'); + +// Extract the language codes from the AvailableLanguages array +const languageCodesRegex = /\{ label: "[^"]+", value: "([^"]+)" \}/g; +const supportedLanguageCodes = []; +let match; + +while ((match = languageCodesRegex.exec(i18nIndexContent)) !== null) { + supportedLanguageCodes.push(match[1]); +} + +// Track missing and extra translations +const missingTranslations = {}; +const extraLanguages = {}; +let hasErrors = false; + +// Check each translation key +Object.entries(translationJson).forEach(([key, translations]) => { + // Get the languages available for this key + const availableLanguages = Object.keys(translations); + + // Find missing languages for this key + const missing = supportedLanguageCodes.filter( + (langCode) => !availableLanguages.includes(langCode) + ); + + if (missing.length > 0) { + missingTranslations[key] = missing; + hasErrors = true; + } + + // Find extra languages for this key + const extra = availableLanguages.filter( + (langCode) => !supportedLanguageCodes.includes(langCode) + ); + + if (extra.length > 0) { + extraLanguages[key] = extra; + hasErrors = true; + } +}); + +// Generate detailed error message if there are missing translations +if (Object.keys(missingTranslations).length > 0) { + console.error('\x1b[31m%s\x1b[0m', 'ERROR: Missing translations detected'); + console.error(`Found ${Object.keys(missingTranslations).length} translation keys with missing languages:`); + + Object.entries(missingTranslations).forEach(([key, langs]) => { + console.error(`- Key "${key}" is missing translations for: ${langs.join(', ')}`); + }); + + console.error('\nPlease add the missing translations before committing.'); +} + +// Generate detailed error message if there are extra languages +if (Object.keys(extraLanguages).length > 0) { + console.error('\x1b[31m%s\x1b[0m', 'ERROR: Extra languages detected'); + console.error(`Found ${Object.keys(extraLanguages).length} translation keys with extra languages not in AvailableLanguages:`); + + Object.entries(extraLanguages).forEach(([key, langs]) => { + console.error(`- Key "${key}" has translations for unsupported languages: ${langs.join(', ')}`); + }); + + console.error('\nPlease remove the extra languages before committing.'); +} + +// Exit with error code if there are issues +if (hasErrors) { + process.exit(1); +} else { + console.log('\x1b[32m%s\x1b[0m', 'All translation keys have complete language coverage!'); +} \ No newline at end of file diff --git a/frontend/scripts/check-unlocalized-strings.cjs b/frontend/scripts/check-unlocalized-strings.cjs new file mode 100644 index 0000000000000000000000000000000000000000..86425d61fb2fadfa8c33ac6e679dcf25bfa9a5e1 --- /dev/null +++ b/frontend/scripts/check-unlocalized-strings.cjs @@ -0,0 +1,737 @@ +#!/usr/bin/env node + +/** + * Pre-commit hook script to check for unlocalized strings in the frontend code + * This script is based on the test in __tests__/utils/check-hardcoded-strings.test.tsx + */ + +const path = require('path'); +const fs = require('fs'); +const parser = require('@babel/parser'); +const traverse = require('@babel/traverse').default; + +// Files/directories to ignore +const IGNORE_PATHS = [ + // Build and dependency files + "node_modules", + "dist", + ".git", + "test", + "__tests__", + ".d.ts", + "i18n", + "package.json", + "package-lock.json", + "tsconfig.json", + + // Internal code that doesn't need localization + "mocks", // Mock data + "assets", // SVG paths and CSS classes + "types", // Type definitions and constants + "state", // Redux state management + "api", // API endpoints + "services", // Internal services + "hooks", // React hooks + "context", // React context + "store", // Redux store + "routes.ts", // Route definitions + "root.tsx", // Root component + "entry.client.tsx", // Client entry point + "utils/scan-unlocalized-strings.ts", // Original scanner + "utils/scan-unlocalized-strings-ast.ts", // This file itself + "frontend/src/components/features/home/tasks/get-prompt-for-query.ts", // Only contains agent prompts +]; + +// Extensions to scan +const SCAN_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"]; + +// Attributes that typically don't contain user-facing text +const NON_TEXT_ATTRIBUTES = [ + "allow", + "className", + "i18nKey", + "testId", + "id", + "name", + "type", + "href", + "src", + "alt", + "placeholder", + "rel", + "target", + "style", + "onClick", + "onChange", + "onSubmit", + "data-testid", + "aria-label", + "aria-labelledby", + "aria-describedby", + "aria-hidden", + "role", + "sandbox", +]; + +function shouldIgnorePath(filePath) { + return IGNORE_PATHS.some((ignore) => filePath.includes(ignore)); +} + +// Check if a string looks like a translation key +// Translation keys typically use dots, underscores, or are all caps +// Also check for the pattern with $ which is used in our translation keys +function isLikelyTranslationKey(str) { + return ( + /^[A-Z0-9_$.]+$/.test(str) || + str.includes(".") || + /[A-Z0-9_]+\$[A-Z0-9_]+/.test(str) + ); +} + +// Check if a string is a raw translation key that should be wrapped in t() +function isRawTranslationKey(str) { + // Check for our specific translation key pattern (e.g., "SETTINGS$GITHUB_SETTINGS") + // Exclude specific keys that are already properly used with i18next.t() in the code + const excludedKeys = [ + "STATUS$ERROR_LLM_OUT_OF_CREDITS", + "ERROR$GENERIC", + "GITHUB$AUTH_SCOPE", + ]; + + if (excludedKeys.includes(str)) { + return false; + } + + return /^[A-Z0-9_]+\$[A-Z0-9_]+$/.test(str); +} + +// Specific technical strings that should be excluded from localization +const EXCLUDED_TECHNICAL_STRINGS = [ + "openid email profile", // OAuth scope string - not user-facing + "OPEN_ISSUE", // Task type identifier, not a UI string + "Merge Request", // Git provider specific terminology + "GitLab API", // Git provider specific terminology + "Pull Request", // Git provider specific terminology + "GitHub API", // Git provider specific terminology + "add-secret-form", // Test ID for secret form + "edit-secret-form", // Test ID for secret form + "search-api-key-input", // Input name for search API key + "noopener,noreferrer", // Options for window.open +]; + +function isExcludedTechnicalString(str) { + return EXCLUDED_TECHNICAL_STRINGS.includes(str); +} + +function isLikelyCode(str) { + // A string with no spaces and at least one underscore or colon is likely a code. + // (e.g.: "browser_interactive" or "error:") + if (str.includes(" ")) { + return false + } + if (str.includes(":") || str.includes("_")){ + return true + } + return false +} + +function isCommonDevelopmentString(str) { + // Technical patterns that are definitely not UI strings + const technicalPatterns = [ + // URLs and paths + /^https?:\/\//, // URLs + /^\/[a-zA-Z0-9_\-./]*$/, // File paths + /^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$/, // File extensions, class names + /^@[a-zA-Z0-9/-]+$/, // Import paths + /^#\/[a-zA-Z0-9/-]+$/, // Alias imports + /^[a-zA-Z0-9/-]+\/[a-zA-Z0-9/-]+$/, // Module paths + /^data:image\/[a-zA-Z0-9;,]+$/, // Data URLs + /^application\/[a-zA-Z0-9-]+$/, // MIME types + /^!\[image]\(data:image\/png;base64,$/, // Markdown image with base64 data + + // Numbers, IDs, and technical values + /^\d+(\.\d+)?$/, // Numbers + /^#[0-9a-fA-F]{3,8}$/, // Color codes + /^[a-zA-Z0-9_-]+=[a-zA-Z0-9_-]+$/, // Key-value pairs + /^mm:ss$/, // Time format + /^[a-zA-Z0-9]+\/[a-zA-Z0-9-]+$/, // Provider/model format + /^\?[a-zA-Z0-9_-]+$/, // URL parameters + /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i, // UUID + /^[A-Za-z0-9+/=]+$/, // Base64 + + // HTML and CSS selectors + /^[a-z]+(\[[^\]]+\])+$/, // CSS attribute selectors + /^[a-z]+:[a-z-]+$/, // CSS pseudo-selectors + /^[a-z]+\.[a-z0-9_-]+$/, // CSS class selectors + /^[a-z]+#[a-z0-9_-]+$/, // CSS ID selectors + /^[a-z]+\s*>\s*[a-z]+$/, // CSS child selectors + /^[a-z]+\s+[a-z]+$/, // CSS descendant selectors + + // CSS and styling patterns + /^[a-z0-9-]+:[a-z0-9-]+$/, // CSS property:value + /^[a-z0-9-]+:[a-z0-9-]+;[a-z0-9-]+:[a-z0-9-]+$/, // Multiple CSS properties + ]; + + // File extensions and media types + const fileExtensionPattern = + /^\.(png|jpg|jpeg|gif|svg|webp|bmp|ico|pdf|mp4|webm|ogg|mp3|wav|json|xml|csv|txt|md|html|css|js|jsx|ts|tsx)$/i; + if (fileExtensionPattern.test(str)) { + return true; + } + + // AI model and provider patterns + const aiRelatedPattern = + /^(AI|OpenAI|VertexAI|PaLM|Gemini|Anthropic|Anyscale|Databricks|Ollama|FriendliAI|Groq|DeepInfra|AI21|Replicate|OpenRouter|Azure|AWS|SageMaker|Bedrock|Mistral|Perplexity|Fireworks|Cloudflare|Workers|Voyage|claude-|gpt-|o1-|o3-)/i; + if (aiRelatedPattern.test(str)) { + return true; + } + + // CSS units and values + const cssUnitsPattern = + /(px|rem|em|vh|vw|vmin|vmax|ch|ex|fr|deg|rad|turn|grad|ms|s)$/; + const cssValuesPattern = + /(rgb|rgba|hsl|hsla|#[0-9a-fA-F]+|solid|absolute|relative|sticky|fixed|static|block|inline|flex|grid|none|auto|hidden|visible)/; + + if (cssUnitsPattern.test(str) || cssValuesPattern.test(str)) { + return true; + } + + // Check for CSS class strings with brackets (common in the codebase) + if ( + str.includes("[") && + str.includes("]") && + (str.includes("px") || + str.includes("rem") || + str.includes("em") || + str.includes("w-") || + str.includes("h-") || + str.includes("p-") || + str.includes("m-")) + ) { + return true; + } + + // Check for CSS class strings with specific patterns + if ( + str.includes("border-") || + str.includes("rounded-") || + str.includes("cursor-") || + str.includes("opacity-") || + str.includes("disabled:") || + str.includes("hover:") || + str.includes("focus-within:") || + str.includes("first-of-type:") || + str.includes("last-of-type:") || + str.includes("group-data-") + ) { + return true; + } + + // Check if it looks like a Tailwind class string + if (/^[a-z0-9-]+(\s+[a-z0-9-]+)*$/.test(str)) { + // Common Tailwind prefixes and patterns + const tailwindPrefixes = [ + "bg-", "text-", "border-", "rounded-", "p-", "m-", "px-", "py-", "mx-", "my-", + "w-", "h-", "min-w-", "min-h-", "max-w-", "max-h-", "flex-", "grid-", "gap-", + "space-", "items-", "justify-", "self-", "col-", "row-", "order-", "object-", + "overflow-", "opacity-", "z-", "top-", "right-", "bottom-", "left-", "inset-", + "font-", "tracking-", "leading-", "list-", "placeholder-", "shadow-", "ring-", + "transition-", "duration-", "ease-", "delay-", "animate-", "scale-", "rotate-", + "translate-", "skew-", "origin-", "cursor-", "select-", "resize-", "fill-", "stroke-", + ]; + + // Check if any word in the string starts with a Tailwind prefix + const words = str.split(/\s+/); + for (const word of words) { + for (const prefix of tailwindPrefixes) { + if (word.startsWith(prefix)) { + return true; + } + } + } + + // Check for Tailwind modifiers + const tailwindModifiers = [ + "hover:", "focus:", "active:", "disabled:", "visited:", "first:", "last:", + "odd:", "even:", "group-hover:", "focus-within:", "focus-visible:", "motion-safe:", + "motion-reduce:", "dark:", "light:", "sm:", "md:", "lg:", "xl:", "2xl:", + ]; + + for (const word of words) { + for (const modifier of tailwindModifiers) { + if (word.includes(modifier)) { + return true; + } + } + } + + // Check for CSS property combinations + const cssProperties = [ + "border", "rounded", "px", "py", "mx", "my", "p", "m", "w", "h", "flex", + "grid", "gap", "transition", "duration", "font", "leading", "tracking", + ]; + + // If the string contains multiple CSS properties, it's likely a CSS class string + let cssPropertyCount = 0; + for (const word of words) { + if ( + cssProperties.some( + (prop) => word === prop || word.startsWith(`${prop}-`), + ) + ) { + cssPropertyCount += 1; + } + } + + if (cssPropertyCount >= 2) { + return true; + } + } + + // Check for specific CSS class patterns that appear in the test failures + if ( + str.match( + /^(border|rounded|flex|grid|transition|duration|ease|hover:|focus:|active:|disabled:|placeholder:|text-|bg-|w-|h-|p-|m-|gap-|items-|justify-|self-|overflow-|cursor-|opacity-|z-|top-|right-|bottom-|left-|inset-|font-|tracking-|leading-|whitespace-|break-|truncate|shadow-|ring-|outline-|animate-|transform|rotate-|scale-|skew-|translate-|origin-|first-of-type:|last-of-type:|group-data-|max-|min-|px-|py-|mx-|my-|grow|shrink|resize-|underline|italic|normal)/, + ) + ) { + return true; + } + + // HTML tags and attributes + if ( + /^<[a-z0-9]+(?:\s[^>]*)?>.*<\/[a-z0-9]+>$/i.test(str) || + /^<[a-z0-9]+ [^>]+\/>$/i.test(str) + ) { + return true; + } + + // Check for specific patterns in suggestions and examples + if ( + str.includes("* ") && + (str.includes("create a") || + str.includes("build a") || + str.includes("make a")) + ) { + // This is likely a suggestion or example, not a UI string + return false; + } + + // Check for specific technical identifiers from the test failures + if ( + /^(download_via_vscode_button_clicked|open-vscode-error-|set-indicator|settings_saved|openhands-trace-|provider-item-|last_browser_action_error)$/.test( + str, + ) + ) { + return true; + } + + // Check for URL paths and query parameters + if ( + str.startsWith("?") || + str.startsWith("/") || + str.includes("auth.") || + str.includes("$1auth.") + ) { + return true; + } + + // Check for specific strings that should be excluded + if ( + str === "Cache Hit:" || + str === "Cache Write:" || + str === "ADD_DOCS" || + str === "ADD_DOCKERFILE" || + str === "Verified" || + str === "Others" || + str === "Feedback" || + str === "JSON File" || + str === "mt-0.5 md:mt-0" + ) { + return true; + } + + // Check for long suggestion texts + if ( + str.length > 100 && + (str.includes("Please write a bash script") || + str.includes("Please investigate the repo") || + str.includes("Please push the changes") || + str.includes("Examine the dependencies") || + str.includes("Investigate the documentation") || + str.includes("Investigate the current repo") || + str.includes("I want to create a Hello World app") || + str.includes("I want to create a VueJS app") || + str.includes("This should be a client-only app")) + ) { + return true; + } + + // Check for specific error messages and UI text + if ( + str === "All data associated with this project will be lost." || + str === "You will lose any unsaved information." || + str === + "This conversation does not exist, or you do not have permission to access it." || + str === "Failed to fetch settings. Please try reloading." || + str === + "If you tell OpenHands to start a web server, the app will appear here." || + str === + "Your browser doesn't support downloading files. Please use Chrome, Edge, or another browser that supports the File System Access API." || + str === + "Something went wrong while fetching settings. Please reload the page." || + str === + "To help us improve, we collect feedback from your interactions to improve our prompts. By submitting this form, you consent to us collecting this data." || + str === "Please push the latest changes to the existing pull request." + ) { + return true; + } + + // Check against all technical patterns + return technicalPatterns.some((pattern) => pattern.test(str)); +} + +function isLikelyUserFacingText(str) { + // Basic validation - skip very short strings or strings without letters + if (!str || str.length <= 2 || !/[a-zA-Z]/.test(str)) { + return false; + } + + // Check if it's a specifically excluded technical string + if (isExcludedTechnicalString(str)) { + return false; + } + + // Check if it looks like a code rather than a key + if (isLikelyCode(str)) { + return false + } + + // Check if it's a raw translation key that should be wrapped in t() + if (isRawTranslationKey(str)) { + return true; + } + + // Check if it's a translation key pattern (e.g., "SETTINGS$BASE_URL") + // These should be wrapped in t() or use I18nKey enum + if (isLikelyTranslationKey(str) && /^[A-Z0-9_]+\$[A-Z0-9_]+$/.test(str)) { + return true; + } + + // First, check if it's a common development string (not user-facing) + if (isCommonDevelopmentString(str)) { + return false; + } + + // Multi-word phrases are likely UI text + const hasMultipleWords = /\s+/.test(str) && str.split(/\s+/).length > 1; + + // Sentences and questions are likely UI text + const hasPunctuation = /[?!.,:]/.test(str); + const isCapitalizedPhrase = /^[A-Z]/.test(str) && hasMultipleWords; + const isTitleCase = hasMultipleWords && /\s[A-Z]/.test(str); + const hasSentenceStructure = /^[A-Z].*[.!?]$/.test(str); // Starts with capital, ends with punctuation + const hasQuestionForm = + /^(What|How|Why|When|Where|Who|Can|Could|Would|Will|Is|Are|Do|Does|Did|Should|May|Might)/.test( + str, + ); + + // Product names and camelCase identifiers are likely UI text + const hasInternalCapitals = /[a-z][A-Z]/.test(str); // CamelCase product names + + // Instruction text patterns are likely UI text + const looksLikeInstruction = + /^(Enter|Type|Select|Choose|Provide|Specify|Search|Find|Input|Add|Write|Describe|Set|Pick|Browse|Upload|Download|Click|Tap|Press|Go to|Visit|Open|Close)/i.test( + str, + ); + + // Error and status messages are likely UI text + const looksLikeErrorOrStatus = + /(failed|error|invalid|required|missing|incorrect|wrong|unavailable|not found|not available|try again|success|completed|finished|done|saved|updated|created|deleted|removed|added)/i.test( + str, + ); + + // Single word check - assume it's UI text unless proven otherwise + const isSingleWord = + !str.includes(" ") && str.length > 1 && /^[a-zA-Z]+$/.test(str); + + // For single words, we need to be more careful + if (isSingleWord) { + // Skip common programming terms and variable names + const isCommonProgrammingTerm = + /^(null|undefined|true|false|function|class|interface|type|enum|const|let|var|return|import|export|default|async|await|try|catch|finally|throw|new|this|super|extends|implements|instanceof|typeof|void|delete|in|of|for|while|do|if|else|switch|case|break|continue|yield|static|get|set|public|private|protected|readonly|abstract|implements|namespace|module|declare|as|from|with)$/i.test( + str, + ); + + if (isCommonProgrammingTerm) { + return false; + } + + // Skip common variable name patterns + const looksLikeVariableName = + /^[a-z][a-zA-Z0-9]*$/.test(str) && str.length <= 20; + + if (looksLikeVariableName) { + return false; + } + + // Skip common CSS values + const isCommonCssValue = + /^(auto|none|hidden|visible|block|inline|flex|grid|row|column|wrap|nowrap|center|start|end|stretch|cover|contain|fixed|absolute|relative|static|sticky|pointer|default|inherit|initial|unset)$/i.test( + str, + ); + + if (isCommonCssValue) { + return false; + } + + // Skip common file extensions + const isFileExtension = /^\.[a-z0-9]+$/i.test(str); + if (isFileExtension) { + return false; + } + + // Skip common abbreviations + const isCommonAbbreviation = + /^(id|src|href|url|alt|img|btn|nav|div|span|ul|li|ol|dl|dt|dd|svg|png|jpg|gif|pdf|doc|txt|md|js|ts|jsx|tsx|css|scss|less|html|xml|json|yaml|yml|toml|csv|mp3|mp4|wav|avi|mov|mpeg|webm|webp|ttf|woff|eot|otf)$/i.test( + str, + ); + + if (isCommonAbbreviation) { + return false; + } + + // If it's a single word that's not a programming term, variable name, CSS value, file extension, or abbreviation, + // it might be UI text, but we'll be conservative and return false + return false; + } + + // If it has multiple words, punctuation, or looks like a sentence, it's likely UI text + return ( + hasMultipleWords || + hasPunctuation || + isCapitalizedPhrase || + isTitleCase || + hasSentenceStructure || + hasQuestionForm || + hasInternalCapitals || + looksLikeInstruction || + looksLikeErrorOrStatus + ); +} + +function isInTranslationContext(path) { + // Check if the JSX text is inside a component + let current = path; + while (current.parentPath) { + if ( + current.isJSXElement() && + current.node.openingElement && + current.node.openingElement.name && + current.node.openingElement.name.name === "Trans" + ) { + return true; + } + current = current.parentPath; + } + return false; +} + +function scanFileForUnlocalizedStrings(filePath) { + // Skip all suggestion files as they contain special strings + if (filePath.includes("suggestions")) { + return []; + } + + try { + const content = fs.readFileSync(filePath, "utf-8"); + const unlocalizedStrings = []; + + // Skip files that are too large + if (content.length > 1000000) { + console.warn(`Skipping large file: ${filePath}`); + return []; + } + + try { + // Parse the file + const ast = parser.parse(content, { + sourceType: "module", + plugins: ["jsx", "typescript", "classProperties", "decorators-legacy"], + }); + + // Traverse the AST + traverse(ast, { + // Find JSX text content + JSXText(jsxTextPath) { + const text = jsxTextPath.node.value.trim(); + if ( + text && + isLikelyUserFacingText(text) && + !isInTranslationContext(jsxTextPath) + ) { + unlocalizedStrings.push(text); + } + }, + + // Find string literals in JSX attributes + JSXAttribute(jsxAttrPath) { + const attrName = jsxAttrPath.node.name.name.toString(); + + // Skip technical attributes that don't contain user-facing text + if (NON_TEXT_ATTRIBUTES.includes(attrName)) { + return; + } + + // Skip styling attributes + if ( + attrName === "className" || + attrName === "class" || + attrName === "style" + ) { + return; + } + + // Skip data attributes and event handlers + if (attrName.startsWith("data-") || attrName.startsWith("on")) { + return; + } + + // Check the attribute value + const value = jsxAttrPath.node.value; + if (value && value.type === "StringLiteral") { + const text = value.value.trim(); + if (text && isLikelyUserFacingText(text)) { + unlocalizedStrings.push(text); + } + } + }, + + // Find string literals in code + StringLiteral(stringPath) { + // Skip if parent is JSX attribute (already handled above) + if (stringPath.parent.type === "JSXAttribute") { + return; + } + + // Skip if parent is import/export declaration + if ( + stringPath.parent.type === "ImportDeclaration" || + stringPath.parent.type === "ExportDeclaration" + ) { + return; + } + + // Skip if parent is object property key + if ( + stringPath.parent.type === "ObjectProperty" && + stringPath.parent.key === stringPath.node + ) { + return; + } + + // Skip if inside a t() call or Trans component + let isInsideTranslation = false; + let current = stringPath; + + while (current.parentPath && !isInsideTranslation) { + // Check for t() function call + if ( + current.parent.type === "CallExpression" && + current.parent.callee && + ((current.parent.callee.type === "Identifier" && + current.parent.callee.name === "t") || + (current.parent.callee.type === "MemberExpression" && + current.parent.callee.property && + current.parent.callee.property.name === "t")) + ) { + isInsideTranslation = true; + break; + } + + // Check for component + if ( + current.parent.type === "JSXElement" && + current.parent.openingElement && + current.parent.openingElement.name && + current.parent.openingElement.name.name === "Trans" + ) { + isInsideTranslation = true; + break; + } + + current = current.parentPath; + } + + if (!isInsideTranslation) { + const text = stringPath.node.value.trim(); + if (text && isLikelyUserFacingText(text)) { + unlocalizedStrings.push(text); + } + } + }, + }); + + return unlocalizedStrings; + } catch (error) { + console.error(`Error parsing file ${filePath}:`, error); + return []; + } + } catch (error) { + console.error(`Error reading file ${filePath}:`, error); + return []; + } +} + +function scanDirectoryForUnlocalizedStrings(dirPath) { + const results = new Map(); + + function scanDir(currentPath) { + const entries = fs.readdirSync(currentPath, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(currentPath, entry.name); + + if (!shouldIgnorePath(fullPath)) { + if (entry.isDirectory()) { + scanDir(fullPath); + } else if ( + entry.isFile() && + SCAN_EXTENSIONS.includes(path.extname(fullPath)) + ) { + const unlocalized = scanFileForUnlocalizedStrings(fullPath); + if (unlocalized.length > 0) { + results.set(fullPath, unlocalized); + } + } + } + } + } + + scanDir(dirPath); + return results; +} + +// Run the check +try { + const srcPath = path.resolve(__dirname, '../src'); + console.log('Checking for unlocalized strings in frontend code...'); + + // Get unlocalized strings using the AST scanner + const results = scanDirectoryForUnlocalizedStrings(srcPath); + + // If we found any unlocalized strings, format them for output and exit with error + if (results.size > 0) { + const formattedResults = Array.from(results.entries()) + .map(([file, strings]) => `\n${file}:\n ${strings.join('\n ')}`) + .join('\n'); + + console.error(`Error: Found unlocalized strings in the following files:${formattedResults}`); + process.exit(1); + } + + console.log('✅ No unlocalized strings found in frontend code.'); + process.exit(0); +} catch (error) { + console.error('Error running unlocalized strings check:', error); + process.exit(1); +} diff --git a/frontend/scripts/detect-node-version.js b/frontend/scripts/detect-node-version.js new file mode 100644 index 0000000000000000000000000000000000000000..d8c7de2a19b75df0f69d52a52a02593086d6500f --- /dev/null +++ b/frontend/scripts/detect-node-version.js @@ -0,0 +1,28 @@ +function parseVersion(version) { + return version.split('.').map(Number); +} + +function compareVersions(v1, v2) { + const v1Parts = parseVersion(v1); + const v2Parts = parseVersion(v2); + + for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { + const v1Part = v1Parts[i] || 0; + const v2Part = v2Parts[i] || 0; + + if (v1Part > v2Part) return 1; + if (v1Part < v2Part) return -1; + } + + return 0; +} + +const currentVersion = process.version.substring(1); +const targetVersion = "18.17.1"; + +if (compareVersions(currentVersion, targetVersion) >= 0) { + console.log(`Current Node.js version is ${currentVersion}, corepack is supported.`); +} else { + console.error(`Current Node.js version is ${currentVersion}, but corepack is unsupported. Required version: ^${targetVersion}.`); + process.exit(1) +} diff --git a/frontend/scripts/make-i18n-translations.cjs b/frontend/scripts/make-i18n-translations.cjs new file mode 100644 index 0000000000000000000000000000000000000000..948f2a0198922edb4e2eacb1753f33e902fc12e3 --- /dev/null +++ b/frontend/scripts/make-i18n-translations.cjs @@ -0,0 +1,42 @@ +const fs = require("fs"); +const path = require("path"); +const i18n = require("../src/i18n/translation.json"); + +// { [lang]: { [key]: content } } +const translationMap = {}; + +Object.entries(i18n).forEach(([key, transMap]) => { + Object.entries(transMap).forEach(([lang, content]) => { + if (!translationMap[lang]) { + translationMap[lang] = {}; + } + translationMap[lang][key] = content; + }) +}); + +// remove old locales directory +const localesPath = path.join(__dirname, "../public/locales"); +if (fs.existsSync(localesPath)) { + fs.rmSync(localesPath, { recursive: true }); +} + +// write translation files +Object.entries(translationMap).forEach(([lang, transMap]) => { + const filePath = path.join(__dirname, `../public/locales/${lang}/translation.json`); + if (!fs.existsSync(filePath)) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + } + fs.writeFileSync(filePath, JSON.stringify(transMap, null, 2)); +}); + +// write translation key enum +const transKeys = Object.keys(translationMap.en); +const transKeyDeclareFilePath = path.join(__dirname, "../src/i18n/declaration.ts"); +if (!fs.existsSync(transKeyDeclareFilePath)) { + fs.mkdirSync(path.dirname(transKeyDeclareFilePath), { recursive: true }); +} +fs.writeFileSync(transKeyDeclareFilePath, ` +// this file generate by script, don't modify it manually!!! +export enum I18nKey { +${transKeys.map(key => ` ${key} = "${key}",`).join('\n')} +}`.trim() + '\n'); diff --git a/frontend/src/api/api-keys.ts b/frontend/src/api/api-keys.ts new file mode 100644 index 0000000000000000000000000000000000000000..cfcad2fede69eadd0ecac86641b2c122edc62365 --- /dev/null +++ b/frontend/src/api/api-keys.ts @@ -0,0 +1,49 @@ +import { openHands } from "./open-hands-axios"; + +export interface ApiKey { + id: string; + name: string; + prefix: string; + created_at: string; + last_used_at: string | null; +} + +export interface CreateApiKeyResponse { + id: string; + name: string; + key: string; // Full key, only returned once upon creation + prefix: string; + created_at: string; +} + +class ApiKeysClient { + /** + * Get all API keys for the current user + */ + static async getApiKeys(): Promise { + const { data } = await openHands.get("/api/keys"); + // Ensure we always return an array, even if the API returns something else + return Array.isArray(data) ? (data as ApiKey[]) : []; + } + + /** + * Create a new API key + * @param name - A descriptive name for the API key + */ + static async createApiKey(name: string): Promise { + const { data } = await openHands.post("/api/keys", { + name, + }); + return data; + } + + /** + * Delete an API key + * @param id - The ID of the API key to delete + */ + static async deleteApiKey(id: string): Promise { + await openHands.delete(`/api/keys/${id}`); + } +} + +export default ApiKeysClient; diff --git a/frontend/src/api/conversation.utils.ts b/frontend/src/api/conversation.utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..cd1db7d34d67079817aed53616713bec221a4b4c --- /dev/null +++ b/frontend/src/api/conversation.utils.ts @@ -0,0 +1,9 @@ +import OpenHands from "#/api/open-hands"; + +/** + * Returns a URL compatible for the file service + * @param conversationId ID of the conversation + * @returns URL of the conversation + */ +export const getConversationUrl = (conversationId: string) => + OpenHands.getConversationUrl(conversationId); diff --git a/frontend/src/api/file-service/file-service.api.ts b/frontend/src/api/file-service/file-service.api.ts new file mode 100644 index 0000000000000000000000000000000000000000..3899251b1023dc5058d51473a52c9ed6b834e805 --- /dev/null +++ b/frontend/src/api/file-service/file-service.api.ts @@ -0,0 +1,38 @@ +import { openHands } from "../open-hands-axios"; +import { GetFilesResponse, GetFileResponse } from "./file-service.types"; +import { getConversationUrl } from "../conversation.utils"; + +export class FileService { + /** + * Retrieve the list of files available in the workspace + * @param conversationId ID of the conversation + * @param path Path to list files from. If provided, it lists all the files in the given path + * @returns List of files available in the given path. If path is not provided, it lists all the files in the workspace + */ + static async getFiles( + conversationId: string, + path?: string, + ): Promise { + const url = `${getConversationUrl(conversationId)}/list-files`; + const { data } = await openHands.get(url, { + params: { path }, + }); + + return data; + } + + /** + * Retrieve the content of a file + * @param conversationId ID of the conversation + * @param path Full path of the file to retrieve + * @returns Code content of the file + */ + static async getFile(conversationId: string, path: string): Promise { + const url = `${getConversationUrl(conversationId)}/select-file`; + const { data } = await openHands.get(url, { + params: { file: path }, + }); + + return data.code; + } +} diff --git a/frontend/src/api/file-service/file-service.types.ts b/frontend/src/api/file-service/file-service.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ccbe46faa0ef192063ee0a5fbf979f3e61cf62a --- /dev/null +++ b/frontend/src/api/file-service/file-service.types.ts @@ -0,0 +1,5 @@ +export type GetFilesResponse = string[]; + +export interface GetFileResponse { + code: string; +} diff --git a/frontend/src/api/invariant-service.ts b/frontend/src/api/invariant-service.ts new file mode 100644 index 0000000000000000000000000000000000000000..324aa4ea86d1001abe70a79664229f7ff892fb7c --- /dev/null +++ b/frontend/src/api/invariant-service.ts @@ -0,0 +1,30 @@ +import { openHands } from "./open-hands-axios"; + +class InvariantService { + static async getPolicy() { + const { data } = await openHands.get("/api/security/policy"); + return data.policy; + } + + static async getRiskSeverity() { + const { data } = await openHands.get("/api/security/settings"); + return data.RISK_SEVERITY; + } + + static async getTraces() { + const { data } = await openHands.get("/api/security/export-trace"); + return data; + } + + static async updatePolicy(policy: string) { + await openHands.post("/api/security/policy", { policy }); + } + + static async updateRiskSeverity(riskSeverity: number) { + await openHands.post("/api/security/settings", { + RISK_SEVERITY: riskSeverity, + }); + } +} + +export default InvariantService; diff --git a/frontend/src/api/open-hands-axios.ts b/frontend/src/api/open-hands-axios.ts new file mode 100644 index 0000000000000000000000000000000000000000..85ebb764d4591f03fdba247acecf9857ce45f663 --- /dev/null +++ b/frontend/src/api/open-hands-axios.ts @@ -0,0 +1,60 @@ +import axios, { AxiosError, AxiosResponse } from "axios"; + +export const openHands = axios.create({ + baseURL: `${window.location.protocol}//${import.meta.env.VITE_BACKEND_BASE_URL || window?.location.host}`, +}); + +// Helper function to check if a response contains an email verification error +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const checkForEmailVerificationError = (data: any): boolean => { + const EMAIL_NOT_VERIFIED = "EmailNotVerifiedError"; + + if (typeof data === "string") { + return data.includes(EMAIL_NOT_VERIFIED); + } + + if (typeof data === "object" && data !== null) { + if ("message" in data) { + const { message } = data; + if (typeof message === "string") { + return message.includes(EMAIL_NOT_VERIFIED); + } + if (Array.isArray(message)) { + return message.some( + (msg) => typeof msg === "string" && msg.includes(EMAIL_NOT_VERIFIED), + ); + } + } + + // Search any values in object in case message key is different + return Object.values(data).some( + (value) => + (typeof value === "string" && value.includes(EMAIL_NOT_VERIFIED)) || + (Array.isArray(value) && + value.some( + (v) => typeof v === "string" && v.includes(EMAIL_NOT_VERIFIED), + )), + ); + } + + return false; +}; + +// Set up the global interceptor +openHands.interceptors.response.use( + (response: AxiosResponse) => response, + (error: AxiosError) => { + // Check if it's a 403 error with the email verification message + if ( + error.response?.status === 403 && + checkForEmailVerificationError(error.response?.data) + ) { + if (window.location.pathname !== "/settings/user") { + window.location.reload(); + } + } + + // Continue with the error for other error handlers + return Promise.reject(error); + }, +); diff --git a/frontend/src/api/open-hands.ts b/frontend/src/api/open-hands.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c20f0c302e586fdf31a1d9f513917edc5b90cbf --- /dev/null +++ b/frontend/src/api/open-hands.ts @@ -0,0 +1,398 @@ +import { AxiosHeaders } from "axios"; +import { + Feedback, + FeedbackResponse, + GitHubAccessTokenResponse, + GetConfigResponse, + GetVSCodeUrlResponse, + AuthenticateResponse, + Conversation, + ResultSet, + GetTrajectoryResponse, + GitChangeDiff, + GitChange, +} from "./open-hands.types"; +import { openHands } from "./open-hands-axios"; +import { ApiSettings, PostApiSettings, Provider } from "#/types/settings"; +import { GitUser, GitRepository, Branch } from "#/types/git"; +import { SuggestedTask } from "#/components/features/home/tasks/task.types"; + +class OpenHands { + private static currentConversation: Conversation | null = null; + + /** + * Get a current conversation + * @return the current conversation + */ + static getCurrentConversation(): Conversation | null { + return this.currentConversation; + } + + /** + * Set a current conversation + * @param url Custom URL to use for conversation endpoints + */ + static setCurrentConversation( + currentConversation: Conversation | null, + ): void { + this.currentConversation = currentConversation; + } + + /** + * Get the url for the conversation. If + */ + static getConversationUrl(conversationId: string): string { + if (this.currentConversation?.conversation_id === conversationId) { + if (this.currentConversation.url) { + return this.currentConversation.url; + } + } + return `/api/conversations/${conversationId}`; + } + + /** + * Retrieve the list of models available + * @returns List of models available + */ + static async getModels(): Promise { + const { data } = await openHands.get("/api/options/models"); + return data; + } + + /** + * Retrieve the list of agents available + * @returns List of agents available + */ + static async getAgents(): Promise { + const { data } = await openHands.get("/api/options/agents"); + return data; + } + + /** + * Retrieve the list of security analyzers available + * @returns List of security analyzers available + */ + static async getSecurityAnalyzers(): Promise { + const { data } = await openHands.get( + "/api/options/security-analyzers", + ); + return data; + } + + static async getConfig(): Promise { + const { data } = await openHands.get( + "/api/options/config", + ); + return data; + } + + static getConversationHeaders(): AxiosHeaders { + const headers = new AxiosHeaders(); + const sessionApiKey = this.currentConversation?.session_api_key; + if (sessionApiKey) { + headers.set("X-Session-API-Key", sessionApiKey); + } + return headers; + } + + /** + * Send feedback to the server + * @param data Feedback data + * @returns The stored feedback data + */ + static async submitFeedback( + conversationId: string, + feedback: Feedback, + ): Promise { + const url = `/api/conversations/${conversationId}/submit-feedback`; + const { data } = await openHands.post(url, feedback); + return data; + } + + /** + * Authenticate with GitHub token + * @returns Response with authentication status and user info if successful + */ + static async authenticate( + appMode: GetConfigResponse["APP_MODE"], + ): Promise { + if (appMode === "oss") return true; + + // Just make the request, if it succeeds (no exception thrown), return true + await openHands.post("/api/authenticate"); + return true; + } + + /** + * Get the blob of the workspace zip + * @returns Blob of the workspace zip + */ + static async getWorkspaceZip(conversationId: string): Promise { + const url = `${this.getConversationUrl(conversationId)}/zip-directory`; + const response = await openHands.get(url, { + responseType: "blob", + headers: this.getConversationHeaders(), + }); + return response.data; + } + + /** + * Get the web hosts + * @returns Array of web hosts + */ + static async getWebHosts(conversationId: string): Promise { + const url = `${this.getConversationUrl(conversationId)}/web-hosts`; + const response = await openHands.get(url, { + headers: this.getConversationHeaders(), + }); + return Object.keys(response.data.hosts); + } + + /** + * @param code Code provided by GitHub + * @returns GitHub access token + */ + static async getGitHubAccessToken( + code: string, + ): Promise { + const { data } = await openHands.post( + "/api/keycloak/callback", + { + code, + }, + ); + return data; + } + + /** + * Get the VSCode URL + * @returns VSCode URL + */ + static async getVSCodeUrl( + conversationId: string, + ): Promise { + const url = `${this.getConversationUrl(conversationId)}/vscode-url`; + const { data } = await openHands.get(url, { + headers: this.getConversationHeaders(), + }); + return data; + } + + static async getRuntimeId( + conversationId: string, + ): Promise<{ runtime_id: string }> { + const url = `${this.getConversationUrl(conversationId)}/config`; + const { data } = await openHands.get<{ runtime_id: string }>(url, { + headers: this.getConversationHeaders(), + }); + return data; + } + + static async getUserConversations(): Promise { + const { data } = await openHands.get>( + "/api/conversations?limit=20", + ); + return data.results; + } + + static async deleteUserConversation(conversationId: string): Promise { + await openHands.delete(`/api/conversations/${conversationId}`); + } + + static async createConversation( + selectedRepository?: string, + git_provider?: Provider, + initialUserMsg?: string, + imageUrls?: string[], + replayJson?: string, + suggested_task?: SuggestedTask, + selected_branch?: string, + ): Promise { + const body = { + repository: selectedRepository, + git_provider, + selected_branch, + initial_user_msg: initialUserMsg, + image_urls: imageUrls, + replay_json: replayJson, + suggested_task, + }; + + const { data } = await openHands.post( + "/api/conversations", + body, + ); + + return data; + } + + static async getConversation( + conversationId: string, + ): Promise { + const { data } = await openHands.get( + `/api/conversations/${conversationId}`, + ); + + return data; + } + + static async startConversation( + conversationId: string, + ): Promise { + const { data } = await openHands.post( + `/api/conversations/${conversationId}/start`, + ); + + return data; + } + + static async stopConversation( + conversationId: string, + ): Promise { + const { data } = await openHands.post( + `/api/conversations/${conversationId}/stop`, + ); + + return data; + } + + /** + * Get the settings from the server or use the default settings if not found + */ + static async getSettings(): Promise { + const { data } = await openHands.get("/api/settings"); + return data; + } + + /** + * Save the settings to the server. Only valid settings are saved. + * @param settings - the settings to save + */ + static async saveSettings( + settings: Partial, + ): Promise { + const data = await openHands.post("/api/settings", settings); + return data.status === 200; + } + + static async createCheckoutSession(amount: number): Promise { + const { data } = await openHands.post( + "/api/billing/create-checkout-session", + { + amount, + }, + ); + return data.redirect_url; + } + + static async createBillingSessionResponse(): Promise { + const { data } = await openHands.post( + "/api/billing/create-customer-setup-session", + ); + return data.redirect_url; + } + + static async getBalance(): Promise { + const { data } = await openHands.get<{ credits: string }>( + "/api/billing/credits", + ); + return data.credits; + } + + static async getGitUser(): Promise { + const response = await openHands.get("/api/user/info"); + + const { data } = response; + + const user: GitUser = { + id: data.id, + login: data.login, + avatar_url: data.avatar_url, + company: data.company, + name: data.name, + email: data.email, + }; + + return user; + } + + static async searchGitRepositories( + query: string, + per_page = 5, + ): Promise { + const response = await openHands.get( + "/api/user/search/repositories", + { + params: { + query, + per_page, + }, + }, + ); + + return response.data; + } + + static async getTrajectory( + conversationId: string, + ): Promise { + const url = `${this.getConversationUrl(conversationId)}/trajectory`; + const { data } = await openHands.get(url, { + headers: this.getConversationHeaders(), + }); + return data; + } + + static async logout(appMode: GetConfigResponse["APP_MODE"]): Promise { + const endpoint = + appMode === "saas" ? "/api/logout" : "/api/unset-provider-tokens"; + await openHands.post(endpoint); + } + + static async getGitChanges(conversationId: string): Promise { + const url = `${this.getConversationUrl(conversationId)}/git/changes`; + const { data } = await openHands.get(url, { + headers: this.getConversationHeaders(), + }); + return data; + } + + static async getGitChangeDiff( + conversationId: string, + path: string, + ): Promise { + const url = `${this.getConversationUrl(conversationId)}/git/diff`; + const { data } = await openHands.get(url, { + params: { path }, + headers: this.getConversationHeaders(), + }); + return data; + } + + /** + * Given a PAT, retrieves the repositories of the user + * @returns A list of repositories + */ + static async retrieveUserGitRepositories() { + const { data } = await openHands.get( + "/api/user/repositories", + { + params: { + sort: "pushed", + }, + }, + ); + + return data; + } + + static async getRepositoryBranches(repository: string): Promise { + const { data } = await openHands.get( + `/api/user/repository/branches?repository=${encodeURIComponent(repository)}`, + ); + + return data; + } +} + +export default OpenHands; diff --git a/frontend/src/api/open-hands.types.ts b/frontend/src/api/open-hands.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..15c614b340bdc8bd8a697a1de477273e2da260a8 --- /dev/null +++ b/frontend/src/api/open-hands.types.ts @@ -0,0 +1,104 @@ +import { ProjectStatus } from "#/components/features/conversation-panel/conversation-state-indicator"; + +export interface ErrorResponse { + error: string; +} + +export interface SaveFileSuccessResponse { + message: string; +} + +export interface FileUploadSuccessResponse { + message: string; + uploaded_files: string[]; + skipped_files: { name: string; reason: string }[]; +} + +export interface FeedbackBodyResponse { + message: string; + feedback_id: string; + password: string; +} + +export interface FeedbackResponse { + statusCode: number; + body: FeedbackBodyResponse; +} + +export interface GitHubAccessTokenResponse { + access_token: string; +} + +export interface AuthenticationResponse { + message: string; + login?: string; // Only present when allow list is enabled +} + +export interface Feedback { + version: string; + email: string; + token: string; + polarity: "positive" | "negative"; + permissions: "public" | "private"; + trajectory: unknown[]; +} + +export interface GetConfigResponse { + APP_MODE: "saas" | "oss"; + APP_SLUG?: string; + GITHUB_CLIENT_ID: string; + POSTHOG_CLIENT_KEY: string; + STRIPE_PUBLISHABLE_KEY?: string; + FEATURE_FLAGS: { + ENABLE_BILLING: boolean; + HIDE_LLM_SETTINGS: boolean; + }; +} + +export interface GetVSCodeUrlResponse { + vscode_url: string | null; + error?: string; +} + +export interface GetTrajectoryResponse { + trajectory: unknown[] | null; + error?: string; +} + +export interface AuthenticateResponse { + message?: string; + error?: string; +} + +export type ConversationTrigger = "resolver" | "gui" | "suggested_task"; + +export interface Conversation { + conversation_id: string; + title: string; + selected_repository: string | null; + selected_branch: string | null; + git_provider: string | null; + last_updated_at: string; + created_at: string; + status: ProjectStatus; + trigger?: ConversationTrigger; + url: string | null; + session_api_key: string | null; +} + +export interface ResultSet { + results: T[]; + next_page_id: string | null; +} + +export type GitChangeStatus = "M" | "A" | "D" | "R" | "U"; + +export interface GitChange { + status: GitChangeStatus; + path: string; +} + +export interface GitChangeDiff { + modified: string; + original: string; +} diff --git a/frontend/src/api/secrets-service.ts b/frontend/src/api/secrets-service.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd247ec9bdd20bc8d61dc70452300182bef1ac01 --- /dev/null +++ b/frontend/src/api/secrets-service.ts @@ -0,0 +1,51 @@ +import { openHands } from "./open-hands-axios"; +import { + CustomSecret, + GetSecretsResponse, + POSTProviderTokens, +} from "./secrets-service.types"; +import { Provider, ProviderToken } from "#/types/settings"; + +export class SecretsService { + static async getSecrets() { + const { data } = await openHands.get("/api/secrets"); + return data.custom_secrets; + } + + static async createSecret(name: string, value: string, description?: string) { + const secret: CustomSecret = { + name, + value, + description, + }; + + const { status } = await openHands.post("/api/secrets", secret); + return status === 201; + } + + static async updateSecret(id: string, name: string, description?: string) { + const secret: Omit = { + name, + description, + }; + + const { status } = await openHands.put(`/api/secrets/${id}`, secret); + return status === 200; + } + + static async deleteSecret(id: string) { + const { status } = await openHands.delete(`/api/secrets/${id}`); + return status === 200; + } + + static async addGitProvider(providers: Record) { + const tokens: POSTProviderTokens = { + provider_tokens: providers, + }; + const { data } = await openHands.post( + "/api/add-git-providers", + tokens, + ); + return data; + } +} diff --git a/frontend/src/api/secrets-service.types.ts b/frontend/src/api/secrets-service.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..888d1a1feaf5b4537483141cb369c26a70a08a48 --- /dev/null +++ b/frontend/src/api/secrets-service.types.ts @@ -0,0 +1,15 @@ +import { Provider, ProviderToken } from "#/types/settings"; + +export type CustomSecret = { + name: string; + value: string; + description?: string; +}; + +export interface GetSecretsResponse { + custom_secrets: Omit[]; +} + +export interface POSTProviderTokens { + provider_tokens: Record; +} diff --git a/frontend/src/api/suggestions-service/suggestions-service.api.ts b/frontend/src/api/suggestions-service/suggestions-service.api.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69727877041067ed1a96403d3eb3cde29632911 --- /dev/null +++ b/frontend/src/api/suggestions-service/suggestions-service.api.ts @@ -0,0 +1,9 @@ +import { SuggestedTask } from "#/components/features/home/tasks/task.types"; +import { openHands } from "../open-hands-axios"; + +export class SuggestionsService { + static async getSuggestedTasks(): Promise { + const { data } = await openHands.get("/api/user/suggested-tasks"); + return data; + } +} diff --git a/frontend/src/assets/arrow.tsx b/frontend/src/assets/arrow.tsx new file mode 100644 index 0000000000000000000000000000000000000000..004776be289b82fdadf232a35952c65c77f868e3 --- /dev/null +++ b/frontend/src/assets/arrow.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +function ArrowIcon() { + return ( + + + + ); +} + +export default ArrowIcon; diff --git a/frontend/src/assets/branding/all-hands-logo-spark.svg b/frontend/src/assets/branding/all-hands-logo-spark.svg new file mode 100644 index 0000000000000000000000000000000000000000..00cf1097c0510a08343748c998af53c6a6c7297c --- /dev/null +++ b/frontend/src/assets/branding/all-hands-logo-spark.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/branding/all-hands-logo.svg b/frontend/src/assets/branding/all-hands-logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..ea8249b19fb67ac7e54431f4aae77b09742a8574 --- /dev/null +++ b/frontend/src/assets/branding/all-hands-logo.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/branding/github-logo.svg b/frontend/src/assets/branding/github-logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..8d94fbcb8ffd23511e17e1fff8ce9fe4ac1be897 --- /dev/null +++ b/frontend/src/assets/branding/github-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/branding/gitlab-logo.svg b/frontend/src/assets/branding/gitlab-logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..cd6bff4c3afa3878f51eed5bd5db1c21bf7d16a7 --- /dev/null +++ b/frontend/src/assets/branding/gitlab-logo.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/frontend/src/assets/calendar.tsx b/frontend/src/assets/calendar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..290691d87d3b5fb648ef4149cd89bf75751bcf13 --- /dev/null +++ b/frontend/src/assets/calendar.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +function Calendar() { + return ( + + + + ); +} + +export default Calendar; diff --git a/frontend/src/assets/chevron-left.tsx b/frontend/src/assets/chevron-left.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b2ae9abdde09d81182b4740f26566c52899175fa --- /dev/null +++ b/frontend/src/assets/chevron-left.tsx @@ -0,0 +1,28 @@ +interface ChevronLeftProps { + width?: number; + height?: number; + active?: boolean; +} + +export function ChevronLeft({ + width = 20, + height = 20, + active, +}: ChevronLeftProps) { + return ( + + + + ); +} diff --git a/frontend/src/assets/chevron-right.tsx b/frontend/src/assets/chevron-right.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4d7119d4a99a74dabe353b1fd25009d054530663 --- /dev/null +++ b/frontend/src/assets/chevron-right.tsx @@ -0,0 +1,28 @@ +interface ChevronRightProps { + width?: number; + height?: number; + active?: boolean; +} + +export function ChevronRight({ + width = 20, + height = 20, + active, +}: ChevronRightProps) { + return ( + + + + ); +} diff --git a/frontend/src/assets/cog-tooth.tsx b/frontend/src/assets/cog-tooth.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5131beb534bf7f8b9b29493654e1409d425eaee3 --- /dev/null +++ b/frontend/src/assets/cog-tooth.tsx @@ -0,0 +1,26 @@ +import React from "react"; + +function CogTooth() { + return ( + + + + + ); +} + +export default CogTooth; diff --git a/frontend/src/assets/confirm.tsx b/frontend/src/assets/confirm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2f0ecf003d7a247e85af103687cb6493c822b1c7 --- /dev/null +++ b/frontend/src/assets/confirm.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +function ConfirmIcon() { + return ( + + + + ); +} + +export default ConfirmIcon; diff --git a/frontend/src/assets/earth.tsx b/frontend/src/assets/earth.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9bb022364e5121d63cead5734c437f6c483744e6 --- /dev/null +++ b/frontend/src/assets/earth.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +function Earth() { + return ( + + + + ); +} + +export default Earth; diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0de1b61ff4a4ffca3f519bab87444dbb5934c3fd --- /dev/null +++ b/frontend/src/assets/logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3aaa26c8bead0446230853a2d19376f73427768f7f1f26379eab65008178972 +size 395586 diff --git a/frontend/src/assets/notification.mp3 b/frontend/src/assets/notification.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7937b7c56671f5c5d166dfea8cd1875c8857da35 Binary files /dev/null and b/frontend/src/assets/notification.mp3 differ diff --git a/frontend/src/assets/pause.tsx b/frontend/src/assets/pause.tsx new file mode 100644 index 0000000000000000000000000000000000000000..06b5034c159ea5d82dcee2bb327ffbffb61a2bb1 --- /dev/null +++ b/frontend/src/assets/pause.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +function PauseIcon() { + return ( + + + + ); +} + +export default PauseIcon; diff --git a/frontend/src/assets/pencil.tsx b/frontend/src/assets/pencil.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0fab58444097d241d11cf01809e7f8c9b41390ca --- /dev/null +++ b/frontend/src/assets/pencil.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +function Pencil() { + return ( + + + + ); +} + +export default Pencil; diff --git a/frontend/src/assets/play.tsx b/frontend/src/assets/play.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8e1e83682233230505bbebe8a06ae2c8adf51ea0 --- /dev/null +++ b/frontend/src/assets/play.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +function PlayIcon() { + return ( + + + + ); +} + +export default PlayIcon; diff --git a/frontend/src/assets/reject.tsx b/frontend/src/assets/reject.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5400aa079b9653781100d0cfc22baacaba3d8f4f --- /dev/null +++ b/frontend/src/assets/reject.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +function RejectIcon() { + return ( + + + + ); +} + +export default RejectIcon; diff --git a/frontend/src/assets/stop.tsx b/frontend/src/assets/stop.tsx new file mode 100644 index 0000000000000000000000000000000000000000..425b98460121c5ae9c6728590909ed322de3eeba --- /dev/null +++ b/frontend/src/assets/stop.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +function StopIcon() { + return ( + + + + ); +} + +export default StopIcon; diff --git a/frontend/src/assets/vscode-alt.svg b/frontend/src/assets/vscode-alt.svg new file mode 100644 index 0000000000000000000000000000000000000000..66699157ddee9c8887be4f6b76378f0c70166f86 --- /dev/null +++ b/frontend/src/assets/vscode-alt.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/agent-status-map.constant.ts b/frontend/src/components/agent-status-map.constant.ts new file mode 100644 index 0000000000000000000000000000000000000000..346186bb1a18ecaf4f981ea41cdf246b85971202 --- /dev/null +++ b/frontend/src/components/agent-status-map.constant.ts @@ -0,0 +1,68 @@ +import { I18nKey } from "#/i18n/declaration"; +import { AgentState } from "#/types/agent-state"; + +export enum IndicatorColor { + BLUE = "bg-blue-500", + GREEN = "bg-green-500", + ORANGE = "bg-orange-500", + YELLOW = "bg-yellow-500", + RED = "bg-red-500", + DARK_ORANGE = "bg-orange-800", +} + +export const AGENT_STATUS_MAP: { + [k: string]: { message: string; indicator: IndicatorColor }; +} = { + [AgentState.INIT]: { + message: I18nKey.CHAT_INTERFACE$AGENT_INIT_MESSAGE, + indicator: IndicatorColor.BLUE, + }, + [AgentState.RUNNING]: { + message: I18nKey.CHAT_INTERFACE$AGENT_RUNNING_MESSAGE, + indicator: IndicatorColor.GREEN, + }, + [AgentState.AWAITING_USER_INPUT]: { + message: I18nKey.CHAT_INTERFACE$AGENT_AWAITING_USER_INPUT_MESSAGE, + indicator: IndicatorColor.BLUE, + }, + [AgentState.PAUSED]: { + message: I18nKey.CHAT_INTERFACE$AGENT_PAUSED_MESSAGE, + indicator: IndicatorColor.YELLOW, + }, + [AgentState.LOADING]: { + message: I18nKey.CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE, + indicator: IndicatorColor.DARK_ORANGE, + }, + [AgentState.STOPPED]: { + message: I18nKey.CHAT_INTERFACE$AGENT_STOPPED_MESSAGE, + indicator: IndicatorColor.RED, + }, + [AgentState.FINISHED]: { + message: I18nKey.CHAT_INTERFACE$AGENT_FINISHED_MESSAGE, + indicator: IndicatorColor.GREEN, + }, + [AgentState.REJECTED]: { + message: I18nKey.CHAT_INTERFACE$AGENT_REJECTED_MESSAGE, + indicator: IndicatorColor.YELLOW, + }, + [AgentState.ERROR]: { + message: I18nKey.CHAT_INTERFACE$AGENT_ERROR_MESSAGE, + indicator: IndicatorColor.RED, + }, + [AgentState.AWAITING_USER_CONFIRMATION]: { + message: I18nKey.CHAT_INTERFACE$AGENT_AWAITING_USER_CONFIRMATION_MESSAGE, + indicator: IndicatorColor.ORANGE, + }, + [AgentState.USER_CONFIRMED]: { + message: I18nKey.CHAT_INTERFACE$AGENT_ACTION_USER_CONFIRMED_MESSAGE, + indicator: IndicatorColor.GREEN, + }, + [AgentState.USER_REJECTED]: { + message: I18nKey.CHAT_INTERFACE$AGENT_ACTION_USER_REJECTED_MESSAGE, + indicator: IndicatorColor.RED, + }, + [AgentState.RATE_LIMITED]: { + message: I18nKey.CHAT_INTERFACE$AGENT_RATE_LIMITED_MESSAGE, + indicator: IndicatorColor.YELLOW, + }, +}; diff --git a/frontend/src/components/extension-icon-map.constant.tsx b/frontend/src/components/extension-icon-map.constant.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4c6a2149b28178c4e9e224d18d1e2bdcffb15a19 --- /dev/null +++ b/frontend/src/components/extension-icon-map.constant.tsx @@ -0,0 +1,20 @@ +import { DiJavascript } from "react-icons/di"; +import { + FaCss3, + FaHtml5, + FaList, + FaMarkdown, + FaNpm, + FaPython, +} from "react-icons/fa"; + +export const EXTENSION_ICON_MAP: Record = { + js: , + ts: , + py: , + css: , + json: , + npmignore: , + html: , + md: , +}; diff --git a/frontend/src/components/features/analytics/analytics-consent-form-modal.tsx b/frontend/src/components/features/analytics/analytics-consent-form-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3fadc4dd81fbb9635ba4ee6c579805c8a6553278 --- /dev/null +++ b/frontend/src/components/features/analytics/analytics-consent-form-modal.tsx @@ -0,0 +1,69 @@ +import { useTranslation } from "react-i18next"; +import { + BaseModalTitle, + BaseModalDescription, +} from "#/components/shared/modals/confirmation-modals/base-modal"; +import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop"; +import { ModalBody } from "#/components/shared/modals/modal-body"; +import { useSaveSettings } from "#/hooks/mutation/use-save-settings"; +import { handleCaptureConsent } from "#/utils/handle-capture-consent"; +import { BrandButton } from "../settings/brand-button"; +import { I18nKey } from "#/i18n/declaration"; + +interface AnalyticsConsentFormModalProps { + onClose: () => void; +} + +export function AnalyticsConsentFormModal({ + onClose, +}: AnalyticsConsentFormModalProps) { + const { t } = useTranslation(); + const { mutate: saveUserSettings } = useSaveSettings(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const formData = new FormData(e.currentTarget); + const analytics = formData.get("analytics") === "on"; + + saveUserSettings( + { user_consents_to_analytics: analytics }, + { + onSuccess: () => { + handleCaptureConsent(analytics); + onClose(); + }, + }, + ); + }; + + return ( + +
+ + + + {t(I18nKey.ANALYTICS$DESCRIPTION)} + + + + + + {t(I18nKey.ANALYTICS$CONFIRM_PREFERENCES)} + + +
+
+ ); +} diff --git a/frontend/src/components/features/browser/browser-snapshot.tsx b/frontend/src/components/features/browser/browser-snapshot.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1f9e3670bb7906d5326b4ae35fa40310796651f5 --- /dev/null +++ b/frontend/src/components/features/browser/browser-snapshot.tsx @@ -0,0 +1,19 @@ +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; + +interface BrowserSnaphsotProps { + src: string; +} + +export function BrowserSnapshot({ src }: BrowserSnaphsotProps) { + const { t } = useTranslation(); + + return ( + {t(I18nKey.BROWSER$SCREENSHOT_ALT)} + ); +} diff --git a/frontend/src/components/features/browser/browser.tsx b/frontend/src/components/features/browser/browser.tsx new file mode 100644 index 0000000000000000000000000000000000000000..44e06869ece44c3de5f8fe1a18019592eb3dc3c4 --- /dev/null +++ b/frontend/src/components/features/browser/browser.tsx @@ -0,0 +1,44 @@ +import { useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { RootState } from "#/store"; +import { BrowserSnapshot } from "./browser-snapshot"; +import { EmptyBrowserMessage } from "./empty-browser-message"; +import { useConversationId } from "#/hooks/use-conversation-id"; +import { + initialState as browserInitialState, + setUrl, + setScreenshotSrc, +} from "#/state/browser-slice"; + +export function BrowserPanel() { + const { url, screenshotSrc } = useSelector( + (state: RootState) => state.browser, + ); + const { conversationId } = useConversationId(); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(setUrl(browserInitialState.url)); + dispatch(setScreenshotSrc(browserInitialState.screenshotSrc)); + }, [conversationId]); + + const imgSrc = + screenshotSrc && screenshotSrc.startsWith("data:image/png;base64,") + ? screenshotSrc + : `data:image/png;base64,${screenshotSrc || ""}`; + + return ( +
+
+ {url} +
+
+ {screenshotSrc ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/frontend/src/components/features/browser/empty-browser-message.tsx b/frontend/src/components/features/browser/empty-browser-message.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a4adc9529245549b0706626395cf55d383510677 --- /dev/null +++ b/frontend/src/components/features/browser/empty-browser-message.tsx @@ -0,0 +1,14 @@ +import { useTranslation } from "react-i18next"; +import { IoIosGlobe } from "react-icons/io"; +import { I18nKey } from "#/i18n/declaration"; + +export function EmptyBrowserMessage() { + const { t } = useTranslation(); + + return ( +
+ + {t(I18nKey.BROWSER$NO_PAGE_LOADED)} +
+ ); +} diff --git a/frontend/src/components/features/chat/action-suggestions.tsx b/frontend/src/components/features/chat/action-suggestions.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4bf65c9f95b6d09683f369177be193457ed6b3c0 --- /dev/null +++ b/frontend/src/components/features/chat/action-suggestions.tsx @@ -0,0 +1,83 @@ +import posthog from "posthog-js"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { SuggestionItem } from "#/components/features/suggestions/suggestion-item"; +import { I18nKey } from "#/i18n/declaration"; +import { useUserProviders } from "#/hooks/use-user-providers"; +import { useActiveConversation } from "#/hooks/query/use-active-conversation"; + +interface ActionSuggestionsProps { + onSuggestionsClick: (value: string) => void; +} + +export function ActionSuggestions({ + onSuggestionsClick, +}: ActionSuggestionsProps) { + const { t } = useTranslation(); + const { providers } = useUserProviders(); + const { data: conversation } = useActiveConversation(); + const [hasPullRequest, setHasPullRequest] = React.useState(false); + + const providersAreSet = providers.length > 0; + const isGitLab = providers.includes("gitlab"); + + const pr = isGitLab ? "merge request" : "pull request"; + const prShort = isGitLab ? "MR" : "PR"; + + const terms = { + pr, + prShort, + pushToBranch: `Please push the changes to a remote branch on ${ + isGitLab ? "GitLab" : "GitHub" + }, but do NOT create a ${pr}. Please use the exact SAME branch name as the one you are currently on.`, + createPR: `Please push the changes to ${ + isGitLab ? "GitLab" : "GitHub" + } and open a ${pr}. Please create a meaningful branch name that describes the changes. If a ${pr} template exists in the repository, please follow it when creating the ${prShort} description.`, + pushToPR: `Please push the latest changes to the existing ${pr}.`, + }; + + return ( +
+ {providersAreSet && conversation?.selected_repository && ( +
+ {!hasPullRequest ? ( + <> + { + posthog.capture("push_to_branch_button_clicked"); + onSuggestionsClick(value); + }} + /> + { + posthog.capture("create_pr_button_clicked"); + onSuggestionsClick(value); + setHasPullRequest(true); + }} + /> + + ) : ( + { + posthog.capture("push_to_pr_button_clicked"); + onSuggestionsClick(value); + }} + /> + )} +
+ )} +
+ ); +} diff --git a/frontend/src/components/features/chat/chat-input.tsx b/frontend/src/components/features/chat/chat-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d3964a184ff50547fcb2e2a2fd5d4f1c8191641f --- /dev/null +++ b/frontend/src/components/features/chat/chat-input.tsx @@ -0,0 +1,155 @@ +import React from "react"; +import TextareaAutosize from "react-textarea-autosize"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { cn } from "#/utils/utils"; +import { SubmitButton } from "#/components/shared/buttons/submit-button"; +import { StopButton } from "#/components/shared/buttons/stop-button"; + +interface ChatInputProps { + name?: string; + button?: "submit" | "stop"; + disabled?: boolean; + showButton?: boolean; + value?: string; + maxRows?: number; + onSubmit: (message: string) => void; + onStop?: () => void; + onChange?: (message: string) => void; + onFocus?: () => void; + onBlur?: () => void; + onImagePaste?: (files: File[]) => void; + className?: React.HTMLAttributes["className"]; + buttonClassName?: React.HTMLAttributes["className"]; +} + +export function ChatInput({ + name, + button = "submit", + disabled, + showButton = true, + value, + maxRows = 16, + onSubmit, + onStop, + onChange, + onFocus, + onBlur, + onImagePaste, + className, + buttonClassName, +}: ChatInputProps) { + const { t } = useTranslation(); + const textareaRef = React.useRef(null); + const [isDraggingOver, setIsDraggingOver] = React.useState(false); + + const handlePaste = (event: React.ClipboardEvent) => { + // Only handle paste if we have an image paste handler and there are files + if (onImagePaste && event.clipboardData.files.length > 0) { + const files = Array.from(event.clipboardData.files).filter((file) => + file.type.startsWith("image/"), + ); + // Only prevent default if we found image files to handle + if (files.length > 0) { + event.preventDefault(); + onImagePaste(files); + } + } + // For text paste, let the default behavior handle it + }; + + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); + if (event.dataTransfer.types.includes("Files")) { + setIsDraggingOver(true); + } + }; + + const handleDragLeave = (event: React.DragEvent) => { + event.preventDefault(); + setIsDraggingOver(false); + }; + + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + setIsDraggingOver(false); + if (onImagePaste && event.dataTransfer.files.length > 0) { + const files = Array.from(event.dataTransfer.files).filter((file) => + file.type.startsWith("image/"), + ); + if (files.length > 0) { + onImagePaste(files); + } + } + }; + + const handleSubmitMessage = () => { + const message = value || textareaRef.current?.value || ""; + if (message.trim()) { + onSubmit(message); + onChange?.(""); + if (textareaRef.current) { + textareaRef.current.value = ""; + } + } + }; + + const handleKeyPress = (event: React.KeyboardEvent) => { + if ( + event.key === "Enter" && + !event.shiftKey && + !disabled && + !event.nativeEvent.isComposing + ) { + event.preventDefault(); + handleSubmitMessage(); + } + }; + + const handleChange = (event: React.ChangeEvent) => { + onChange?.(event.target.value); + }; + + return ( +
+ + {showButton && ( +
+ {button === "submit" && ( + + )} + {button === "stop" && ( + + )} +
+ )} +
+ ); +} diff --git a/frontend/src/components/features/chat/chat-interface.tsx b/frontend/src/components/features/chat/chat-interface.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9cc2c214bc7c1cb539bd0cad5753c435f9afe7be --- /dev/null +++ b/frontend/src/components/features/chat/chat-interface.tsx @@ -0,0 +1,205 @@ +import { useSelector } from "react-redux"; +import React from "react"; +import posthog from "posthog-js"; +import { useParams } from "react-router"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { convertImageToBase64 } from "#/utils/convert-image-to-base-64"; +import { TrajectoryActions } from "../trajectory/trajectory-actions"; +import { createChatMessage } from "#/services/chat-service"; +import { InteractiveChatBox } from "./interactive-chat-box"; +import { RootState } from "#/store"; +import { AgentState } from "#/types/agent-state"; +import { generateAgentStateChangeEvent } from "#/services/agent-state-service"; +import { FeedbackModal } from "../feedback/feedback-modal"; +import { useScrollToBottom } from "#/hooks/use-scroll-to-bottom"; +import { TypingIndicator } from "./typing-indicator"; +import { useWsClient } from "#/context/ws-client-provider"; +import { Messages } from "./messages"; +import { ChatSuggestions } from "./chat-suggestions"; +import { ActionSuggestions } from "./action-suggestions"; + +import { ScrollToBottomButton } from "#/components/shared/buttons/scroll-to-bottom-button"; +import { LoadingSpinner } from "#/components/shared/loading-spinner"; +import { useGetTrajectory } from "#/hooks/mutation/use-get-trajectory"; +import { downloadTrajectory } from "#/utils/download-trajectory"; +import { displayErrorToast } from "#/utils/custom-toast-handlers"; +import { useOptimisticUserMessage } from "#/hooks/use-optimistic-user-message"; +import { useWSErrorMessage } from "#/hooks/use-ws-error-message"; +import { ErrorMessageBanner } from "./error-message-banner"; +import { shouldRenderEvent } from "./event-content-helpers/should-render-event"; + +function getEntryPoint( + hasRepository: boolean | null, + hasReplayJson: boolean | null, +): string { + if (hasRepository) return "github"; + if (hasReplayJson) return "replay"; + return "direct"; +} + +export function ChatInterface() { + const { getErrorMessage } = useWSErrorMessage(); + const { send, isLoadingMessages, parsedEvents } = useWsClient(); + const { setOptimisticUserMessage, getOptimisticUserMessage } = + useOptimisticUserMessage(); + const { t } = useTranslation(); + const scrollRef = React.useRef(null); + const { scrollDomToBottom, onChatBodyScroll, hitBottom } = + useScrollToBottom(scrollRef); + + const { curAgentState } = useSelector((state: RootState) => state.agent); + + const [feedbackPolarity, setFeedbackPolarity] = React.useState< + "positive" | "negative" + >("positive"); + const [feedbackModalIsOpen, setFeedbackModalIsOpen] = React.useState(false); + const [messageToSend, setMessageToSend] = React.useState(null); + const { selectedRepository, replayJson } = useSelector( + (state: RootState) => state.initialQuery, + ); + const params = useParams(); + const { mutate: getTrajectory } = useGetTrajectory(); + + const optimisticUserMessage = getOptimisticUserMessage(); + const errorMessage = getErrorMessage(); + + const events = parsedEvents.filter(shouldRenderEvent); + + const handleSendMessage = async (content: string, files: File[]) => { + if (events.length === 0) { + posthog.capture("initial_query_submitted", { + entry_point: getEntryPoint( + selectedRepository !== null, + replayJson !== null, + ), + query_character_length: content.length, + replay_json_size: replayJson?.length, + }); + } else { + posthog.capture("user_message_sent", { + session_message_count: events.length, + current_message_length: content.length, + }); + } + const promises = files.map((file) => convertImageToBase64(file)); + const imageUrls = await Promise.all(promises); + + const timestamp = new Date().toISOString(); + send(createChatMessage(content, imageUrls, timestamp)); + setOptimisticUserMessage(content); + setMessageToSend(null); + }; + + const handleStop = () => { + posthog.capture("stop_button_clicked"); + send(generateAgentStateChangeEvent(AgentState.STOPPED)); + }; + + const onClickShareFeedbackActionButton = async ( + polarity: "positive" | "negative", + ) => { + setFeedbackModalIsOpen(true); + setFeedbackPolarity(polarity); + }; + + const onClickExportTrajectoryButton = () => { + if (!params.conversationId) { + displayErrorToast(t(I18nKey.CONVERSATION$DOWNLOAD_ERROR)); + return; + } + + getTrajectory(params.conversationId, { + onSuccess: async (data) => { + await downloadTrajectory( + params.conversationId ?? t(I18nKey.CONVERSATION$UNKNOWN), + data.trajectory, + ); + }, + onError: () => { + displayErrorToast(t(I18nKey.CONVERSATION$DOWNLOAD_ERROR)); + }, + }); + }; + + const isWaitingForUserInput = + curAgentState === AgentState.AWAITING_USER_INPUT || + curAgentState === AgentState.FINISHED; + + return ( +
+ {events.length === 0 && !optimisticUserMessage && ( + + )} + +
onChatBodyScroll(e.currentTarget)} + className="scrollbar scrollbar-thin scrollbar-thumb-gray-400 scrollbar-thumb-rounded-full scrollbar-track-gray-800 hover:scrollbar-thumb-gray-300 flex flex-col grow overflow-y-auto overflow-x-hidden px-4 pt-4 gap-2 fast-smooth-scroll" + > + {isLoadingMessages && ( +
+ +
+ )} + + {!isLoadingMessages && ( + + )} + + {isWaitingForUserInput && + events.length > 0 && + !optimisticUserMessage && ( + handleSendMessage(value, [])} + /> + )} +
+ +
+
+ + onClickShareFeedbackActionButton("positive") + } + onNegativeFeedback={() => + onClickShareFeedbackActionButton("negative") + } + onExportTrajectory={() => onClickExportTrajectoryButton()} + /> + +
+ {curAgentState === AgentState.RUNNING && } +
+ + {!hitBottom && } +
+ + {errorMessage && } + + +
+ + setFeedbackModalIsOpen(false)} + polarity={feedbackPolarity} + /> +
+ ); +} diff --git a/frontend/src/components/features/chat/chat-message.tsx b/frontend/src/components/features/chat/chat-message.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6b2cf58516569e9f1a77a9288bfeeebeedb80607 --- /dev/null +++ b/frontend/src/components/features/chat/chat-message.tsx @@ -0,0 +1,79 @@ +import React from "react"; +import Markdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { code } from "../markdown/code"; +import { cn } from "#/utils/utils"; +import { ul, ol } from "../markdown/list"; +import { CopyToClipboardButton } from "#/components/shared/buttons/copy-to-clipboard-button"; +import { anchor } from "../markdown/anchor"; +import { OpenHandsSourceType } from "#/types/core/base"; +import { paragraph } from "../markdown/paragraph"; + +interface ChatMessageProps { + type: OpenHandsSourceType; + message: string; +} + +export function ChatMessage({ + type, + message, + children, +}: React.PropsWithChildren) { + const [isHovering, setIsHovering] = React.useState(false); + const [isCopy, setIsCopy] = React.useState(false); + + const handleCopyToClipboard = async () => { + await navigator.clipboard.writeText(message); + setIsCopy(true); + }; + + React.useEffect(() => { + let timeout: NodeJS.Timeout; + + if (isCopy) { + timeout = setTimeout(() => { + setIsCopy(false); + }, 2000); + } + + return () => { + clearTimeout(timeout); + }; + }, [isCopy]); + + return ( +
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + className={cn( + "rounded-xl relative", + "flex flex-col gap-2", + type === "user" && " max-w-[305px] p-4 bg-tertiary self-end", + type === "agent" && "mt-6 max-w-full bg-transparent", + )} + > + +
+ + {message} + +
+ {children} +
+ ); +} diff --git a/frontend/src/components/features/chat/chat-suggestions.tsx b/frontend/src/components/features/chat/chat-suggestions.tsx new file mode 100644 index 0000000000000000000000000000000000000000..397f1b14867c0195c34cf29c5934086d58708897 --- /dev/null +++ b/frontend/src/components/features/chat/chat-suggestions.tsx @@ -0,0 +1,33 @@ +import { useTranslation } from "react-i18next"; +import { Suggestions } from "#/components/features/suggestions/suggestions"; +import { I18nKey } from "#/i18n/declaration"; +import BuildIt from "#/icons/build-it.svg?react"; +import { SUGGESTIONS } from "#/utils/suggestions"; + +interface ChatSuggestionsProps { + onSuggestionsClick: (value: string) => void; +} + +export function ChatSuggestions({ onSuggestionsClick }: ChatSuggestionsProps) { + const { t } = useTranslation(); + + return ( +
+
+ + + {t(I18nKey.LANDING$TITLE)} + +
+ ({ + label, + value, + }))} + onSuggestionClick={onSuggestionsClick} + /> +
+ ); +} diff --git a/frontend/src/components/features/chat/error-message-banner.tsx b/frontend/src/components/features/chat/error-message-banner.tsx new file mode 100644 index 0000000000000000000000000000000000000000..36876ab7e32733822f23658a55f144b9cd8c97c9 --- /dev/null +++ b/frontend/src/components/features/chat/error-message-banner.tsx @@ -0,0 +1,31 @@ +import { Trans } from "react-i18next"; +import { Link } from "react-router"; +import i18n from "#/i18n"; + +interface ErrorMessageBannerProps { + message: string; +} + +export function ErrorMessageBanner({ message }: ErrorMessageBannerProps) { + return ( +
+ {i18n.exists(message) ? ( + + link + + ), + }} + /> + ) : ( + message + )} +
+ ); +} diff --git a/frontend/src/components/features/chat/error-message.tsx b/frontend/src/components/features/chat/error-message.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9de846b9b7e8877c7ae924d7eb42f6e90307a285 --- /dev/null +++ b/frontend/src/components/features/chat/error-message.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import Markdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { useTranslation } from "react-i18next"; +import { code } from "../markdown/code"; +import { ol, ul } from "../markdown/list"; +import ArrowDown from "#/icons/angle-down-solid.svg?react"; +import ArrowUp from "#/icons/angle-up-solid.svg?react"; +import i18n from "#/i18n"; + +interface ErrorMessageProps { + errorId?: string; + defaultMessage: string; +} + +export function ErrorMessage({ errorId, defaultMessage }: ErrorMessageProps) { + const { t } = useTranslation(); + const [showDetails, setShowDetails] = React.useState(false); + + const hasValidTranslationId = !!errorId && i18n.exists(errorId); + const errorKey = hasValidTranslationId + ? errorId + : "CHAT_INTERFACE$AGENT_ERROR_MESSAGE"; + + return ( +
+
+ {t(errorKey)} + +
+ + {showDetails && ( + + {defaultMessage} + + )} +
+ ); +} diff --git a/frontend/src/components/features/chat/event-content-helpers/get-action-content.ts b/frontend/src/components/features/chat/event-content-helpers/get-action-content.ts new file mode 100644 index 0000000000000000000000000000000000000000..7501b03ee7c27537d9a12e8a6e8f73359098ee25 --- /dev/null +++ b/frontend/src/components/features/chat/event-content-helpers/get-action-content.ts @@ -0,0 +1,125 @@ +import { ActionSecurityRisk } from "#/state/security-analyzer-slice"; +import { + FileWriteAction, + CommandAction, + IPythonAction, + BrowseAction, + BrowseInteractiveAction, + MCPAction, + ThinkAction, + OpenHandsAction, + FinishAction, +} from "#/types/core/actions"; +import { getDefaultEventContent, MAX_CONTENT_LENGTH } from "./shared"; +import i18n from "#/i18n"; + +const getRiskText = (risk: ActionSecurityRisk) => { + switch (risk) { + case ActionSecurityRisk.LOW: + return i18n.t("SECURITY$LOW_RISK"); + case ActionSecurityRisk.MEDIUM: + return i18n.t("SECURITY$MEDIUM_RISK"); + case ActionSecurityRisk.HIGH: + return i18n.t("SECURITY$HIGH_RISK"); + case ActionSecurityRisk.UNKNOWN: + default: + return i18n.t("SECURITY$UNKNOWN_RISK"); + } +}; + +const getWriteActionContent = (event: FileWriteAction): string => { + let { content } = event.args; + if (content.length > MAX_CONTENT_LENGTH) { + content = `${event.args.content.slice(0, MAX_CONTENT_LENGTH)}...`; + } + return `${event.args.path}\n${content}`; +}; + +const getRunActionContent = (event: CommandAction): string => { + let content = `Command:\n\`${event.args.command}\``; + + if (event.args.confirmation_state === "awaiting_confirmation") { + content += `\n\n${getRiskText(event.args.security_risk)}`; + } + + return content; +}; + +const getIPythonActionContent = (event: IPythonAction): string => { + let content = `\`\`\`\n${event.args.code}\n\`\`\``; + + if (event.args.confirmation_state === "awaiting_confirmation") { + content += `\n\n${getRiskText(event.args.security_risk)}`; + } + + return content; +}; + +const getBrowseActionContent = (event: BrowseAction): string => + `Browsing ${event.args.url}`; + +const getBrowseInteractiveActionContent = (event: BrowseInteractiveAction) => + `**Action:**\n\n\`\`\`python\n${event.args.browser_actions}\n\`\`\``; + +const getMcpActionContent = (event: MCPAction): string => { + // Format MCP action with name and arguments + const name = event.args.name || ""; + const args = event.args.arguments || {}; + let details = `**MCP Tool Call:** ${name}\n\n`; + // Include thought if available + if (event.args.thought) { + details += `\n\n**Thought:**\n${event.args.thought}`; + } + details += `\n\n**Arguments:**\n\`\`\`json\n${JSON.stringify(args, null, 2)}\n\`\`\``; + return details; +}; + +const getThinkActionContent = (event: ThinkAction): string => + event.args.thought; + +const getFinishActionContent = (event: FinishAction): string => { + let content = event.args.final_thought; + + switch (event.args.task_completed) { + case "success": + content += `\n\n\n${i18n.t("FINISH$TASK_COMPLETED_SUCCESSFULLY")}`; + break; + case "failure": + content += `\n\n\n${i18n.t("FINISH$TASK_NOT_COMPLETED")}`; + break; + case "partial": + default: + content += `\n\n\n${i18n.t("FINISH$TASK_COMPLETED_PARTIALLY")}`; + break; + } + + return content.trim(); +}; + +const getNoContentActionContent = (): string => ""; + +export const getActionContent = (event: OpenHandsAction): string => { + switch (event.action) { + case "read": + case "edit": + return getNoContentActionContent(); + case "write": + return getWriteActionContent(event); + case "run": + return getRunActionContent(event); + case "run_ipython": + return getIPythonActionContent(event); + case "browse": + return getBrowseActionContent(event); + case "browse_interactive": + return getBrowseInteractiveActionContent(event); + case "call_tool_mcp": + return getMcpActionContent(event); + case "think": + return getThinkActionContent(event); + case "finish": + return getFinishActionContent(event); + default: + return getDefaultEventContent(event); + } +}; diff --git a/frontend/src/components/features/chat/event-content-helpers/get-event-content.tsx b/frontend/src/components/features/chat/event-content-helpers/get-event-content.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ee75007a053ffa5a4f51472213e26766cc5e79d5 --- /dev/null +++ b/frontend/src/components/features/chat/event-content-helpers/get-event-content.tsx @@ -0,0 +1,90 @@ +import { Trans } from "react-i18next"; +import { OpenHandsAction } from "#/types/core/actions"; +import { isOpenHandsAction, isOpenHandsObservation } from "#/types/core/guards"; +import { OpenHandsObservation } from "#/types/core/observations"; +import { MonoComponent } from "../mono-component"; +import { PathComponent } from "../path-component"; +import { getActionContent } from "./get-action-content"; +import { getObservationContent } from "./get-observation-content"; +import i18n from "#/i18n"; + +const hasPathProperty = ( + obj: Record, +): obj is { path: string } => typeof obj.path === "string"; + +const hasCommandProperty = ( + obj: Record, +): obj is { command: string } => typeof obj.command === "string"; + +const trimText = (text: string, maxLength: number): string => { + if (!text) return ""; + return text.length > maxLength ? `${text.substring(0, maxLength)}...` : text; +}; + +export const getEventContent = ( + event: OpenHandsAction | OpenHandsObservation, +) => { + let title: React.ReactNode = ""; + let details: string = ""; + + if (isOpenHandsAction(event)) { + const actionKey = `ACTION_MESSAGE$${event.action.toUpperCase()}`; + + // If translation key exists, use Trans component + if (i18n.exists(actionKey)) { + title = ( + , + cmd: , + }} + /> + ); + } else { + // For generic actions, just use the uppercase type + title = event.action.toUpperCase(); + } + details = getActionContent(event); + } + + if (isOpenHandsObservation(event)) { + const observationKey = `OBSERVATION_MESSAGE$${event.observation.toUpperCase()}`; + + // If translation key exists, use Trans component + if (i18n.exists(observationKey)) { + title = ( + , + cmd: , + }} + /> + ); + } else { + // For generic observations, just use the uppercase type + title = event.observation.toUpperCase(); + } + details = getObservationContent(event); + } + + return { + title: title ?? i18n.t("EVENT$UNKNOWN_EVENT"), + details: details ?? i18n.t("EVENT$UNKNOWN_EVENT"), + }; +}; diff --git a/frontend/src/components/features/chat/event-content-helpers/get-observation-content.ts b/frontend/src/components/features/chat/event-content-helpers/get-observation-content.ts new file mode 100644 index 0000000000000000000000000000000000000000..1481f71b58ecd578654b7354f1251af228ae90ac --- /dev/null +++ b/frontend/src/components/features/chat/event-content-helpers/get-observation-content.ts @@ -0,0 +1,124 @@ +import { + ReadObservation, + CommandObservation, + IPythonObservation, + EditObservation, + BrowseObservation, + OpenHandsObservation, + RecallObservation, +} from "#/types/core/observations"; +import { getObservationResult } from "./get-observation-result"; +import { getDefaultEventContent, MAX_CONTENT_LENGTH } from "./shared"; +import i18n from "#/i18n"; + +const getReadObservationContent = (event: ReadObservation): string => + `\`\`\`\n${event.content}\n\`\`\``; + +const getCommandObservationContent = ( + event: CommandObservation | IPythonObservation, +): string => { + let { content } = event; + if (content.length > MAX_CONTENT_LENGTH) { + content = `${content.slice(0, MAX_CONTENT_LENGTH)}...`; + } + return `Output:\n\`\`\`sh\n${content.trim() || i18n.t("OBSERVATION$COMMAND_NO_OUTPUT")}\n\`\`\``; +}; + +const getEditObservationContent = ( + event: EditObservation, + successMessage: boolean, +): string => { + if (successMessage) { + return `\`\`\`diff\n${event.extras.diff}\n\`\`\``; // Content is already truncated by the ACI + } + return event.content; +}; + +const getBrowseObservationContent = (event: BrowseObservation) => { + let contentDetails = `**URL:** ${event.extras.url}\n`; + if (event.extras.error) { + contentDetails += `\n\n**Error:**\n${event.extras.error}\n`; + } + contentDetails += `\n\n**Output:**\n${event.content}`; + if (contentDetails.length > MAX_CONTENT_LENGTH) { + contentDetails = `${contentDetails.slice(0, MAX_CONTENT_LENGTH)}...(truncated)`; + } + return contentDetails; +}; + +const getRecallObservationContent = (event: RecallObservation): string => { + let content = ""; + + if (event.extras.recall_type === "workspace_context") { + if (event.extras.repo_name) { + content += `\n\n**Repository:** ${event.extras.repo_name}`; + } + if (event.extras.repo_directory) { + content += `\n\n**Directory:** ${event.extras.repo_directory}`; + } + if (event.extras.date) { + content += `\n\n**Date:** ${event.extras.date}`; + } + if ( + event.extras.runtime_hosts && + Object.keys(event.extras.runtime_hosts).length > 0 + ) { + content += `\n\n**Available Hosts**`; + for (const [host, port] of Object.entries(event.extras.runtime_hosts)) { + content += `\n\n- ${host} (port ${port})`; + } + } + if (event.extras.repo_instructions) { + content += `\n\n**Repository Instructions:**\n\n${event.extras.repo_instructions}`; + } + if (event.extras.additional_agent_instructions) { + content += `\n\n**Additional Instructions:**\n\n${event.extras.additional_agent_instructions}`; + } + } + + // Handle microagent knowledge + if ( + event.extras.microagent_knowledge && + event.extras.microagent_knowledge.length > 0 + ) { + content += `\n\n**Triggered Microagent Knowledge:**`; + for (const knowledge of event.extras.microagent_knowledge) { + content += `\n\n- **${knowledge.name}** (triggered by keyword: ${knowledge.trigger})\n\n\`\`\`\n${knowledge.content}\n\`\`\``; + } + } + + if ( + event.extras.custom_secrets_descriptions && + Object.keys(event.extras.custom_secrets_descriptions).length > 0 + ) { + content += `\n\n**Custom Secrets**`; + for (const [name, description] of Object.entries( + event.extras.custom_secrets_descriptions, + )) { + content += `\n\n- $${name}: ${description}`; + } + } + + return content; +}; + +export const getObservationContent = (event: OpenHandsObservation): string => { + switch (event.observation) { + case "read": + return getReadObservationContent(event); + case "edit": + return getEditObservationContent( + event, + getObservationResult(event) === "success", + ); + case "run_ipython": + case "run": + return getCommandObservationContent(event); + case "browse": + return getBrowseObservationContent(event); + case "recall": + return getRecallObservationContent(event); + default: + return getDefaultEventContent(event); + } +}; diff --git a/frontend/src/components/features/chat/event-content-helpers/get-observation-result.ts b/frontend/src/components/features/chat/event-content-helpers/get-observation-result.ts new file mode 100644 index 0000000000000000000000000000000000000000..30504983a0a5625dab76b98bb8c9b546c09d87c6 --- /dev/null +++ b/frontend/src/components/features/chat/event-content-helpers/get-observation-result.ts @@ -0,0 +1,26 @@ +import { OpenHandsObservation } from "#/types/core/observations"; + +export type ObservationResultStatus = "success" | "error" | "timeout"; + +export const getObservationResult = (event: OpenHandsObservation) => { + const hasContent = event.content.length > 0; + const contentIncludesError = event.content.toLowerCase().includes("error:"); + + switch (event.observation) { + case "run": { + const exitCode = event.extras.metadata.exit_code; + + if (exitCode === -1) return "timeout"; // Command timed out + if (exitCode === 0) return "success"; // Command executed successfully + return "error"; // Command failed + } + case "run_ipython": + case "read": + case "edit": + case "mcp": + if (!hasContent || contentIncludesError) return "error"; + return "success"; // Content is valid + default: + return "success"; + } +}; diff --git a/frontend/src/components/features/chat/event-content-helpers/shared.ts b/frontend/src/components/features/chat/event-content-helpers/shared.ts new file mode 100644 index 0000000000000000000000000000000000000000..6dbce5b2a762abbf2d3ebd26bbf570754d116396 --- /dev/null +++ b/frontend/src/components/features/chat/event-content-helpers/shared.ts @@ -0,0 +1,8 @@ +import { OpenHandsAction } from "#/types/core/actions"; +import { OpenHandsObservation } from "#/types/core/observations"; + +export const MAX_CONTENT_LENGTH = 1000; + +export const getDefaultEventContent = ( + event: OpenHandsAction | OpenHandsObservation, +): string => `\`\`\`json\n${JSON.stringify(event, null, 2)}\n\`\`\``; diff --git a/frontend/src/components/features/chat/event-content-helpers/should-render-event.ts b/frontend/src/components/features/chat/event-content-helpers/should-render-event.ts new file mode 100644 index 0000000000000000000000000000000000000000..e6838a2ab352dce453922ad698e0019f62d32caa --- /dev/null +++ b/frontend/src/components/features/chat/event-content-helpers/should-render-event.ts @@ -0,0 +1,42 @@ +import { OpenHandsAction } from "#/types/core/actions"; +import { OpenHandsEventType } from "#/types/core/base"; +import { + isCommandAction, + isCommandObservation, + isOpenHandsAction, + isOpenHandsObservation, +} from "#/types/core/guards"; +import { OpenHandsObservation } from "#/types/core/observations"; + +const COMMON_NO_RENDER_LIST: OpenHandsEventType[] = [ + "system", + "agent_state_changed", + "change_agent_state", +]; + +const ACTION_NO_RENDER_LIST: OpenHandsEventType[] = ["recall"]; + +export const shouldRenderEvent = ( + event: OpenHandsAction | OpenHandsObservation, +) => { + if (isOpenHandsAction(event)) { + if (isCommandAction(event) && event.source === "user") { + // For user commands, we always hide them from the chat interface + return false; + } + + const noRenderList = COMMON_NO_RENDER_LIST.concat(ACTION_NO_RENDER_LIST); + return !noRenderList.includes(event.action); + } + + if (isOpenHandsObservation(event)) { + if (isCommandObservation(event) && event.source === "user") { + // For user commands, we always hide them from the chat interface + return false; + } + + return !COMMON_NO_RENDER_LIST.includes(event.observation); + } + + return true; +}; diff --git a/frontend/src/components/features/chat/event-message.tsx b/frontend/src/components/features/chat/event-message.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2739872ef2bd7983c9f38aca0f92ff251e8828cf --- /dev/null +++ b/frontend/src/components/features/chat/event-message.tsx @@ -0,0 +1,114 @@ +import { ConfirmationButtons } from "#/components/shared/buttons/confirmation-buttons"; +import { OpenHandsAction } from "#/types/core/actions"; +import { + isUserMessage, + isErrorObservation, + isAssistantMessage, + isOpenHandsAction, + isOpenHandsObservation, + isFinishAction, + isRejectObservation, + isMcpObservation, +} from "#/types/core/guards"; +import { OpenHandsObservation } from "#/types/core/observations"; +import { ImageCarousel } from "../images/image-carousel"; +import { ChatMessage } from "./chat-message"; +import { ErrorMessage } from "./error-message"; +import { MCPObservationContent } from "./mcp-observation-content"; +import { getObservationResult } from "./event-content-helpers/get-observation-result"; +import { getEventContent } from "./event-content-helpers/get-event-content"; +import { GenericEventMessage } from "./generic-event-message"; + +const hasThoughtProperty = ( + obj: Record, +): obj is { thought: string } => "thought" in obj && !!obj.thought; + +interface EventMessageProps { + event: OpenHandsAction | OpenHandsObservation; + hasObservationPair: boolean; + isAwaitingUserConfirmation: boolean; + isLastMessage: boolean; +} + +export function EventMessage({ + event, + hasObservationPair, + isAwaitingUserConfirmation, + isLastMessage, +}: EventMessageProps) { + const shouldShowConfirmationButtons = + isLastMessage && event.source === "agent" && isAwaitingUserConfirmation; + + if (isErrorObservation(event)) { + return ( + + ); + } + + if (hasObservationPair && isOpenHandsAction(event)) { + if (hasThoughtProperty(event.args)) { + return ; + } + return null; + } + + if (isFinishAction(event)) { + return ( + + ); + } + + if (isUserMessage(event) || isAssistantMessage(event)) { + return ( + + {event.args.image_urls && event.args.image_urls.length > 0 && ( + + )} + {shouldShowConfirmationButtons && } + + ); + } + + if (isRejectObservation(event)) { + return ; + } + + if (isMcpObservation(event)) { + return ( +
+ } + success={getObservationResult(event)} + /> + {shouldShowConfirmationButtons && } +
+ ); + } + + return ( +
+ {isOpenHandsAction(event) && hasThoughtProperty(event.args) && ( + + )} + + + + {shouldShowConfirmationButtons && } +
+ ); +} diff --git a/frontend/src/components/features/chat/expandable-message.tsx b/frontend/src/components/features/chat/expandable-message.tsx new file mode 100644 index 0000000000000000000000000000000000000000..34160801307676d42aff0fd216739d93963a4bbb --- /dev/null +++ b/frontend/src/components/features/chat/expandable-message.tsx @@ -0,0 +1,211 @@ +import { PayloadAction } from "@reduxjs/toolkit"; +import { useEffect, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import Markdown from "react-markdown"; +import { Link } from "react-router"; +import remarkGfm from "remark-gfm"; +import { useConfig } from "#/hooks/query/use-config"; +import { I18nKey } from "#/i18n/declaration"; +import ArrowDown from "#/icons/angle-down-solid.svg?react"; +import ArrowUp from "#/icons/angle-up-solid.svg?react"; +import CheckCircle from "#/icons/check-circle-solid.svg?react"; +import XCircle from "#/icons/x-circle-solid.svg?react"; +import { OpenHandsAction } from "#/types/core/actions"; +import { OpenHandsObservation } from "#/types/core/observations"; +import { cn } from "#/utils/utils"; +import { code } from "../markdown/code"; +import { ol, ul } from "../markdown/list"; +import { paragraph } from "../markdown/paragraph"; +import { MonoComponent } from "./mono-component"; +import { PathComponent } from "./path-component"; + +const trimText = (text: string, maxLength: number): string => { + if (!text) return ""; + return text.length > maxLength ? `${text.substring(0, maxLength)}...` : text; +}; + +interface ExpandableMessageProps { + id?: string; + message: string; + type: string; + success?: boolean; + observation?: PayloadAction; + action?: PayloadAction; +} + +export function ExpandableMessage({ + id, + message, + type, + success, + observation, + action, +}: ExpandableMessageProps) { + const { data: config } = useConfig(); + const { t, i18n } = useTranslation(); + const [showDetails, setShowDetails] = useState(true); + const [details, setDetails] = useState(message); + const [translationId, setTranslationId] = useState(id); + const [translationParams, setTranslationParams] = useState< + Record + >({ + observation, + action, + }); + + useEffect(() => { + // If we have a translation ID, process it + if (id && i18n.exists(id)) { + let processedObservation = observation; + let processedAction = action; + + if (action && action.payload.action === "run") { + const trimmedCommand = trimText(action.payload.args.command, 80); + processedAction = { + ...action, + payload: { + ...action.payload, + args: { + ...action.payload.args, + command: trimmedCommand, + }, + }, + }; + } + + if (observation && observation.payload.observation === "run") { + const trimmedCommand = trimText(observation.payload.extras.command, 80); + processedObservation = { + ...observation, + payload: { + ...observation.payload, + extras: { + ...observation.payload.extras, + command: trimmedCommand, + }, + }, + }; + } + + setTranslationId(id); + setTranslationParams({ + observation: processedObservation, + action: processedAction, + }); + setDetails(message); + setShowDetails(false); + } + }, [id, message, observation, action, i18n.language]); + + const statusIconClasses = "h-4 w-4 ml-2 inline"; + + if ( + config?.FEATURE_FLAGS.ENABLE_BILLING && + config?.APP_MODE === "saas" && + id === I18nKey.STATUS$ERROR_LLM_OUT_OF_CREDITS + ) { + return ( +
+
+
+ {t(I18nKey.STATUS$ERROR_LLM_OUT_OF_CREDITS)} +
+ + {t(I18nKey.BILLING$CLICK_TO_TOP_UP)} + +
+
+ ); + } + + return ( +
+
+
+ + {translationId && i18n.exists(translationId) ? ( + , + path: , + cmd: , + }} + /> + ) : ( + message + )} + + + {type === "action" && success !== undefined && ( + + {success ? ( + + ) : ( + + )} + + )} +
+ {showDetails && ( +
+ + {details} + +
+ )} +
+
+ ); +} diff --git a/frontend/src/components/features/chat/generic-event-message.tsx b/frontend/src/components/features/chat/generic-event-message.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0588a1026b12e3bb4f08415954033b9b1fcccf45 --- /dev/null +++ b/frontend/src/components/features/chat/generic-event-message.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import Markdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { code } from "../markdown/code"; +import { ol, ul } from "../markdown/list"; +import ArrowDown from "#/icons/angle-down-solid.svg?react"; +import ArrowUp from "#/icons/angle-up-solid.svg?react"; +import { SuccessIndicator } from "./success-indicator"; +import { ObservationResultStatus } from "./event-content-helpers/get-observation-result"; + +interface GenericEventMessageProps { + title: React.ReactNode; + details: string | React.ReactNode; + success?: ObservationResultStatus; +} + +export function GenericEventMessage({ + title, + details, + success, +}: GenericEventMessageProps) { + const [showDetails, setShowDetails] = React.useState(false); + + return ( +
+
+
+ {title} + {details && ( + + )} +
+ + {success && } +
+ + {showDetails && + (typeof details === "string" ? ( + + {details} + + ) : ( + details + ))} +
+ ); +} diff --git a/frontend/src/components/features/chat/interactive-chat-box.tsx b/frontend/src/components/features/chat/interactive-chat-box.tsx new file mode 100644 index 0000000000000000000000000000000000000000..40b52498aa6a6cc4700598819131c0a5d56d8213 --- /dev/null +++ b/frontend/src/components/features/chat/interactive-chat-box.tsx @@ -0,0 +1,82 @@ +import React from "react"; +import { ChatInput } from "./chat-input"; +import { cn } from "#/utils/utils"; +import { ImageCarousel } from "../images/image-carousel"; +import { UploadImageInput } from "../images/upload-image-input"; + +interface InteractiveChatBoxProps { + isDisabled?: boolean; + mode?: "stop" | "submit"; + onSubmit: (message: string, images: File[]) => void; + onStop: () => void; + value?: string; + onChange?: (message: string) => void; +} + +export function InteractiveChatBox({ + isDisabled, + mode = "submit", + onSubmit, + onStop, + value, + onChange, +}: InteractiveChatBoxProps) { + const [images, setImages] = React.useState([]); + + const handleUpload = (files: File[]) => { + setImages((prevImages) => [...prevImages, ...files]); + }; + + const handleRemoveImage = (index: number) => { + setImages((prevImages) => { + const newImages = [...prevImages]; + newImages.splice(index, 1); + return newImages; + }); + }; + + const handleSubmit = (message: string) => { + onSubmit(message, images); + setImages([]); + if (message) { + onChange?.(""); + } + }; + + return ( +
+ {images.length > 0 && ( + URL.createObjectURL(image))} + onRemove={handleRemoveImage} + /> + )} + +
+ + +
+
+ ); +} diff --git a/frontend/src/components/features/chat/mcp-observation-content.tsx b/frontend/src/components/features/chat/mcp-observation-content.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0eef3b2132b2df7bb9119af182966de2f01d0118 --- /dev/null +++ b/frontend/src/components/features/chat/mcp-observation-content.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import ReactJsonView from "@microlink/react-json-view"; +import { useTranslation } from "react-i18next"; +import { MCPObservation } from "#/types/core/observations"; +import { JSON_VIEW_THEME } from "#/utils/constants"; + +interface MCPObservationContentProps { + event: MCPObservation; +} + +export function MCPObservationContent({ event }: MCPObservationContentProps) { + const { t } = useTranslation(); + + // Parse the content as JSON if possible + let outputData: unknown; + try { + outputData = JSON.parse(event.content); + } catch (e) { + // If parsing fails, use the raw content + outputData = event.content; + } + + const hasArguments = + event.extras.arguments && Object.keys(event.extras.arguments).length > 0; + + return ( +
+ {/* Arguments section */} + {hasArguments && ( +
+
+

+ {t("MCP_OBSERVATION$ARGUMENTS")} +

+
+
+ +
+
+ )} + + {/* Output section */} +
+
+

+ {t("MCP_OBSERVATION$OUTPUT")} +

+
+
+ {typeof outputData === "object" && outputData !== null ? ( + + ) : ( +
+              {event.content.trim() || t("OBSERVATION$MCP_NO_OUTPUT")}
+            
+ )} +
+
+
+ ); +} diff --git a/frontend/src/components/features/chat/messages.tsx b/frontend/src/components/features/chat/messages.tsx new file mode 100644 index 0000000000000000000000000000000000000000..baaf436715d5ffb4d613d299d4de53415b6f953a --- /dev/null +++ b/frontend/src/components/features/chat/messages.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import { OpenHandsAction } from "#/types/core/actions"; +import { OpenHandsObservation } from "#/types/core/observations"; +import { isOpenHandsAction, isOpenHandsObservation } from "#/types/core/guards"; +import { EventMessage } from "./event-message"; +import { ChatMessage } from "./chat-message"; +import { useOptimisticUserMessage } from "#/hooks/use-optimistic-user-message"; + +interface MessagesProps { + messages: (OpenHandsAction | OpenHandsObservation)[]; + isAwaitingUserConfirmation: boolean; +} + +export const Messages: React.FC = React.memo( + ({ messages, isAwaitingUserConfirmation }) => { + const { getOptimisticUserMessage } = useOptimisticUserMessage(); + + const optimisticUserMessage = getOptimisticUserMessage(); + + const actionHasObservationPair = React.useCallback( + (event: OpenHandsAction | OpenHandsObservation): boolean => { + if (isOpenHandsAction(event)) { + return !!messages.some( + (msg) => isOpenHandsObservation(msg) && msg.cause === event.id, + ); + } + + return false; + }, + [messages], + ); + + return ( + <> + {messages.map((message, index) => ( + + ))} + + {optimisticUserMessage && ( + + )} + + ); + }, + (prevProps, nextProps) => { + // Prevent re-renders if messages are the same length + if (prevProps.messages.length !== nextProps.messages.length) { + return false; + } + + return true; + }, +); + +Messages.displayName = "Messages"; diff --git a/frontend/src/components/features/chat/mono-component.tsx b/frontend/src/components/features/chat/mono-component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a279e8554892346fd013637b2c1866893f0117d1 --- /dev/null +++ b/frontend/src/components/features/chat/mono-component.tsx @@ -0,0 +1,37 @@ +import { ReactNode } from "react"; +import EventLogger from "#/utils/event-logger"; + +const decodeHtmlEntities = (text: string): string => { + const textarea = document.createElement("textarea"); + textarea.innerHTML = text; + return textarea.value; +}; + +function MonoComponent(props: { children?: ReactNode }) { + const { children } = props; + + const decodeString = (str: string): string => { + try { + return decodeHtmlEntities(str); + } catch (e) { + EventLogger.error(String(e)); + return str; + } + }; + + if (Array.isArray(children)) { + const processedChildren = children.map((child) => + typeof child === "string" ? decodeString(child) : child, + ); + + return {processedChildren}; + } + + if (typeof children === "string") { + return {decodeString(children)}; + } + + return {children}; +} + +export { MonoComponent }; diff --git a/frontend/src/components/features/chat/path-component.tsx b/frontend/src/components/features/chat/path-component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e431bb900a7f36a9827ab7b8bbdbb1ccfa6f3276 --- /dev/null +++ b/frontend/src/components/features/chat/path-component.tsx @@ -0,0 +1,89 @@ +import { ReactNode } from "react"; +import EventLogger from "#/utils/event-logger"; + +/** + * Decodes HTML entities in a string + * @param text The text to decode + * @returns The decoded text + */ +const decodeHtmlEntities = (text: string): string => { + const textarea = document.createElement("textarea"); + textarea.innerHTML = text; + return textarea.value; +}; + +/** + * Checks if a path is likely a directory + * @param path The full path + * @returns True if the path is likely a directory + */ +const isLikelyDirectory = (path: string): boolean => { + if (!path) return false; + // Check if path already ends with a slash + if (path.endsWith("/") || path.endsWith("\\")) return true; + // Check if path has no extension (simple heuristic) + const lastPart = path.split(/[/\\]/).pop() || ""; + // If the last part has no dots, it's likely a directory + return !lastPart.includes("."); +}; + +/** + * Extracts the filename from a path + * @param path The full path + * @returns The filename (last part of the path) + */ +const extractFilename = (path: string): string => { + if (!path) return ""; + // Handle both Unix and Windows paths + const parts = path.split(/[/\\]/); + const filename = parts[parts.length - 1]; + + // Add trailing slash for directories + if (isLikelyDirectory(path) && !filename.endsWith("/")) { + return `${filename}/`; + } + + return filename; +}; + +/** + * Component that displays only the filename in the text but shows the full path on hover + * Similar to MonoComponent but with path-specific functionality + */ +function PathComponent(props: { children?: ReactNode }) { + const { children } = props; + + const processPath = (path: string) => { + try { + // First decode any HTML entities in the path + const decodedPath = decodeHtmlEntities(path); + // Extract the filename from the decoded path + const filename = extractFilename(decodedPath); + return ( + + {filename} + + ); + } catch (e) { + // Just log the error without any message to avoid localization issues + EventLogger.error(String(e)); + return {path}; + } + }; + + if (Array.isArray(children)) { + const processedChildren = children.map((child) => + typeof child === "string" ? processPath(child) : child, + ); + + return {processedChildren}; + } + + if (typeof children === "string") { + return {processPath(children)}; + } + + return {children}; +} + +export { PathComponent, isLikelyDirectory }; diff --git a/frontend/src/components/features/chat/success-indicator.tsx b/frontend/src/components/features/chat/success-indicator.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4e5ac4779a9c4e90860d7f48aa7206dfc4349e1e --- /dev/null +++ b/frontend/src/components/features/chat/success-indicator.tsx @@ -0,0 +1,35 @@ +import { FaClock } from "react-icons/fa"; +import CheckCircle from "#/icons/check-circle-solid.svg?react"; +import XCircle from "#/icons/x-circle-solid.svg?react"; +import { ObservationResultStatus } from "./event-content-helpers/get-observation-result"; + +interface SuccessIndicatorProps { + status: ObservationResultStatus; +} + +export function SuccessIndicator({ status }: SuccessIndicatorProps) { + return ( + + {status === "success" && ( + + )} + + {status === "error" && ( + + )} + + {status === "timeout" && ( + + )} + + ); +} diff --git a/frontend/src/components/features/chat/typing-indicator.tsx b/frontend/src/components/features/chat/typing-indicator.tsx new file mode 100644 index 0000000000000000000000000000000000000000..99694bcb472ac473564419c59335fe295525317a --- /dev/null +++ b/frontend/src/components/features/chat/typing-indicator.tsx @@ -0,0 +1,18 @@ +export function TypingIndicator() { + return ( +
+ + + +
+ ); +} diff --git a/frontend/src/components/features/context-menu/account-settings-context-menu.tsx b/frontend/src/components/features/context-menu/account-settings-context-menu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..71829ef903755deb37676b2aa5301a708abcef03 --- /dev/null +++ b/frontend/src/components/features/context-menu/account-settings-context-menu.tsx @@ -0,0 +1,30 @@ +import { useTranslation } from "react-i18next"; +import { ContextMenu } from "./context-menu"; +import { ContextMenuListItem } from "./context-menu-list-item"; +import { useClickOutsideElement } from "#/hooks/use-click-outside-element"; +import { I18nKey } from "#/i18n/declaration"; + +interface AccountSettingsContextMenuProps { + onLogout: () => void; + onClose: () => void; +} + +export function AccountSettingsContextMenu({ + onLogout, + onClose, +}: AccountSettingsContextMenuProps) { + const ref = useClickOutsideElement(onClose); + const { t } = useTranslation(); + + return ( + + + {t(I18nKey.ACCOUNT_SETTINGS$LOGOUT)} + + + ); +} diff --git a/frontend/src/components/features/context-menu/context-menu-list-item.tsx b/frontend/src/components/features/context-menu/context-menu-list-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..deb87e1240b11cbc800b5ced7e937fd689af43a7 --- /dev/null +++ b/frontend/src/components/features/context-menu/context-menu-list-item.tsx @@ -0,0 +1,29 @@ +import { cn } from "#/utils/utils"; + +interface ContextMenuListItemProps { + testId?: string; + onClick: (event: React.MouseEvent) => void; + isDisabled?: boolean; +} + +export function ContextMenuListItem({ + children, + testId, + onClick, + isDisabled, +}: React.PropsWithChildren) { + return ( + + ); +} diff --git a/frontend/src/components/features/context-menu/context-menu-separator.tsx b/frontend/src/components/features/context-menu/context-menu-separator.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3bde16acde6699cb1526a2a129b4e1f0df65ebc1 --- /dev/null +++ b/frontend/src/components/features/context-menu/context-menu-separator.tsx @@ -0,0 +1,3 @@ +export function ContextMenuSeparator() { + return
; +} diff --git a/frontend/src/components/features/context-menu/context-menu.tsx b/frontend/src/components/features/context-menu/context-menu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..38a5cc031234d50cde19bd3097520e5436b1f905 --- /dev/null +++ b/frontend/src/components/features/context-menu/context-menu.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { cn } from "#/utils/utils"; + +interface ContextMenuProps { + ref?: React.RefObject; + testId?: string; + children: React.ReactNode; + className?: React.HTMLAttributes["className"]; +} + +export function ContextMenu({ + testId, + children, + className, + ref, +}: ContextMenuProps) { + return ( +
    + {children} +
+ ); +} diff --git a/frontend/src/components/features/controls/agent-control-bar.tsx b/frontend/src/components/features/controls/agent-control-bar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..97749c667b1a93c743879467763d975b73ebb759 --- /dev/null +++ b/frontend/src/components/features/controls/agent-control-bar.tsx @@ -0,0 +1,47 @@ +import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import PauseIcon from "#/assets/pause"; +import PlayIcon from "#/assets/play"; +import { generateAgentStateChangeEvent } from "#/services/agent-state-service"; +import { RootState } from "#/store"; +import { AgentState } from "#/types/agent-state"; +import { useWsClient } from "#/context/ws-client-provider"; +import { IGNORE_TASK_STATE_MAP } from "#/ignore-task-state-map.constant"; +import { ActionButton } from "#/components/shared/buttons/action-button"; + +export function AgentControlBar() { + const { t } = useTranslation(); + const { send } = useWsClient(); + const { curAgentState } = useSelector((state: RootState) => state.agent); + + const handleAction = (action: AgentState) => { + if (!IGNORE_TASK_STATE_MAP[action].includes(curAgentState)) { + send(generateAgentStateChangeEvent(action)); + } + }; + + return ( +
+ + {curAgentState === AgentState.PAUSED ? : } + +
+ ); +} diff --git a/frontend/src/components/features/controls/agent-status-bar.tsx b/frontend/src/components/features/controls/agent-status-bar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4900d9661d1bc27951c9703f2bdec6cbc85e3372 --- /dev/null +++ b/frontend/src/components/features/controls/agent-status-bar.tsx @@ -0,0 +1,117 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { useSelector } from "react-redux"; +import { I18nKey } from "#/i18n/declaration"; +import { showErrorToast } from "#/utils/error-handler"; +import { RootState } from "#/store"; +import { AgentState } from "#/types/agent-state"; +import { + AGENT_STATUS_MAP, + IndicatorColor, +} from "../../agent-status-map.constant"; +import { + useWsClient, + WsClientProviderStatus, +} from "#/context/ws-client-provider"; +import { useNotification } from "#/hooks/useNotification"; +import { browserTab } from "#/utils/browser-tab"; +import { useActiveConversation } from "#/hooks/query/use-active-conversation"; + +const notificationStates = [ + AgentState.AWAITING_USER_INPUT, + AgentState.FINISHED, + AgentState.AWAITING_USER_CONFIRMATION, +]; + +export function AgentStatusBar() { + const { t, i18n } = useTranslation(); + const { curAgentState } = useSelector((state: RootState) => state.agent); + const { curStatusMessage } = useSelector((state: RootState) => state.status); + const { status } = useWsClient(); + const { notify } = useNotification(); + const { data: conversation } = useActiveConversation(); + + const [statusMessage, setStatusMessage] = React.useState(""); + + const updateStatusMessage = () => { + let message = curStatusMessage.message || ""; + if (curStatusMessage?.id) { + const id = curStatusMessage.id.trim(); + if (i18n.exists(id)) { + message = t(curStatusMessage.id.trim()) || message; + } + } + if (curStatusMessage?.type === "error") { + showErrorToast({ + message, + source: "agent-status", + metadata: { ...curStatusMessage }, + }); + return; + } + if (message.trim()) { + setStatusMessage(message); + } else { + setStatusMessage(AGENT_STATUS_MAP[curAgentState].message); + } + }; + + React.useEffect(() => { + updateStatusMessage(); + }, [curStatusMessage.id]); + + // Handle window focus/blur + React.useEffect(() => { + if (typeof window === "undefined") return undefined; + + const handleFocus = () => { + browserTab.stopNotification(); + }; + + window.addEventListener("focus", handleFocus); + return () => { + window.removeEventListener("focus", handleFocus); + browserTab.stopNotification(); + }; + }, []); + + const [indicatorColor, setIndicatorColor] = React.useState( + AGENT_STATUS_MAP[curAgentState].indicator, + ); + + React.useEffect(() => { + if (conversation?.status === "STARTING") { + setStatusMessage(t(I18nKey.STATUS$STARTING_RUNTIME)); + setIndicatorColor(IndicatorColor.RED); + } else if (status === WsClientProviderStatus.DISCONNECTED) { + setStatusMessage(t(I18nKey.STATUS$WEBSOCKET_CLOSED)); + setIndicatorColor(IndicatorColor.RED); + } else { + setStatusMessage(AGENT_STATUS_MAP[curAgentState].message); + setIndicatorColor(AGENT_STATUS_MAP[curAgentState].indicator); + if (notificationStates.includes(curAgentState)) { + const message = t(AGENT_STATUS_MAP[curAgentState].message); + notify(t(AGENT_STATUS_MAP[curAgentState].message), { + body: t(`Agent state changed to ${curAgentState}`), + playSound: true, + }); + + // Update browser tab if window exists and is not focused + if (typeof document !== "undefined" && !document.hasFocus()) { + browserTab.startNotification(message); + } + } + } + }, [curAgentState, status, notify, t, conversation?.status]); + + return ( +
+
+
+ {t(statusMessage)} +
+
+ ); +} diff --git a/frontend/src/components/features/controls/controls.tsx b/frontend/src/components/features/controls/controls.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3afe4cdf3063805c703018e08dcd4f378cd813a7 --- /dev/null +++ b/frontend/src/components/features/controls/controls.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { AgentControlBar } from "./agent-control-bar"; +import { AgentStatusBar } from "./agent-status-bar"; +import { SecurityLock } from "./security-lock"; +import { useActiveConversation } from "#/hooks/query/use-active-conversation"; +import { ConversationCard } from "../conversation-panel/conversation-card"; + +interface ControlsProps { + setSecurityOpen: (isOpen: boolean) => void; + showSecurityLock: boolean; +} + +export function Controls({ setSecurityOpen, showSecurityLock }: ControlsProps) { + const { data: conversation } = useActiveConversation(); + + return ( +
+
+ + + + {showSecurityLock && ( + setSecurityOpen(true)} /> + )} +
+ + +
+ ); +} diff --git a/frontend/src/components/features/controls/security-lock.tsx b/frontend/src/components/features/controls/security-lock.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3fe4c8010abef9a9919eb4030211aed47681ec81 --- /dev/null +++ b/frontend/src/components/features/controls/security-lock.tsx @@ -0,0 +1,17 @@ +import { IoLockClosed } from "react-icons/io5"; + +interface SecurityLockProps { + onClick: () => void; +} + +export function SecurityLock({ onClick }: SecurityLockProps) { + return ( +
+ +
+ ); +} diff --git a/frontend/src/components/features/conversation-panel/confirm-delete-modal.tsx b/frontend/src/components/features/conversation-panel/confirm-delete-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a672022d2682c67f5509bffbe074d75b1d122009 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/confirm-delete-modal.tsx @@ -0,0 +1,57 @@ +import { useTranslation } from "react-i18next"; +import { + BaseModalDescription, + BaseModalTitle, +} from "#/components/shared/modals/confirmation-modals/base-modal"; +import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop"; +import { ModalBody } from "#/components/shared/modals/modal-body"; +import { BrandButton } from "../settings/brand-button"; +import { I18nKey } from "#/i18n/declaration"; + +interface ConfirmDeleteModalProps { + onConfirm: () => void; + onCancel: () => void; +} + +export function ConfirmDeleteModal({ + onConfirm, + onCancel, +}: ConfirmDeleteModalProps) { + const { t } = useTranslation(); + + return ( + + +
+ + +
+
event.stopPropagation()} + > + + {t(I18nKey.ACTION$CONFIRM)} + + + {t(I18nKey.BUTTON$CANCEL)} + +
+
+
+ ); +} diff --git a/frontend/src/components/features/conversation-panel/conversation-card-context-menu.tsx b/frontend/src/components/features/conversation-panel/conversation-card-context-menu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0ee1cd425c5afc1b14e23a70311960fa5831bfb7 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/conversation-card-context-menu.tsx @@ -0,0 +1,73 @@ +import { useClickOutsideElement } from "#/hooks/use-click-outside-element"; +import { cn } from "#/utils/utils"; +import { ContextMenu } from "../context-menu/context-menu"; +import { ContextMenuListItem } from "../context-menu/context-menu-list-item"; + +interface ConversationCardContextMenuProps { + onClose: () => void; + onDelete?: (event: React.MouseEvent) => void; + onEdit?: (event: React.MouseEvent) => void; + onDisplayCost?: (event: React.MouseEvent) => void; + onShowAgentTools?: (event: React.MouseEvent) => void; + onDownloadViaVSCode?: (event: React.MouseEvent) => void; + position?: "top" | "bottom"; +} + +export function ConversationCardContextMenu({ + onClose, + onDelete, + onEdit, + onDisplayCost, + onShowAgentTools, + onDownloadViaVSCode, + position = "bottom", +}: ConversationCardContextMenuProps) { + const ref = useClickOutsideElement(onClose); + + return ( + + {onDelete && ( + + Delete + + )} + {onEdit && ( + + Edit Title + + )} + {onDownloadViaVSCode && ( + + Download via VS Code + + )} + {onDisplayCost && ( + + Display Cost + + )} + {onShowAgentTools && ( + + Show Agent Tools & Metadata + + )} + + ); +} diff --git a/frontend/src/components/features/conversation-panel/conversation-card.tsx b/frontend/src/components/features/conversation-panel/conversation-card.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c3bcf25e2db83a6926a59a6dd2329e97299ba7e8 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/conversation-card.tsx @@ -0,0 +1,372 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import posthog from "posthog-js"; +import { useTranslation } from "react-i18next"; +import { formatTimeDelta } from "#/utils/format-time-delta"; +import { ConversationRepoLink } from "./conversation-repo-link"; +import { + ProjectStatus, + ConversationStateIndicator, +} from "./conversation-state-indicator"; +import { EllipsisButton } from "./ellipsis-button"; +import { ConversationCardContextMenu } from "./conversation-card-context-menu"; +import { SystemMessageModal } from "./system-message-modal"; +import { cn } from "#/utils/utils"; +import { BaseModal } from "../../shared/modals/base-modal/base-modal"; +import { RootState } from "#/store"; +import { I18nKey } from "#/i18n/declaration"; +import { transformVSCodeUrl } from "#/utils/vscode-url-helper"; +import OpenHands from "#/api/open-hands"; +import { useWsClient } from "#/context/ws-client-provider"; +import { isSystemMessage } from "#/types/core/guards"; + +interface ConversationCardProps { + onClick?: () => void; + onDelete?: () => void; + onChangeTitle?: (title: string) => void; + showOptions?: boolean; + isActive?: boolean; + title: string; + selectedRepository: string | null; + lastUpdatedAt: string; // ISO 8601 + createdAt?: string; // ISO 8601 + status?: ProjectStatus; + variant?: "compact" | "default"; + conversationId?: string; // Optional conversation ID for VS Code URL +} + +const MAX_TIME_BETWEEN_CREATION_AND_UPDATE = 1000 * 60 * 30; // 30 minutes + +export function ConversationCard({ + onClick, + onDelete, + onChangeTitle, + showOptions, + isActive, + title, + selectedRepository, + // lastUpdatedAt is kept in props for backward compatibility + // eslint-disable-next-line @typescript-eslint/no-unused-vars + lastUpdatedAt, + createdAt, + status = "STOPPED", + variant = "default", + conversationId, +}: ConversationCardProps) { + const { t } = useTranslation(); + const { parsedEvents } = useWsClient(); + const [contextMenuVisible, setContextMenuVisible] = React.useState(false); + const [titleMode, setTitleMode] = React.useState<"view" | "edit">("view"); + const [metricsModalVisible, setMetricsModalVisible] = React.useState(false); + const [systemModalVisible, setSystemModalVisible] = React.useState(false); + const inputRef = React.useRef(null); + + const systemMessage = parsedEvents.find(isSystemMessage); + + // Subscribe to metrics data from Redux store + const metrics = useSelector((state: RootState) => state.metrics); + + const handleBlur = () => { + if (inputRef.current?.value) { + const trimmed = inputRef.current.value.trim(); + onChangeTitle?.(trimmed); + inputRef.current!.value = trimmed; + } else { + // reset the value if it's empty + inputRef.current!.value = title; + } + + setTitleMode("view"); + }; + + const handleKeyUp = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + event.currentTarget.blur(); + } + }; + + const handleInputClick = (event: React.MouseEvent) => { + if (titleMode === "edit") { + event.preventDefault(); + event.stopPropagation(); + } + }; + + const handleDelete = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + onDelete?.(); + setContextMenuVisible(false); + }; + + const handleEdit = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + setTitleMode("edit"); + setContextMenuVisible(false); + }; + + const handleDownloadViaVSCode = async ( + event: React.MouseEvent, + ) => { + event.preventDefault(); + event.stopPropagation(); + posthog.capture("download_via_vscode_button_clicked"); + + // Fetch the VS Code URL from the API + if (conversationId) { + try { + const data = await OpenHands.getVSCodeUrl(conversationId); + if (data.vscode_url) { + const transformedUrl = transformVSCodeUrl(data.vscode_url); + if (transformedUrl) { + window.open(transformedUrl, "_blank"); + } + } + // VS Code URL not available + } catch (error) { + // Failed to fetch VS Code URL + } + } + + setContextMenuVisible(false); + }; + + const handleDisplayCost = (event: React.MouseEvent) => { + event.stopPropagation(); + setMetricsModalVisible(true); + }; + + const handleShowAgentTools = (event: React.MouseEvent) => { + event.stopPropagation(); + setSystemModalVisible(true); + }; + + React.useEffect(() => { + if (titleMode === "edit") { + inputRef.current?.focus(); + } + }, [titleMode]); + + const hasContextMenu = !!(onDelete || onChangeTitle || showOptions); + const timeBetweenUpdateAndCreation = createdAt + ? new Date(lastUpdatedAt).getTime() - new Date(createdAt).getTime() + : 0; + const showUpdateTime = + createdAt && + timeBetweenUpdateAndCreation > MAX_TIME_BETWEEN_CREATION_AND_UPDATE; + + return ( + <> +
+
+
+ {isActive && ( + + )} + {titleMode === "edit" && ( + + )} + {titleMode === "view" && ( +

+ {title} +

+ )} +
+ +
+ + {hasContextMenu && ( +
+ { + event.preventDefault(); + event.stopPropagation(); + setContextMenuVisible((prev) => !prev); + }} + /> +
+ )} +
+ {contextMenuVisible && ( + setContextMenuVisible(false)} + onDelete={onDelete && handleDelete} + onEdit={onChangeTitle && handleEdit} + onDownloadViaVSCode={ + conversationId && showOptions + ? handleDownloadViaVSCode + : undefined + } + onDisplayCost={showOptions ? handleDisplayCost : undefined} + onShowAgentTools={ + showOptions && systemMessage + ? handleShowAgentTools + : undefined + } + position={variant === "compact" ? "top" : "bottom"} + /> + )} +
+
+
+ +
+ {selectedRepository && ( + + )} +

+ {t(I18nKey.CONVERSATION$CREATED)} + + {showUpdateTime && ( + <> + {t(I18nKey.CONVERSATION$UPDATED)} + + + )} +

+
+
+ + +
+ {(metrics?.cost !== null || metrics?.usage !== null) && ( +
+
+ {metrics?.cost !== null && ( +
+ + {t(I18nKey.CONVERSATION$TOTAL_COST)} + + + ${metrics.cost.toFixed(4)} + +
+ )} + + {metrics?.usage !== null && ( + <> +
+ {t(I18nKey.CONVERSATION$INPUT)} + + {metrics.usage.prompt_tokens.toLocaleString()} + +
+ +
+ Cache Hit: + + {metrics.usage.cache_read_tokens.toLocaleString()} + + Cache Write: + + {metrics.usage.cache_write_tokens.toLocaleString()} + +
+ +
+ {t(I18nKey.CONVERSATION$OUTPUT)} + + {metrics.usage.completion_tokens.toLocaleString()} + +
+ +
+ + {t(I18nKey.CONVERSATION$TOTAL)} + + + {( + metrics.usage.prompt_tokens + + metrics.usage.completion_tokens + ).toLocaleString()} + +
+ +
+
+ + {t(I18nKey.CONVERSATION$CONTEXT_WINDOW)} + +
+
+
+
+
+ + {metrics.usage.per_turn_token.toLocaleString()} /{" "} + {metrics.usage.context_window.toLocaleString()} ( + {( + (metrics.usage.per_turn_token / + metrics.usage.context_window) * + 100 + ).toFixed(2)} + % {t(I18nKey.CONVERSATION$USED)}) + +
+
+ + )} +
+
+ )} + + {!metrics?.cost && !metrics?.usage && ( +
+

+ {t(I18nKey.CONVERSATION$NO_METRICS)} +

+
+ )} +
+ + + setSystemModalVisible(false)} + systemMessage={systemMessage ? systemMessage.args : null} + /> + + ); +} diff --git a/frontend/src/components/features/conversation-panel/conversation-panel-wrapper.tsx b/frontend/src/components/features/conversation-panel/conversation-panel-wrapper.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0e11e6a3256d99dcb960f99e364c55e713cd7ef4 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/conversation-panel-wrapper.tsx @@ -0,0 +1,22 @@ +import ReactDOM from "react-dom"; + +interface ConversationPanelWrapperProps { + isOpen: boolean; +} + +export function ConversationPanelWrapper({ + isOpen, + children, +}: React.PropsWithChildren) { + if (!isOpen) return null; + + const portalTarget = document.getElementById("root-outlet"); + if (!portalTarget) return null; + + return ReactDOM.createPortal( +
+ {children} +
, + portalTarget, + ); +} diff --git a/frontend/src/components/features/conversation-panel/conversation-panel.tsx b/frontend/src/components/features/conversation-panel/conversation-panel.tsx new file mode 100644 index 0000000000000000000000000000000000000000..17a9cb6a6b3ffa5cdf48c5f466e9e2a2886e888f --- /dev/null +++ b/frontend/src/components/features/conversation-panel/conversation-panel.tsx @@ -0,0 +1,121 @@ +import React from "react"; +import { NavLink, useParams, useNavigate } from "react-router"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { ConversationCard } from "./conversation-card"; +import { useUserConversations } from "#/hooks/query/use-user-conversations"; +import { useDeleteConversation } from "#/hooks/mutation/use-delete-conversation"; +import { ConfirmDeleteModal } from "./confirm-delete-modal"; +import { LoadingSpinner } from "#/components/shared/loading-spinner"; +import { ExitConversationModal } from "./exit-conversation-modal"; +import { useClickOutsideElement } from "#/hooks/use-click-outside-element"; + +interface ConversationPanelProps { + onClose: () => void; +} + +export function ConversationPanel({ onClose }: ConversationPanelProps) { + const { t } = useTranslation(); + const { conversationId: currentConversationId } = useParams(); + const ref = useClickOutsideElement(onClose); + const navigate = useNavigate(); + + const [confirmDeleteModalVisible, setConfirmDeleteModalVisible] = + React.useState(false); + const [ + confirmExitConversationModalVisible, + setConfirmExitConversationModalVisible, + ] = React.useState(false); + const [selectedConversationId, setSelectedConversationId] = React.useState< + string | null + >(null); + + const { data: conversations, isFetching, error } = useUserConversations(); + + const { mutate: deleteConversation } = useDeleteConversation(); + + const handleDeleteProject = (conversationId: string) => { + setConfirmDeleteModalVisible(true); + setSelectedConversationId(conversationId); + }; + + const handleConfirmDelete = () => { + if (selectedConversationId) { + deleteConversation( + { conversationId: selectedConversationId }, + { + onSuccess: () => { + if (selectedConversationId === currentConversationId) { + navigate("/"); + } + }, + }, + ); + } + }; + + return ( +
+ {isFetching && ( +
+ +
+ )} + {error && ( +
+

{error.message}

+
+ )} + {conversations?.length === 0 && ( +
+

+ {t(I18nKey.CONVERSATION$NO_CONVERSATIONS)} +

+
+ )} + {conversations?.map((project) => ( + + {({ isActive }) => ( + handleDeleteProject(project.conversation_id)} + title={project.title} + selectedRepository={project.selected_repository} + lastUpdatedAt={project.last_updated_at} + createdAt={project.created_at} + status={project.status} + conversationId={project.conversation_id} + /> + )} + + ))} + + {confirmDeleteModalVisible && ( + { + handleConfirmDelete(); + setConfirmDeleteModalVisible(false); + }} + onCancel={() => setConfirmDeleteModalVisible(false)} + /> + )} + + {confirmExitConversationModalVisible && ( + { + onClose(); + }} + onClose={() => setConfirmExitConversationModalVisible(false)} + /> + )} +
+ ); +} diff --git a/frontend/src/components/features/conversation-panel/conversation-repo-link.tsx b/frontend/src/components/features/conversation-panel/conversation-repo-link.tsx new file mode 100644 index 0000000000000000000000000000000000000000..655a67d53e906cf9be154e1961b928fa91ef03a8 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/conversation-repo-link.tsx @@ -0,0 +1,16 @@ +interface ConversationRepoLinkProps { + selectedRepository: string; +} + +export function ConversationRepoLink({ + selectedRepository, +}: ConversationRepoLinkProps) { + return ( + + {selectedRepository} + + ); +} diff --git a/frontend/src/components/features/conversation-panel/conversation-state-indicator.tsx b/frontend/src/components/features/conversation-panel/conversation-state-indicator.tsx new file mode 100644 index 0000000000000000000000000000000000000000..46191b065481cd4f22440d9adc3b6349f6c42c20 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/conversation-state-indicator.tsx @@ -0,0 +1,27 @@ +import ColdIcon from "./state-indicators/cold.svg?react"; +import RunningIcon from "./state-indicators/running.svg?react"; + +type SVGIcon = React.FunctionComponent>; +export type ProjectStatus = "RUNNING" | "STOPPED" | "STARTING"; + +const INDICATORS: Record = { + STOPPED: ColdIcon, + RUNNING: RunningIcon, + STARTING: ColdIcon, +}; + +interface ConversationStateIndicatorProps { + status: ProjectStatus; +} + +export function ConversationStateIndicator({ + status, +}: ConversationStateIndicatorProps) { + const StateIcon = INDICATORS[status]; + + return ( +
+ +
+ ); +} diff --git a/frontend/src/components/features/conversation-panel/ellipsis-button.tsx b/frontend/src/components/features/conversation-panel/ellipsis-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bd8732f7cd4701e2256b7af96a0b004e94019a5d --- /dev/null +++ b/frontend/src/components/features/conversation-panel/ellipsis-button.tsx @@ -0,0 +1,13 @@ +import { FaEllipsisV } from "react-icons/fa"; + +interface EllipsisButtonProps { + onClick: (event: React.MouseEvent) => void; +} + +export function EllipsisButton({ onClick }: EllipsisButtonProps) { + return ( + + ); +} diff --git a/frontend/src/components/features/conversation-panel/exit-conversation-modal.tsx b/frontend/src/components/features/conversation-panel/exit-conversation-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8eddca456722ac8af996f49167fc793a7889166d --- /dev/null +++ b/frontend/src/components/features/conversation-panel/exit-conversation-modal.tsx @@ -0,0 +1,38 @@ +import { useTranslation } from "react-i18next"; +import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop"; +import { ModalBody } from "#/components/shared/modals/modal-body"; +import { ModalButton } from "#/components/shared/buttons/modal-button"; +import { BaseModalTitle } from "#/components/shared/modals/confirmation-modals/base-modal"; +import { I18nKey } from "#/i18n/declaration"; + +interface ExitConversationModalProps { + onConfirm: () => void; + onClose: () => void; +} + +export function ExitConversationModal({ + onConfirm, + onClose, +}: ExitConversationModalProps) { + const { t } = useTranslation(); + + return ( + + + +
+ + +
+
+
+ ); +} diff --git a/frontend/src/components/features/conversation-panel/state-indicators/cold.svg b/frontend/src/components/features/conversation-panel/state-indicators/cold.svg new file mode 100644 index 0000000000000000000000000000000000000000..95b513851439b27feca9e9b9df83750259349de6 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/state-indicators/cold.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/components/features/conversation-panel/state-indicators/cooling.svg b/frontend/src/components/features/conversation-panel/state-indicators/cooling.svg new file mode 100644 index 0000000000000000000000000000000000000000..ef65bfa11c062e965069ab319805d89545b947ed --- /dev/null +++ b/frontend/src/components/features/conversation-panel/state-indicators/cooling.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/components/features/conversation-panel/state-indicators/finished.svg b/frontend/src/components/features/conversation-panel/state-indicators/finished.svg new file mode 100644 index 0000000000000000000000000000000000000000..311d524d17741655da241d4dbad3f43904cdb398 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/state-indicators/finished.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/components/features/conversation-panel/state-indicators/running.svg b/frontend/src/components/features/conversation-panel/state-indicators/running.svg new file mode 100644 index 0000000000000000000000000000000000000000..5537583da5447e3467d807349217e67ffc1fc507 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/state-indicators/running.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/components/features/conversation-panel/state-indicators/waiting.svg b/frontend/src/components/features/conversation-panel/state-indicators/waiting.svg new file mode 100644 index 0000000000000000000000000000000000000000..a73aa2b27653a41f006fac680677b89bcf3807b6 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/state-indicators/waiting.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/components/features/conversation-panel/state-indicators/warm.svg b/frontend/src/components/features/conversation-panel/state-indicators/warm.svg new file mode 100644 index 0000000000000000000000000000000000000000..e7432e75315de6b2777c8a458d6305fea7b70d2a --- /dev/null +++ b/frontend/src/components/features/conversation-panel/state-indicators/warm.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/components/features/conversation-panel/system-message-modal.tsx b/frontend/src/components/features/conversation-panel/system-message-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c71535a69b763c43de39fb5a42826bf7c8f9954b --- /dev/null +++ b/frontend/src/components/features/conversation-panel/system-message-modal.tsx @@ -0,0 +1,220 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { ChevronDown, ChevronRight } from "lucide-react"; +import ReactJsonView from "@microlink/react-json-view"; +import { BaseModalTitle } from "#/components/shared/modals/confirmation-modals/base-modal"; +import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop"; +import { ModalBody } from "#/components/shared/modals/modal-body"; +import { cn } from "#/utils/utils"; +import { JSON_VIEW_THEME } from "#/utils/constants"; + +interface SystemMessageModalProps { + isOpen: boolean; + onClose: () => void; + systemMessage: { + content: string; + tools: Array> | null; + openhands_version: string | null; + agent_class: string | null; + } | null; +} + +interface FunctionData { + name?: string; + description?: string; + parameters?: Record; +} + +interface ToolData { + type?: string; + function?: FunctionData; + name?: string; + description?: string; + parameters?: Record; +} + +export function SystemMessageModal({ + isOpen, + onClose, + systemMessage, +}: SystemMessageModalProps) { + const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState<"system" | "tools">("system"); + const [expandedTools, setExpandedTools] = useState>( + {}, + ); + + if (!systemMessage) { + return null; + } + + const toggleTool = (index: number) => { + setExpandedTools((prev) => ({ + ...prev, + [index]: !prev[index], + })); + }; + + return ( + isOpen && ( + + +
+ +
+ {systemMessage.agent_class && ( +
+ + {t("SYSTEM_MESSAGE_MODAL$AGENT_CLASS")} + {" "} + + {systemMessage.agent_class} + +
+ )} + {systemMessage.openhands_version && ( +
+ + {t("SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION")} + {" "} + + {systemMessage.openhands_version} + +
+ )} +
+
+ +
+
+ + {systemMessage.tools && systemMessage.tools.length > 0 && ( + + )} +
+ +
+ {activeTab === "system" && ( +
+ {systemMessage.content} +
+ )} + + {activeTab === "tools" && + systemMessage.tools && + systemMessage.tools.length > 0 && ( +
+ {systemMessage.tools.map((tool, index) => { + // Extract function data from the nested structure + const toolData = tool as ToolData; + const functionData = toolData.function || toolData; + const name = + functionData.name || + (toolData.type === "function" && + toolData.function?.name) || + ""; + const description = + functionData.description || + (toolData.type === "function" && + toolData.function?.description) || + ""; + const parameters = + functionData.parameters || + (toolData.type === "function" && + toolData.function?.parameters) || + null; + + const isExpanded = expandedTools[index] || false; + + return ( +
+ + + {isExpanded && ( +
+
+

+ {String(description)} +

+
+ + {/* Parameters section */} + {parameters && ( +
+

+ {t("SYSTEM_MESSAGE_MODAL$PARAMETERS")} +

+
+ +
+
+ )} +
+ )} +
+ ); + })} +
+ )} + + {activeTab === "tools" && + (!systemMessage.tools || systemMessage.tools.length === 0) && ( +
+

+ {t("SYSTEM_MESSAGE_MODAL$NO_TOOLS")} +

+
+ )} +
+
+
+
+ ) + ); +} diff --git a/frontend/src/components/features/diff-viewer/file-diff-viewer.tsx b/frontend/src/components/features/diff-viewer/file-diff-viewer.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3bcd4af109d69573ba5a50b68bd4008378ca7479 --- /dev/null +++ b/frontend/src/components/features/diff-viewer/file-diff-viewer.tsx @@ -0,0 +1,196 @@ +import { DiffEditor, Monaco } from "@monaco-editor/react"; +import React from "react"; +import { editor as editor_t } from "monaco-editor"; +import { LuFileDiff, LuFileMinus, LuFilePlus } from "react-icons/lu"; +import { IconType } from "react-icons/lib"; +import { GitChangeStatus } from "#/api/open-hands.types"; +import { getLanguageFromPath } from "#/utils/get-language-from-path"; +import { cn } from "#/utils/utils"; +import ChevronUp from "#/icons/chveron-up.svg?react"; +import { useGitDiff } from "#/hooks/query/use-get-diff"; + +interface LoadingSpinnerProps { + className?: string; +} + +// TODO: Move out of this file and replace the current spinner with this one +function LoadingSpinner({ className }: LoadingSpinnerProps) { + return ( +
+
+
+ ); +} + +const STATUS_MAP: Record = { + A: LuFilePlus, + D: LuFileMinus, + M: LuFileDiff, + R: "Renamed", + U: "Untracked", +}; + +export interface FileDiffViewerProps { + path: string; + type: GitChangeStatus; +} + +export function FileDiffViewer({ path, type }: FileDiffViewerProps) { + const [isCollapsed, setIsCollapsed] = React.useState(true); + const [editorHeight, setEditorHeight] = React.useState(400); + const diffEditorRef = React.useRef(null); + + const isAdded = type === "A" || type === "U"; + const isDeleted = type === "D"; + + const filePath = React.useMemo(() => { + if (type === "R") { + const parts = path.split(/\s+/).slice(1); + return parts[parts.length - 1]; + } + + return path; + }, [path, type]); + + const { + data: diff, + isLoading, + isSuccess, + isRefetching, + } = useGitDiff({ + filePath, + type, + enabled: !isCollapsed, + }); + + // Function to update editor height based on content + const updateEditorHeight = React.useCallback(() => { + if (diffEditorRef.current) { + const originalEditor = diffEditorRef.current.getOriginalEditor(); + const modifiedEditor = diffEditorRef.current.getModifiedEditor(); + + if (originalEditor && modifiedEditor) { + // Get the content height from both editors and use the larger one + const originalHeight = originalEditor.getContentHeight(); + const modifiedHeight = modifiedEditor.getContentHeight(); + const contentHeight = Math.max(originalHeight, modifiedHeight); + + // Add a small buffer to avoid scrollbar + setEditorHeight(contentHeight + 20); + } + } + }, []); + + const beforeMount = (monaco: Monaco) => { + monaco.editor.defineTheme("custom-diff-theme", { + base: "vs-dark", + inherit: true, + rules: [ + { token: "comment", foreground: "6a9955" }, + { token: "keyword", foreground: "569cd6" }, + { token: "string", foreground: "ce9178" }, + { token: "number", foreground: "b5cea8" }, + ], + colors: { + "diffEditor.insertedTextBackground": "#014b01AA", // Stronger green background + "diffEditor.removedTextBackground": "#750000AA", // Stronger red background + "diffEditor.insertedLineBackground": "#003f00AA", // Dark green for added lines + "diffEditor.removedLineBackground": "#5a0000AA", // Dark red for removed lines + "diffEditor.border": "#444444", // Border between diff editors + + "editorUnnecessaryCode.border": "#00000000", // No border for unnecessary code + "editorUnnecessaryCode.opacity": "#00000077", // Slightly faded + }, + }); + }; + + const handleEditorDidMount = (editor: editor_t.IStandaloneDiffEditor) => { + diffEditorRef.current = editor; + updateEditorHeight(); + + const originalEditor = editor.getOriginalEditor(); + const modifiedEditor = editor.getModifiedEditor(); + + originalEditor.onDidContentSizeChange(updateEditorHeight); + modifiedEditor.onDidContentSizeChange(updateEditorHeight); + }; + + const status = (type === "U" ? STATUS_MAP.A : STATUS_MAP[type]) || "?"; + + let statusIcon: React.ReactNode; + if (typeof status === "string") { + statusIcon = {status}; + } else { + const StatusIcon = status; // now it's recognized as a component + statusIcon = ; + } + + const isFetchingData = isLoading || isRefetching; + + return ( +
+
setIsCollapsed((prev) => !prev)} + > + + {isFetchingData && } + {!isFetchingData && statusIcon} + {filePath} + + +
+ {isSuccess && !isCollapsed && ( +
+ +
+ )} +
+ ); +} diff --git a/frontend/src/components/features/feedback/feedback-form.tsx b/frontend/src/components/features/feedback/feedback-form.tsx new file mode 100644 index 0000000000000000000000000000000000000000..67c4c9762881eb6875725fe330176f80eac31815 --- /dev/null +++ b/frontend/src/components/features/feedback/feedback-form.tsx @@ -0,0 +1,152 @@ +import React from "react"; +import hotToast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { Feedback } from "#/api/open-hands.types"; +import { useSubmitFeedback } from "#/hooks/mutation/use-submit-feedback"; +import { BrandButton } from "../settings/brand-button"; + +const FEEDBACK_VERSION = "1.0"; +const VIEWER_PAGE = "https://www.all-hands.dev/share"; + +interface FeedbackFormProps { + onClose: () => void; + polarity: "positive" | "negative"; +} + +export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) { + const { t } = useTranslation(); + + const copiedToClipboardToast = () => { + hotToast(t(I18nKey.FEEDBACK$PASSWORD_COPIED_MESSAGE), { + icon: "📋", + position: "bottom-right", + }); + }; + + const onPressToast = (password: string) => { + navigator.clipboard.writeText(password); + copiedToClipboardToast(); + }; + + const shareFeedbackToast = ( + message: string, + link: string, + password: string, + ) => { + hotToast( +
+ {message} + onPressToast(password)} + href={link} + target="_blank" + rel="noreferrer" + > + {t(I18nKey.FEEDBACK$GO_TO_FEEDBACK)} + + onPressToast(password)} className="cursor-pointer"> + {t(I18nKey.FEEDBACK$PASSWORD)}: {password}{" "} + + ({t(I18nKey.FEEDBACK$COPY_LABEL)}) + + +
, + { duration: 10000 }, + ); + }; + + const { mutate: submitFeedback, isPending } = useSubmitFeedback(); + + const handleSubmit = async (event: React.FormEvent) => { + event?.preventDefault(); + const formData = new FormData(event.currentTarget); + + const email = formData.get("email")?.toString() || ""; + const permissions = (formData.get("permissions")?.toString() || + "private") as "private" | "public"; + + const feedback: Feedback = { + version: FEEDBACK_VERSION, + email, + polarity, + permissions, + trajectory: [], + token: "", + }; + + submitFeedback( + { feedback }, + { + onSuccess: (data) => { + const { message, feedback_id, password } = data.body; // eslint-disable-line + const link = `${VIEWER_PAGE}?share_id=${feedback_id}`; + shareFeedbackToast(message, link, password); + onClose(); + }, + }, + ); + }; + + return ( +
+ + +
+ + +
+ +
+ + {isPending + ? t(I18nKey.FEEDBACK$SUBMITTING_LABEL) + : t(I18nKey.FEEDBACK$SHARE_LABEL)} + + + {t(I18nKey.FEEDBACK$CANCEL_LABEL)} + +
+ {isPending && ( +

+ {t(I18nKey.FEEDBACK$SUBMITTING_MESSAGE)} +

+ )} +
+ ); +} diff --git a/frontend/src/components/features/feedback/feedback-modal.tsx b/frontend/src/components/features/feedback/feedback-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6f79a1540ab33d56643669f8f60bd5f546eb9f77 --- /dev/null +++ b/frontend/src/components/features/feedback/feedback-modal.tsx @@ -0,0 +1,34 @@ +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { + BaseModalTitle, + BaseModalDescription, +} from "#/components/shared/modals/confirmation-modals/base-modal"; +import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop"; +import { ModalBody } from "#/components/shared/modals/modal-body"; +import { FeedbackForm } from "./feedback-form"; + +interface FeedbackModalProps { + onClose: () => void; + isOpen: boolean; + polarity: "positive" | "negative"; +} + +export function FeedbackModal({ + onClose, + isOpen, + polarity, +}: FeedbackModalProps) { + const { t } = useTranslation(); + if (!isOpen) return null; + + return ( + + + + + + + + ); +} diff --git a/frontend/src/components/features/guards/email-verification-guard.tsx b/frontend/src/components/features/guards/email-verification-guard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b21201650334f228bf1185aa164e3073a76352d2 --- /dev/null +++ b/frontend/src/components/features/guards/email-verification-guard.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { useLocation, useNavigate } from "react-router"; +import { useSettings } from "#/hooks/query/use-settings"; + +/** + * A component that restricts access to routes based on email verification status. + * If EMAIL_VERIFIED is false, only allows access to the /settings/user page. + */ +export function EmailVerificationGuard({ + children, +}: { + children: React.ReactNode; +}) { + const { data: settings, isLoading } = useSettings(); + const navigate = useNavigate(); + const { pathname } = useLocation(); + + React.useEffect(() => { + // If settings are still loading, don't do anything yet + if (isLoading) return; + + // If EMAIL_VERIFIED is explicitly false (not undefined or null) + if (settings?.EMAIL_VERIFIED === false) { + // Allow access to /settings/user but redirect from any other page + if (pathname !== "/settings/user") { + navigate("/settings/user", { replace: true }); + } + } + }, [settings?.EMAIL_VERIFIED, pathname, navigate, isLoading]); + + return children; +} diff --git a/frontend/src/components/features/home/connect-to-provider-message.tsx b/frontend/src/components/features/home/connect-to-provider-message.tsx new file mode 100644 index 0000000000000000000000000000000000000000..04aea14f34927470e4e905e74c76580d75d68e2e --- /dev/null +++ b/frontend/src/components/features/home/connect-to-provider-message.tsx @@ -0,0 +1,21 @@ +import { Link } from "react-router"; +import { useTranslation } from "react-i18next"; +import { BrandButton } from "#/components/features/settings/brand-button"; +import { useSettings } from "#/hooks/query/use-settings"; + +export function ConnectToProviderMessage() { + const { isLoading } = useSettings(); + const { t } = useTranslation(); + + return ( +
+

{t("HOME$CONNECT_PROVIDER_MESSAGE")}

+ + + {!isLoading && t("SETTINGS$TITLE")} + {isLoading && t("HOME$LOADING")} + + +
+ ); +} diff --git a/frontend/src/components/features/home/home-header.tsx b/frontend/src/components/features/home/home-header.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cf04a167ae235248aed9ebd16e4e8089a45cf35d --- /dev/null +++ b/frontend/src/components/features/home/home-header.tsx @@ -0,0 +1,57 @@ +import { useTranslation } from "react-i18next"; +import { useCreateConversation } from "#/hooks/mutation/use-create-conversation"; +import { useIsCreatingConversation } from "#/hooks/use-is-creating-conversation"; +import { BrandButton } from "../settings/brand-button"; +import AllHandsLogo from "#/assets/branding/all-hands-logo-spark.svg?react"; + +export function HomeHeader() { + const { + mutate: createConversation, + isPending, + isSuccess, + } = useCreateConversation(); + const isCreatingConversationElsewhere = useIsCreatingConversation(); + const { t } = useTranslation(); + + // We check for isSuccess because the app might require time to render + // into the new conversation screen after the conversation is created. + const isCreatingConversation = + isPending || isSuccess || isCreatingConversationElsewhere; + + return ( +
+ + +
+

{t("HOME$LETS_START_BUILDING")}

+ createConversation({})} + isDisabled={isCreatingConversation} + > + {!isCreatingConversation && t("HOME$LAUNCH_FROM_SCRATCH")} + {isCreatingConversation && t("HOME$LOADING")} + +
+ +
+

+ {t("HOME$OPENHANDS_DESCRIPTION")} +

+

+ {t("HOME$NOT_SURE_HOW_TO_START")}{" "} + + {t("HOME$READ_THIS")} + +

+
+
+ ); +} diff --git a/frontend/src/components/features/home/repo-connector.tsx b/frontend/src/components/features/home/repo-connector.tsx new file mode 100644 index 0000000000000000000000000000000000000000..92426e6eea1f9aeca6c414f6ab10a66befa1b19f --- /dev/null +++ b/frontend/src/components/features/home/repo-connector.tsx @@ -0,0 +1,35 @@ +import { useTranslation } from "react-i18next"; +import { ConnectToProviderMessage } from "./connect-to-provider-message"; +import { RepositorySelectionForm } from "./repo-selection-form"; +import { useConfig } from "#/hooks/query/use-config"; +import { RepoProviderLinks } from "./repo-provider-links"; +import { useUserProviders } from "#/hooks/use-user-providers"; + +interface RepoConnectorProps { + onRepoSelection: (repoTitle: string | null) => void; +} + +export function RepoConnector({ onRepoSelection }: RepoConnectorProps) { + const { providers } = useUserProviders(); + const { data: config } = useConfig(); + const { t } = useTranslation(); + + const isSaaS = config?.APP_MODE === "saas"; + const providersAreSet = providers.length > 0; + + return ( +
+

{t("HOME$CONNECT_TO_REPOSITORY")}

+ + {!providersAreSet && } + {providersAreSet && ( + + )} + + {isSaaS && providersAreSet && } +
+ ); +} diff --git a/frontend/src/components/features/home/repo-provider-links.tsx b/frontend/src/components/features/home/repo-provider-links.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e7e23b6a7a70d725ff8ead26e8c92071ef97c1f6 --- /dev/null +++ b/frontend/src/components/features/home/repo-provider-links.tsx @@ -0,0 +1,17 @@ +import { useConfig } from "#/hooks/query/use-config"; + +export function RepoProviderLinks() { + const { data: config } = useConfig(); + + const githubHref = config + ? `https://github.com/apps/${config.APP_SLUG}/installations/new` + : ""; + + return ( + + ); +} diff --git a/frontend/src/components/features/home/repo-selection-form.test.tsx b/frontend/src/components/features/home/repo-selection-form.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f97d592035ed4a2468e6822a77eaf10dcdc072be --- /dev/null +++ b/frontend/src/components/features/home/repo-selection-form.test.tsx @@ -0,0 +1,151 @@ +import { render, screen } from "@testing-library/react"; +import { describe, test, expect, vi, beforeEach } from "vitest"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { RepositorySelectionForm } from "./repo-selection-form"; + +// Create mock functions +const mockUseUserRepositories = vi.fn(); +const mockUseRepositoryBranches = vi.fn(); +const mockUseCreateConversation = vi.fn(); +const mockUseIsCreatingConversation = vi.fn(); +const mockUseTranslation = vi.fn(); +const mockUseAuth = vi.fn(); + +// Setup default mock returns +mockUseUserRepositories.mockReturnValue({ + data: [], + isLoading: false, + isError: false, +}); + +mockUseRepositoryBranches.mockReturnValue({ + data: [], + isLoading: false, + isError: false, +}); + +mockUseCreateConversation.mockReturnValue({ + mutate: vi.fn(), + isPending: false, + isSuccess: false, +}); + +mockUseIsCreatingConversation.mockReturnValue(false); + +mockUseTranslation.mockReturnValue({ t: (key: string) => key }); + +mockUseAuth.mockReturnValue({ + isAuthenticated: true, + isLoading: false, + providersAreSet: true, + user: { + id: 1, + login: "testuser", + avatar_url: "https://example.com/avatar.png", + name: "Test User", + email: "test@example.com", + company: "Test Company", + }, + login: vi.fn(), + logout: vi.fn(), +}); + +// Mock the modules +vi.mock("#/hooks/query/use-user-repositories", () => ({ + useUserRepositories: () => mockUseUserRepositories(), +})); + +vi.mock("#/hooks/query/use-repository-branches", () => ({ + useRepositoryBranches: () => mockUseRepositoryBranches(), +})); + +vi.mock("#/hooks/mutation/use-create-conversation", () => ({ + useCreateConversation: () => mockUseCreateConversation(), +})); + +vi.mock("#/hooks/use-is-creating-conversation", () => ({ + useIsCreatingConversation: () => mockUseIsCreatingConversation(), +})); + +vi.mock("react-i18next", () => ({ + useTranslation: () => mockUseTranslation(), +})); + +vi.mock("#/context/auth-context", () => ({ + useAuth: () => mockUseAuth(), +})); + +const renderRepositorySelectionForm = () => + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }); + +describe("RepositorySelectionForm", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + test("shows loading indicator when repositories are being fetched", () => { + // Setup loading state + mockUseUserRepositories.mockReturnValue({ + data: undefined, + isLoading: true, + isError: false, + }); + + renderRepositorySelectionForm(); + + // Check if loading indicator is displayed + expect(screen.getByTestId("repo-dropdown-loading")).toBeInTheDocument(); + expect(screen.getByText("HOME$LOADING_REPOSITORIES")).toBeInTheDocument(); + }); + + test("shows dropdown when repositories are loaded", () => { + // Setup loaded repositories + mockUseUserRepositories.mockReturnValue({ + data: [ + { + id: 1, + full_name: "user/repo1", + git_provider: "github", + is_public: true, + }, + { + id: 2, + full_name: "user/repo2", + git_provider: "github", + is_public: true, + }, + ], + isLoading: false, + isError: false, + }); + + renderRepositorySelectionForm(); + + // Check if dropdown is displayed + expect(screen.getByTestId("repo-dropdown")).toBeInTheDocument(); + }); + + test("shows error message when repository fetch fails", () => { + // Setup error state + mockUseUserRepositories.mockReturnValue({ + data: undefined, + isLoading: false, + isError: true, + error: new Error("Failed to fetch repositories"), + }); + + renderRepositorySelectionForm(); + + // Check if error message is displayed + expect(screen.getByTestId("repo-dropdown-error")).toBeInTheDocument(); + expect( + screen.getByText("HOME$FAILED_TO_LOAD_REPOSITORIES"), + ).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/components/features/home/repo-selection-form.tsx b/frontend/src/components/features/home/repo-selection-form.tsx new file mode 100644 index 0000000000000000000000000000000000000000..03f24c95ada44284b8f3e8e9e3042f87fc9c2c34 --- /dev/null +++ b/frontend/src/components/features/home/repo-selection-form.tsx @@ -0,0 +1,225 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { useCreateConversation } from "#/hooks/mutation/use-create-conversation"; +import { useUserRepositories } from "#/hooks/query/use-user-repositories"; +import { useRepositoryBranches } from "#/hooks/query/use-repository-branches"; +import { useIsCreatingConversation } from "#/hooks/use-is-creating-conversation"; +import { Branch, GitRepository } from "#/types/git"; +import { BrandButton } from "../settings/brand-button"; +import { useSearchRepositories } from "#/hooks/query/use-search-repositories"; +import { useDebounce } from "#/hooks/use-debounce"; +import { sanitizeQuery } from "#/utils/sanitize-query"; +import { + RepositoryDropdown, + RepositoryLoadingState, + RepositoryErrorState, + BranchDropdown, + BranchLoadingState, + BranchErrorState, +} from "./repository-selection"; + +interface RepositorySelectionFormProps { + onRepoSelection: (repoTitle: string | null) => void; +} + +export function RepositorySelectionForm({ + onRepoSelection, +}: RepositorySelectionFormProps) { + const [selectedRepository, setSelectedRepository] = + React.useState(null); + const [selectedBranch, setSelectedBranch] = React.useState( + null, + ); + // Add a ref to track if the branch was manually cleared by the user + const branchManuallyClearedRef = React.useRef(false); + const { + data: repositories, + isLoading: isLoadingRepositories, + isError: isRepositoriesError, + } = useUserRepositories(); + const { + data: branches, + isLoading: isLoadingBranches, + isError: isBranchesError, + } = useRepositoryBranches(selectedRepository?.full_name || null); + const { + mutate: createConversation, + isPending, + isSuccess, + } = useCreateConversation(); + const isCreatingConversationElsewhere = useIsCreatingConversation(); + const { t } = useTranslation(); + + const [searchQuery, setSearchQuery] = React.useState(""); + const debouncedSearchQuery = useDebounce(searchQuery, 300); + const { data: searchedRepos } = useSearchRepositories(debouncedSearchQuery); + + // Auto-select main or master branch if it exists, but only if the branch wasn't manually cleared + React.useEffect(() => { + if ( + branches && + branches.length > 0 && + !selectedBranch && + !isLoadingBranches && + !branchManuallyClearedRef.current // Only auto-select if not manually cleared + ) { + // Look for main or master branch + const mainBranch = branches.find((branch) => branch.name === "main"); + const masterBranch = branches.find((branch) => branch.name === "master"); + + // Select main if it exists, otherwise select master if it exists + if (mainBranch) { + setSelectedBranch(mainBranch); + } else if (masterBranch) { + setSelectedBranch(masterBranch); + } + } + }, [branches, isLoadingBranches, selectedBranch]); + + // We check for isSuccess because the app might require time to render + // into the new conversation screen after the conversation is created. + const isCreatingConversation = + isPending || isSuccess || isCreatingConversationElsewhere; + + const allRepositories = repositories?.concat(searchedRepos || []); + const repositoriesItems = allRepositories?.map((repo) => ({ + key: repo.id, + label: decodeURIComponent(repo.full_name), + })); + + const branchesItems = branches?.map((branch) => ({ + key: branch.name, + label: branch.name, + })); + + const handleRepoSelection = (key: React.Key | null) => { + const selectedRepo = allRepositories?.find( + (repo) => repo.id.toString() === key, + ); + + if (selectedRepo) onRepoSelection(selectedRepo.full_name); + setSelectedRepository(selectedRepo || null); + setSelectedBranch(null); // Reset branch selection when repo changes + branchManuallyClearedRef.current = false; // Reset the flag when repo changes + }; + + const handleBranchSelection = (key: React.Key | null) => { + const selectedBranchObj = branches?.find((branch) => branch.name === key); + setSelectedBranch(selectedBranchObj || null); + // Reset the manually cleared flag when a branch is explicitly selected + branchManuallyClearedRef.current = false; + }; + + const handleRepoInputChange = (value: string) => { + if (value === "") { + setSelectedRepository(null); + setSelectedBranch(null); + onRepoSelection(null); + } else if (value.startsWith("https://")) { + const repoName = sanitizeQuery(value); + setSearchQuery(repoName); + } + }; + + const handleBranchInputChange = (value: string) => { + // Clear the selected branch if the input is empty or contains only whitespace + // This fixes the issue where users can't delete the entire default branch name + if (value === "" || value.trim() === "") { + setSelectedBranch(null); + // Set the flag to indicate that the branch was manually cleared + branchManuallyClearedRef.current = true; + } else { + // Reset the flag when the user starts typing again + branchManuallyClearedRef.current = false; + } + }; + + // Render the appropriate UI based on the loading/error state + const renderRepositorySelector = () => { + if (isLoadingRepositories) { + return ; + } + + if (isRepositoriesError) { + return ; + } + + return ( + { + if (!inputValue) return true; + + const repo = allRepositories?.find((r) => r.full_name === textValue); + if (!repo) return false; + + const sanitizedInput = sanitizeQuery(inputValue); + return sanitizeQuery(textValue).includes(sanitizedInput); + }} + /> + ); + }; + + // Render the appropriate UI for branch selector based on the loading/error state + const renderBranchSelector = () => { + if (!selectedRepository) { + return ( + {}} + onInputChange={() => {}} + isDisabled + /> + ); + } + + if (isLoadingBranches) { + return ; + } + + if (isBranchesError) { + return ; + } + + return ( + + ); + }; + + return ( +
+ {renderRepositorySelector()} + + {renderBranchSelector()} + + + createConversation({ + selectedRepository, + selected_branch: selectedBranch?.name, + }) + } + > + {!isCreatingConversation && "Launch"} + {isCreatingConversation && t("HOME$LOADING")} + +
+ ); +} diff --git a/frontend/src/components/features/home/repository-selection/branch-dropdown.tsx b/frontend/src/components/features/home/repository-selection/branch-dropdown.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8f9221304b06353bafb02a5b06b07a10b5ff2831 --- /dev/null +++ b/frontend/src/components/features/home/repository-selection/branch-dropdown.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { SettingsDropdownInput } from "../../settings/settings-dropdown-input"; + +export interface BranchDropdownProps { + items: { key: React.Key; label: string }[]; + onSelectionChange: (key: React.Key | null) => void; + onInputChange: (value: string) => void; + isDisabled: boolean; + selectedKey?: string; +} + +export function BranchDropdown({ + items, + onSelectionChange, + onInputChange, + isDisabled, + selectedKey, +}: BranchDropdownProps) { + return ( + + ); +} diff --git a/frontend/src/components/features/home/repository-selection/branch-error-state.tsx b/frontend/src/components/features/home/repository-selection/branch-error-state.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f498b9e80d147b4507e37b87e2428ab029e82a76 --- /dev/null +++ b/frontend/src/components/features/home/repository-selection/branch-error-state.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +export function BranchErrorState() { + const { t } = useTranslation(); + return ( +
+ {t("HOME$FAILED_TO_LOAD_BRANCHES")} +
+ ); +} diff --git a/frontend/src/components/features/home/repository-selection/branch-loading-state.tsx b/frontend/src/components/features/home/repository-selection/branch-loading-state.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b5e1fb416b59209c781c66c0b3c7e786b18b4e4c --- /dev/null +++ b/frontend/src/components/features/home/repository-selection/branch-loading-state.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Spinner } from "@heroui/react"; + +export function BranchLoadingState() { + const { t } = useTranslation(); + return ( +
+ + {t("HOME$LOADING_BRANCHES")} +
+ ); +} diff --git a/frontend/src/components/features/home/repository-selection/index.ts b/frontend/src/components/features/home/repository-selection/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9cb4aa0507e54034f024ca6123fd668a0e8d2959 --- /dev/null +++ b/frontend/src/components/features/home/repository-selection/index.ts @@ -0,0 +1,6 @@ +export { RepositoryDropdown } from "#/components/features/home/repository-selection/repository-dropdown"; +export { RepositoryLoadingState } from "#/components/features/home/repository-selection/repository-loading-state"; +export { RepositoryErrorState } from "#/components/features/home/repository-selection/repository-error-state"; +export { BranchDropdown } from "#/components/features/home/repository-selection/branch-dropdown"; +export { BranchLoadingState } from "#/components/features/home/repository-selection/branch-loading-state"; +export { BranchErrorState } from "#/components/features/home/repository-selection/branch-error-state"; diff --git a/frontend/src/components/features/home/repository-selection/repository-dropdown.tsx b/frontend/src/components/features/home/repository-selection/repository-dropdown.tsx new file mode 100644 index 0000000000000000000000000000000000000000..03aaddd6cc0218808fe2069167af6980659b8f49 --- /dev/null +++ b/frontend/src/components/features/home/repository-selection/repository-dropdown.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { SettingsDropdownInput } from "../../settings/settings-dropdown-input"; + +export interface RepositoryDropdownProps { + items: { key: React.Key; label: string }[]; + onSelectionChange: (key: React.Key | null) => void; + onInputChange: (value: string) => void; + defaultFilter?: (textValue: string, inputValue: string) => boolean; +} + +export function RepositoryDropdown({ + items, + onSelectionChange, + onInputChange, + defaultFilter, +}: RepositoryDropdownProps) { + return ( + + ); +} diff --git a/frontend/src/components/features/home/repository-selection/repository-error-state.tsx b/frontend/src/components/features/home/repository-selection/repository-error-state.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7be07af54f3da347a904f602e9522739241a0818 --- /dev/null +++ b/frontend/src/components/features/home/repository-selection/repository-error-state.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +export function RepositoryErrorState() { + const { t } = useTranslation(); + return ( +
+ {t("HOME$FAILED_TO_LOAD_REPOSITORIES")} +
+ ); +} diff --git a/frontend/src/components/features/home/repository-selection/repository-loading-state.tsx b/frontend/src/components/features/home/repository-selection/repository-loading-state.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fdcbf195b02c7cf923554622976f5c6460ffcdcd --- /dev/null +++ b/frontend/src/components/features/home/repository-selection/repository-loading-state.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Spinner } from "@heroui/react"; + +export function RepositoryLoadingState() { + const { t } = useTranslation(); + return ( +
+ + {t("HOME$LOADING_REPOSITORIES")} +
+ ); +} diff --git a/frontend/src/components/features/home/tasks/task-card.tsx b/frontend/src/components/features/home/tasks/task-card.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7b53e03b5167cd0f8e6040af7aad12f3a6860462 --- /dev/null +++ b/frontend/src/components/features/home/tasks/task-card.tsx @@ -0,0 +1,86 @@ +import { useTranslation } from "react-i18next"; +import { SuggestedTask } from "./task.types"; +import { useIsCreatingConversation } from "#/hooks/use-is-creating-conversation"; +import { useCreateConversation } from "#/hooks/mutation/use-create-conversation"; +import { cn } from "#/utils/utils"; +import { useUserRepositories } from "#/hooks/query/use-user-repositories"; +import { TaskIssueNumber } from "./task-issue-number"; +import { Provider } from "#/types/settings"; +import { useOptimisticUserMessage } from "#/hooks/use-optimistic-user-message"; + +const getTaskTypeMap = ( + t: (key: string) => string, +): Record => ({ + FAILING_CHECKS: t("HOME$FIX_FAILING_CHECKS"), + MERGE_CONFLICTS: t("HOME$RESOLVE_MERGE_CONFLICTS"), + OPEN_ISSUE: t("HOME$OPEN_ISSUE"), + UNRESOLVED_COMMENTS: t("HOME$RESOLVE_UNRESOLVED_COMMENTS"), +}); + +interface TaskCardProps { + task: SuggestedTask; +} + +export function TaskCard({ task }: TaskCardProps) { + const { setOptimisticUserMessage } = useOptimisticUserMessage(); + const { data: repositories } = useUserRepositories(); + const { mutate: createConversation, isPending } = useCreateConversation(); + const isCreatingConversation = useIsCreatingConversation(); + const { t } = useTranslation(); + + const getRepo = (repo: string, git_provider: Provider) => { + const selectedRepo = repositories?.find( + (repository) => + repository.full_name === repo && + repository.git_provider === git_provider, + ); + + return selectedRepo; + }; + + const handleLaunchConversation = () => { + const repo = getRepo(task.repo, task.git_provider); + setOptimisticUserMessage(t("TASK$ADDRESSING_TASK")); + + return createConversation({ + selectedRepository: repo, + suggested_task: task, + }); + }; + + // Determine the correct URL format based on git provider + let href: string; + if (task.git_provider === "gitlab") { + const issueType = + task.task_type === "OPEN_ISSUE" ? "issues" : "merge_requests"; + href = `https://gitlab.com/${task.repo}/-/${issueType}/${task.issue_number}`; + } else { + const hrefType = task.task_type === "OPEN_ISSUE" ? "issues" : "pull"; + href = `https://github.com/${task.repo}/${hrefType}/${task.issue_number}`; + } + + return ( +
  • + + +
    +

    {getTaskTypeMap(t)[task.task_type]}

    +

    {task.title}

    +
    + + +
  • + ); +} diff --git a/frontend/src/components/features/home/tasks/task-group.tsx b/frontend/src/components/features/home/tasks/task-group.tsx new file mode 100644 index 0000000000000000000000000000000000000000..03ffdbf0458f8c7db2a2e5dd238c3fe679b062eb --- /dev/null +++ b/frontend/src/components/features/home/tasks/task-group.tsx @@ -0,0 +1,22 @@ +import { TaskCard } from "./task-card"; +import { TaskItemTitle } from "./task-item-title"; +import { SuggestedTask } from "./task.types"; + +interface TaskGroupProps { + title: string; + tasks: SuggestedTask[]; +} + +export function TaskGroup({ title, tasks }: TaskGroupProps) { + return ( +
    + {title} + +
      + {tasks.map((task) => ( + + ))} +
    +
    + ); +} diff --git a/frontend/src/components/features/home/tasks/task-issue-number.tsx b/frontend/src/components/features/home/tasks/task-issue-number.tsx new file mode 100644 index 0000000000000000000000000000000000000000..893b2255b0a3cd8345ed9ddf7ec60738db4b963f --- /dev/null +++ b/frontend/src/components/features/home/tasks/task-issue-number.tsx @@ -0,0 +1,17 @@ +interface TaskIssueNumberProps { + issueNumber: number; + href: string; +} + +export function TaskIssueNumber({ href, issueNumber }: TaskIssueNumberProps) { + return ( + + #{issueNumber} + + ); +} diff --git a/frontend/src/components/features/home/tasks/task-item-title.tsx b/frontend/src/components/features/home/tasks/task-item-title.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1bf2c836054b0de69df1ea565ce617b9a9beeb3e --- /dev/null +++ b/frontend/src/components/features/home/tasks/task-item-title.tsx @@ -0,0 +1,7 @@ +export function TaskItemTitle({ children: title }: React.PropsWithChildren) { + return ( +
    +

    {title}

    +
    + ); +} diff --git a/frontend/src/components/features/home/tasks/task-suggestions-skeleton.tsx b/frontend/src/components/features/home/tasks/task-suggestions-skeleton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8691734a9ffc23a43dc928b6aa3e72f0cf8c952e --- /dev/null +++ b/frontend/src/components/features/home/tasks/task-suggestions-skeleton.tsx @@ -0,0 +1,50 @@ +import { cn } from "#/utils/utils"; + +const VALID_WIDTHS = ["w-1/4", "w-1/2", "w-3/4"]; + +const getRandomWidth = () => + VALID_WIDTHS[Math.floor(Math.random() * VALID_WIDTHS.length)]; + +const getRandomNumber = (from = 3, to = 5) => + Math.floor(Math.random() * (to - from + 1)) + from; + +function TaskCardSkeleton() { + return ( +
  • +
    + +
    +
    +
    +
    + +
    +
  • + ); +} + +interface TaskGroupSkeletonProps { + items?: number; +} + +function TaskGroupSkeleton({ items = 3 }: TaskGroupSkeletonProps) { + return ( +
    +
    +
    +
    + +
      + {Array.from({ length: items }).map((_, index) => ( + + ))} +
    +
    + ); +} + +export function TaskSuggestionsSkeleton() { + return Array.from({ length: getRandomNumber(2, 3) }).map((_, index) => ( + + )); +} diff --git a/frontend/src/components/features/home/tasks/task-suggestions.tsx b/frontend/src/components/features/home/tasks/task-suggestions.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8557bc238a83ea0496d8275bd88500030ccff2b9 --- /dev/null +++ b/frontend/src/components/features/home/tasks/task-suggestions.tsx @@ -0,0 +1,38 @@ +import { TaskGroup } from "./task-group"; +import { useSuggestedTasks } from "#/hooks/query/use-suggested-tasks"; +import { TaskSuggestionsSkeleton } from "./task-suggestions-skeleton"; +import { cn } from "#/utils/utils"; + +interface TaskSuggestionsProps { + filterFor?: string | null; +} + +export function TaskSuggestions({ filterFor }: TaskSuggestionsProps) { + const { data: tasks, isLoading } = useSuggestedTasks(); + const suggestedTasks = filterFor + ? tasks?.filter((task) => task.title === filterFor) + : tasks; + + const hasSuggestedTasks = suggestedTasks && suggestedTasks.length > 0; + + return ( +
    +

    Suggested Tasks

    + +
    + {isLoading && } + {!hasSuggestedTasks && !isLoading &&

    No tasks available

    } + {suggestedTasks?.map((taskGroup, index) => ( + + ))} +
    +
    + ); +} diff --git a/frontend/src/components/features/home/tasks/task.types.ts b/frontend/src/components/features/home/tasks/task.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd2a9e696202cc790d682a54ea02bc20cd49c0ea --- /dev/null +++ b/frontend/src/components/features/home/tasks/task.types.ts @@ -0,0 +1,20 @@ +import { Provider } from "#/types/settings"; + +export type SuggestedTaskType = + | "MERGE_CONFLICTS" + | "FAILING_CHECKS" + | "UNRESOLVED_COMMENTS" + | "OPEN_ISSUE"; // This is a task type identifier, not a UI string + +export interface SuggestedTask { + git_provider: Provider; + issue_number: number; + repo: string; + title: string; + task_type: SuggestedTaskType; +} + +export interface SuggestedTaskGroup { + title: string; + tasks: SuggestedTask[]; +} diff --git a/frontend/src/components/features/images/attach-image-label.tsx b/frontend/src/components/features/images/attach-image-label.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e43f14d9dfdb9437d70bbbb15597d58e241aa299 --- /dev/null +++ b/frontend/src/components/features/images/attach-image-label.tsx @@ -0,0 +1,13 @@ +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import Clip from "#/icons/clip.svg?react"; + +export function AttachImageLabel() { + const { t } = useTranslation(); + return ( +
    + + {t(I18nKey.LANDING$ATTACH_IMAGES)} +
    + ); +} diff --git a/frontend/src/components/features/images/image-carousel.tsx b/frontend/src/components/features/images/image-carousel.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5059482f9ff1e2b9c6b699d889de84b7b3f1660a --- /dev/null +++ b/frontend/src/components/features/images/image-carousel.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { ChevronLeft } from "#/assets/chevron-left"; +import { ChevronRight } from "#/assets/chevron-right"; +import { ImagePreview } from "./image-preview"; +import { cn } from "#/utils/utils"; + +interface ImageCarouselProps { + size: "small" | "large"; + images: string[]; + onRemove?: (index: number) => void; +} + +export function ImageCarousel({ + size = "small", + images, + onRemove, +}: ImageCarouselProps) { + const scrollContainerRef = React.useRef(null); + const [isScrollable, setIsScrollable] = React.useState(false); + const [isAtStart, setIsAtStart] = React.useState(true); + const [isAtEnd, setIsAtEnd] = React.useState(false); + + React.useEffect(() => { + const scrollContainer = scrollContainerRef.current; + + if (scrollContainer) { + const hasScroll = + scrollContainer.scrollWidth > scrollContainer.clientWidth; + setIsScrollable(hasScroll); + } + }, [images]); + + const handleScroll = (event: React.UIEvent) => { + const scrollContainer = event.currentTarget; + setIsAtStart(scrollContainer.scrollLeft === 0); + setIsAtEnd( + scrollContainer.scrollLeft + scrollContainer.clientWidth === + scrollContainer.scrollWidth, + ); + }; + + return ( +
    + {isScrollable && ( +
    + +
    + )} +
    + {images.map((src, index) => ( + onRemove(index))} + /> + ))} +
    + {isScrollable && ( +
    + +
    + )} +
    + ); +} diff --git a/frontend/src/components/features/images/image-preview.tsx b/frontend/src/components/features/images/image-preview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..37dc314a4be877a74fdbcc4c7abd42d2d9035ae2 --- /dev/null +++ b/frontend/src/components/features/images/image-preview.tsx @@ -0,0 +1,21 @@ +import { RemoveButton } from "#/components/shared/buttons/remove-button"; +import { Thumbnail } from "./thumbnail"; + +interface ImagePreviewProps { + src: string; + onRemove?: () => void; + size?: "small" | "large"; +} + +export function ImagePreview({ + src, + onRemove, + size = "small", +}: ImagePreviewProps) { + return ( +
    + + {onRemove && } +
    + ); +} diff --git a/frontend/src/components/features/images/thumbnail.tsx b/frontend/src/components/features/images/thumbnail.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5dc0818c287367faf0f6c62d25dbc4d125d55856 --- /dev/null +++ b/frontend/src/components/features/images/thumbnail.tsx @@ -0,0 +1,21 @@ +import { cn } from "#/utils/utils"; + +interface ThumbnailProps { + src: string; + size?: "small" | "large"; +} + +export function Thumbnail({ src, size = "small" }: ThumbnailProps) { + return ( + + ); +} diff --git a/frontend/src/components/features/images/upload-image-input.tsx b/frontend/src/components/features/images/upload-image-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c55d695b12c5528a4f52e27e51df31c2c48e7e32 --- /dev/null +++ b/frontend/src/components/features/images/upload-image-input.tsx @@ -0,0 +1,31 @@ +import Clip from "#/icons/clip.svg?react"; + +interface UploadImageInputProps { + onUpload: (files: File[]) => void; + label?: React.ReactNode; +} + +export function UploadImageInput({ onUpload, label }: UploadImageInputProps) { + const handleUpload = (event: React.ChangeEvent) => { + if (event.target.files) { + const validFiles = Array.from(event.target.files).filter((file) => + file.type.startsWith("image/"), + ); + onUpload(validFiles); + } + }; + + return ( + + ); +} diff --git a/frontend/src/components/features/jupyter/jupyter-cell-input.tsx b/frontend/src/components/features/jupyter/jupyter-cell-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c69651d105d883d2f86f342d2cd239ddffa664e1 --- /dev/null +++ b/frontend/src/components/features/jupyter/jupyter-cell-input.tsx @@ -0,0 +1,22 @@ +import SyntaxHighlighter from "react-syntax-highlighter"; +import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs"; + +interface JupytrerCellInputProps { + code: string; +} + +export function JupytrerCellInput({ code }: JupytrerCellInputProps) { + return ( +
    +
    EXECUTE
    +
    +        
    +          {code}
    +        
    +      
    +
    + ); +} diff --git a/frontend/src/components/features/jupyter/jupyter-cell-output.tsx b/frontend/src/components/features/jupyter/jupyter-cell-output.tsx new file mode 100644 index 0000000000000000000000000000000000000000..be2c5e3a1f3578c199b0940fcd467c6a226667f2 --- /dev/null +++ b/frontend/src/components/features/jupyter/jupyter-cell-output.tsx @@ -0,0 +1,55 @@ +import Markdown from "react-markdown"; +import SyntaxHighlighter from "react-syntax-highlighter"; +import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { JupyterLine } from "#/utils/parse-cell-content"; +import { paragraph } from "../markdown/paragraph"; + +interface JupyterCellOutputProps { + lines: JupyterLine[]; +} + +export function JupyterCellOutput({ lines }: JupyterCellOutputProps) { + const { t } = useTranslation(); + return ( +
    +
    + {t(I18nKey.JUPYTER$OUTPUT_LABEL)} +
    +
    +        {/* display the lines as plaintext or image */}
    +        {lines.map((line, index) => {
    +          if (line.type === "image") {
    +            // Use markdown to display the image
    +            const imageMarkdown = line.url
    +              ? `![image](${line.url})`
    +              : line.content;
    +            return (
    +              
    + value} + > + {imageMarkdown} + +
    + ); + } + return ( +
    + + {line.content} + +
    + ); + })} +
    +
    + ); +} diff --git a/frontend/src/components/features/jupyter/jupyter-cell.tsx b/frontend/src/components/features/jupyter/jupyter-cell.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3d22a1f12b6ed8d341455f88bae1719a86a08e0d --- /dev/null +++ b/frontend/src/components/features/jupyter/jupyter-cell.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Cell } from "#/state/jupyter-slice"; +import { JupyterLine, parseCellContent } from "#/utils/parse-cell-content"; +import { JupytrerCellInput } from "./jupyter-cell-input"; +import { JupyterCellOutput } from "./jupyter-cell-output"; + +interface JupyterCellProps { + cell: Cell; +} + +export function JupyterCell({ cell }: JupyterCellProps) { + const [lines, setLines] = React.useState([]); + + React.useEffect(() => { + setLines(parseCellContent(cell.content, cell.imageUrls)); + }, [cell.content, cell.imageUrls]); + + if (cell.type === "input") { + return ; + } + + return ; +} diff --git a/frontend/src/components/features/jupyter/jupyter.tsx b/frontend/src/components/features/jupyter/jupyter.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0ce1480c94b8e00b19c3e07e57f59782bcfd0ac1 --- /dev/null +++ b/frontend/src/components/features/jupyter/jupyter.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import { RootState } from "#/store"; +import { useScrollToBottom } from "#/hooks/use-scroll-to-bottom"; +import { JupyterCell } from "./jupyter-cell"; +import { ScrollToBottomButton } from "#/components/shared/buttons/scroll-to-bottom-button"; + +interface JupyterEditorProps { + maxWidth: number; +} + +export function JupyterEditor({ maxWidth }: JupyterEditorProps) { + const cells = useSelector((state: RootState) => state.jupyter?.cells ?? []); + const jupyterRef = React.useRef(null); + + const { hitBottom, scrollDomToBottom, onChatBodyScroll } = + useScrollToBottom(jupyterRef); + + return ( +
    +
    onChatBodyScroll(e.currentTarget)} + > + {cells.map((cell, index) => ( + + ))} +
    + {!hitBottom && ( +
    + +
    + )} +
    + ); +} diff --git a/frontend/src/components/features/markdown/anchor.tsx b/frontend/src/components/features/markdown/anchor.tsx new file mode 100644 index 0000000000000000000000000000000000000000..519d5e9a789cf645b6e514a6305bccf364c5d70f --- /dev/null +++ b/frontend/src/components/features/markdown/anchor.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { ExtraProps } from "react-markdown"; + +export function anchor({ + href, + children, +}: React.ClassAttributes & + React.AnchorHTMLAttributes & + ExtraProps) { + return ( + + {children} + + ); +} diff --git a/frontend/src/components/features/markdown/code.tsx b/frontend/src/components/features/markdown/code.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2a801f6848531b1c3599af40c2f739941bae4f4f --- /dev/null +++ b/frontend/src/components/features/markdown/code.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import { ExtraProps } from "react-markdown"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"; + +// See https://github.com/remarkjs/react-markdown?tab=readme-ov-file#use-custom-components-syntax-highlight + +/** + * Component to render code blocks in markdown. + */ +export function code({ + children, + className, +}: React.ClassAttributes & + React.HTMLAttributes & + ExtraProps) { + const match = /language-(\w+)/.exec(className || ""); // get the language + + if (!match) { + const isMultiline = String(children).includes("\n"); + + if (!isMultiline) { + return ( + + {children} + + ); + } + + return ( +
    +        {String(children).replace(/\n$/, "")}
    +      
    + ); + } + + return ( + + {String(children).replace(/\n$/, "")} + + ); +} diff --git a/frontend/src/components/features/markdown/list.tsx b/frontend/src/components/features/markdown/list.tsx new file mode 100644 index 0000000000000000000000000000000000000000..19b449be171ed8bdc5adea03ccf51c5dc1f6db21 --- /dev/null +++ b/frontend/src/components/features/markdown/list.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { ExtraProps } from "react-markdown"; + +// Custom component to render
      in markdown +export function ul({ + children, +}: React.ClassAttributes & + React.HTMLAttributes & + ExtraProps) { + return
        {children}
      ; +} + +// Custom component to render
        in markdown +export function ol({ + children, + start, +}: React.ClassAttributes & + React.OlHTMLAttributes & + ExtraProps) { + return ( +
          + {children} +
        + ); +} diff --git a/frontend/src/components/features/markdown/paragraph.tsx b/frontend/src/components/features/markdown/paragraph.tsx new file mode 100644 index 0000000000000000000000000000000000000000..97cd933ce86076a4aa9a1fadca669bd8a8cc0cca --- /dev/null +++ b/frontend/src/components/features/markdown/paragraph.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import { ExtraProps } from "react-markdown"; + +// Custom component to render

        in markdown with bottom padding +export function paragraph({ + children, +}: React.ClassAttributes & + React.HTMLAttributes & + ExtraProps) { + return

        {children}

        ; +} diff --git a/frontend/src/components/features/payment/payment-form.tsx b/frontend/src/components/features/payment/payment-form.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bc337c05dfa89abc1321d639ea966d02124037cb --- /dev/null +++ b/frontend/src/components/features/payment/payment-form.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { useCreateStripeCheckoutSession } from "#/hooks/mutation/stripe/use-create-stripe-checkout-session"; +import { useBalance } from "#/hooks/query/use-balance"; +import { cn } from "#/utils/utils"; +import MoneyIcon from "#/icons/money.svg?react"; +import { SettingsInput } from "../settings/settings-input"; +import { BrandButton } from "../settings/brand-button"; +import { LoadingSpinner } from "#/components/shared/loading-spinner"; +import { amountIsValid } from "#/utils/amount-is-valid"; +import { I18nKey } from "#/i18n/declaration"; + +export function PaymentForm() { + const { t } = useTranslation(); + const { data: balance, isLoading } = useBalance(); + const { mutate: addBalance, isPending } = useCreateStripeCheckoutSession(); + + const [buttonIsDisabled, setButtonIsDisabled] = React.useState(true); + + const billingFormAction = async (formData: FormData) => { + const amount = formData.get("top-up-input")?.toString(); + + if (amount?.trim()) { + if (!amountIsValid(amount)) return; + + const intValue = parseInt(amount, 10); + addBalance({ amount: intValue }); + } + + setButtonIsDisabled(true); + }; + + const handleTopUpInputChange = (value: string) => { + setButtonIsDisabled(!amountIsValid(value)); + }; + + return ( +
        +
        +
        + + {t(I18nKey.PAYMENT$MANAGE_CREDITS)} +
        + {!isLoading && ( + ${Number(balance).toFixed(2)} + )} + {isLoading && } +
        + +
        + + +
        + + {t(I18nKey.PAYMENT$ADD_CREDIT)} + + {isPending && } +
        +
        +
        + ); +} diff --git a/frontend/src/components/features/payment/setup-payment-modal.tsx b/frontend/src/components/features/payment/setup-payment-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dfce7d9cd30ff3e01003d1dcaac2be6ab89c5564 --- /dev/null +++ b/frontend/src/components/features/payment/setup-payment-modal.tsx @@ -0,0 +1,51 @@ +import { useMutation } from "@tanstack/react-query"; +import { Trans, useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import AllHandsLogo from "#/assets/branding/all-hands-logo.svg?react"; +import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop"; +import { ModalBody } from "#/components/shared/modals/modal-body"; +import OpenHands from "#/api/open-hands"; +import { BrandButton } from "../settings/brand-button"; +import { displayErrorToast } from "#/utils/custom-toast-handlers"; + +export function SetupPaymentModal() { + const { t } = useTranslation(); + const { mutate, isPending } = useMutation({ + mutationFn: OpenHands.createBillingSessionResponse, + onSuccess: (data) => { + window.location.href = data; + }, + onError: () => { + displayErrorToast(t(I18nKey.BILLING$ERROR_WHILE_CREATING_SESSION)); + }, + }); + + return ( + + + +
        +

        + {t(I18nKey.BILLING$YOUVE_GOT_50)} +

        +

        + }} + /> +

        +
        + + {t(I18nKey.BILLING$PROCEED_TO_STRIPE)} + +
        +
        + ); +} diff --git a/frontend/src/components/features/served-host/path-form.tsx b/frontend/src/components/features/served-host/path-form.tsx new file mode 100644 index 0000000000000000000000000000000000000000..daec8b7c7ef619d7ad8ff0ac458682519229e260 --- /dev/null +++ b/frontend/src/components/features/served-host/path-form.tsx @@ -0,0 +1,19 @@ +interface PathFormProps { + ref: React.RefObject; + onBlur: () => void; + defaultValue: string; +} + +export function PathForm({ ref, onBlur, defaultValue }: PathFormProps) { + return ( +
        e.preventDefault()} className="flex-1"> + +
        + ); +} diff --git a/frontend/src/components/features/settings/api-key-modal-base.tsx b/frontend/src/components/features/settings/api-key-modal-base.tsx new file mode 100644 index 0000000000000000000000000000000000000000..43d8ba89f02813f333c30fc9b0f9bc4898c9f478 --- /dev/null +++ b/frontend/src/components/features/settings/api-key-modal-base.tsx @@ -0,0 +1,33 @@ +import React, { ReactNode } from "react"; +import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop"; + +interface ApiKeyModalBaseProps { + isOpen: boolean; + title: string; + width?: string; + children: ReactNode; + footer: ReactNode; +} + +export function ApiKeyModalBase({ + isOpen, + title, + width = "500px", + children, + footer, +}: ApiKeyModalBaseProps) { + if (!isOpen) return null; + + return ( + +
        +

        {title}

        + {children} +
        {footer}
        +
        +
        + ); +} diff --git a/frontend/src/components/features/settings/api-keys-manager.tsx b/frontend/src/components/features/settings/api-keys-manager.tsx new file mode 100644 index 0000000000000000000000000000000000000000..99097b141e2ef195b3cc2141ea5ce726485d6361 --- /dev/null +++ b/frontend/src/components/features/settings/api-keys-manager.tsx @@ -0,0 +1,160 @@ +import React, { useState } from "react"; +import { useTranslation, Trans } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { BrandButton } from "#/components/features/settings/brand-button"; +import { LoadingSpinner } from "#/components/shared/loading-spinner"; +import { ApiKey, CreateApiKeyResponse } from "#/api/api-keys"; +import { displayErrorToast } from "#/utils/custom-toast-handlers"; +import { CreateApiKeyModal } from "./create-api-key-modal"; +import { DeleteApiKeyModal } from "./delete-api-key-modal"; +import { NewApiKeyModal } from "./new-api-key-modal"; +import { useApiKeys } from "#/hooks/query/use-api-keys"; + +export function ApiKeysManager() { + const { t } = useTranslation(); + const { data: apiKeys = [], isLoading, error } = useApiKeys(); + const [createModalOpen, setCreateModalOpen] = useState(false); + const [deleteModalOpen, setDeleteModalOpen] = useState(false); + const [keyToDelete, setKeyToDelete] = useState(null); + const [newlyCreatedKey, setNewlyCreatedKey] = + useState(null); + const [showNewKeyModal, setShowNewKeyModal] = useState(false); + + // Display error toast if the query fails + if (error) { + displayErrorToast(t(I18nKey.ERROR$GENERIC)); + } + + const handleKeyCreated = (newKey: CreateApiKeyResponse) => { + setNewlyCreatedKey(newKey); + setCreateModalOpen(false); + setShowNewKeyModal(true); + }; + + const handleCloseCreateModal = () => { + setCreateModalOpen(false); + }; + + const handleCloseDeleteModal = () => { + setDeleteModalOpen(false); + setKeyToDelete(null); + }; + + const handleCloseNewKeyModal = () => { + setShowNewKeyModal(false); + setNewlyCreatedKey(null); + }; + + const formatDate = (dateString: string | null) => { + if (!dateString) return "Never"; + return new Date(dateString).toLocaleString(); + }; + + return ( + <> +
        +
        + setCreateModalOpen(true)} + > + {t(I18nKey.SETTINGS$CREATE_API_KEY)} + +
        + +

        + + API documentation + + ), + }} + /> +

        + + {isLoading && ( +
        + +
        + )} + {!isLoading && Array.isArray(apiKeys) && apiKeys.length > 0 && ( +
        + + + + + + + + + + + {apiKeys.map((key) => ( + + + + + + + ))} + +
        + {t(I18nKey.SETTINGS$NAME)} + + {t(I18nKey.SETTINGS$CREATED_AT)} + + {t(I18nKey.SETTINGS$LAST_USED)} + + {t(I18nKey.SETTINGS$ACTIONS)} +
        {key.name} + {formatDate(key.created_at)} + + {formatDate(key.last_used_at)} + + +
        +
        + )} +
        + + {/* Create API Key Modal */} + + + {/* Delete API Key Modal */} + + + {/* Show New API Key Modal */} + + + ); +} diff --git a/frontend/src/components/features/settings/app-settings/app-settings-inputs-skeleton.tsx b/frontend/src/components/features/settings/app-settings/app-settings-inputs-skeleton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..324d1187549b1a5e67080cd836386badbc30bc58 --- /dev/null +++ b/frontend/src/components/features/settings/app-settings/app-settings-inputs-skeleton.tsx @@ -0,0 +1,15 @@ +import { InputSkeleton } from "../input-skeleton"; +import { SwitchSkeleton } from "../switch-skeleton"; + +export function AppSettingsInputsSkeleton() { + return ( +
        + + + +
        + ); +} diff --git a/frontend/src/components/features/settings/app-settings/language-input.tsx b/frontend/src/components/features/settings/app-settings/language-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4e41c71f6221f6a13399a1718bad1cf7feb6021b --- /dev/null +++ b/frontend/src/components/features/settings/app-settings/language-input.tsx @@ -0,0 +1,34 @@ +import { useTranslation } from "react-i18next"; +import { AvailableLanguages } from "#/i18n"; +import { I18nKey } from "#/i18n/declaration"; +import { SettingsDropdownInput } from "../settings-dropdown-input"; + +interface LanguageInputProps { + name: string; + onChange: (value: string) => void; + defaultKey: string; +} + +export function LanguageInput({ + defaultKey, + onChange, + name, +}: LanguageInputProps) { + const { t } = useTranslation(); + + return ( + ({ + key: l.value, + label: l.label, + }))} + defaultSelectedKey={defaultKey} + isClearable={false} + wrapperClassName="w-full max-w-[680px]" + /> + ); +} diff --git a/frontend/src/components/features/settings/brand-button.tsx b/frontend/src/components/features/settings/brand-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ecf9352c4873d9c5d6fb6a7c8773a5555cb29229 --- /dev/null +++ b/frontend/src/components/features/settings/brand-button.tsx @@ -0,0 +1,47 @@ +import { cn } from "#/utils/utils"; + +interface BrandButtonProps { + testId?: string; + name?: string; + variant: "primary" | "secondary" | "danger"; + type: React.ButtonHTMLAttributes["type"]; + isDisabled?: boolean; + className?: string; + onClick?: () => void; + startContent?: React.ReactNode; +} + +export function BrandButton({ + testId, + name, + children, + variant, + type, + isDisabled, + className, + onClick, + startContent, +}: React.PropsWithChildren) { + return ( + + ); +} diff --git a/frontend/src/components/features/settings/create-api-key-modal.tsx b/frontend/src/components/features/settings/create-api-key-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b97d29f349b64fb65d2684b7130b3a5272d39304 --- /dev/null +++ b/frontend/src/components/features/settings/create-api-key-modal.tsx @@ -0,0 +1,101 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { BrandButton } from "#/components/features/settings/brand-button"; +import { SettingsInput } from "#/components/features/settings/settings-input"; +import { LoadingSpinner } from "#/components/shared/loading-spinner"; +import { CreateApiKeyResponse } from "#/api/api-keys"; +import { + displayErrorToast, + displaySuccessToast, +} from "#/utils/custom-toast-handlers"; +import { ApiKeyModalBase } from "./api-key-modal-base"; +import { useCreateApiKey } from "#/hooks/mutation/use-create-api-key"; + +interface CreateApiKeyModalProps { + isOpen: boolean; + onClose: () => void; + onKeyCreated: (newKey: CreateApiKeyResponse) => void; +} + +export function CreateApiKeyModal({ + isOpen, + onClose, + onKeyCreated, +}: CreateApiKeyModalProps) { + const { t } = useTranslation(); + const [newKeyName, setNewKeyName] = useState(""); + + const createApiKeyMutation = useCreateApiKey(); + + const handleCreateKey = async () => { + if (!newKeyName.trim()) { + displayErrorToast(t(I18nKey.ERROR$REQUIRED_FIELD)); + return; + } + + try { + const newKey = await createApiKeyMutation.mutateAsync(newKeyName); + onKeyCreated(newKey); + displaySuccessToast(t(I18nKey.SETTINGS$API_KEY_CREATED)); + setNewKeyName(""); + } catch (error) { + displayErrorToast(t(I18nKey.ERROR$GENERIC)); + } + }; + + const handleCancel = () => { + setNewKeyName(""); + onClose(); + }; + + const modalFooter = ( + <> + + {createApiKeyMutation.isPending ? ( + + ) : ( + t(I18nKey.BUTTON$CREATE) + )} + + + {t(I18nKey.BUTTON$CANCEL)} + + + ); + + return ( + +
        +

        + {t(I18nKey.SETTINGS$CREATE_API_KEY_DESCRIPTION)} +

        + setNewKeyName(value)} + className="w-full mt-4" + type="text" + /> +
        +
        + ); +} diff --git a/frontend/src/components/features/settings/delete-api-key-modal.tsx b/frontend/src/components/features/settings/delete-api-key-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..187507745839b76068f83dfff3f357d5de845e3f --- /dev/null +++ b/frontend/src/components/features/settings/delete-api-key-modal.tsx @@ -0,0 +1,84 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { BrandButton } from "#/components/features/settings/brand-button"; +import { LoadingSpinner } from "#/components/shared/loading-spinner"; +import { ApiKey } from "#/api/api-keys"; +import { + displayErrorToast, + displaySuccessToast, +} from "#/utils/custom-toast-handlers"; +import { ApiKeyModalBase } from "./api-key-modal-base"; +import { useDeleteApiKey } from "#/hooks/mutation/use-delete-api-key"; + +interface DeleteApiKeyModalProps { + isOpen: boolean; + keyToDelete: ApiKey | null; + onClose: () => void; +} + +export function DeleteApiKeyModal({ + isOpen, + keyToDelete, + onClose, +}: DeleteApiKeyModalProps) { + const { t } = useTranslation(); + const deleteApiKeyMutation = useDeleteApiKey(); + + const handleDeleteKey = async () => { + if (!keyToDelete) return; + + try { + await deleteApiKeyMutation.mutateAsync(keyToDelete.id); + displaySuccessToast(t(I18nKey.SETTINGS$API_KEY_DELETED)); + onClose(); + } catch (error) { + displayErrorToast(t(I18nKey.ERROR$GENERIC)); + } + }; + + if (!keyToDelete) return null; + + const modalFooter = ( + <> + + {deleteApiKeyMutation.isPending ? ( + + ) : ( + t(I18nKey.BUTTON$DELETE) + )} + + + {t(I18nKey.BUTTON$CANCEL)} + + + ); + + return ( + +
        +

        + {t(I18nKey.SETTINGS$DELETE_API_KEY_CONFIRMATION, { + name: keyToDelete.name, + })} +

        +
        +
        + ); +} diff --git a/frontend/src/components/features/settings/git-settings/configure-github-repositories-anchor.tsx b/frontend/src/components/features/settings/git-settings/configure-github-repositories-anchor.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4b9294878ce4249ea5ef29fd0c90ee798831b2b8 --- /dev/null +++ b/frontend/src/components/features/settings/git-settings/configure-github-repositories-anchor.tsx @@ -0,0 +1,27 @@ +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { BrandButton } from "../brand-button"; + +interface ConfigureGitHubRepositoriesAnchorProps { + slug: string; +} + +export function ConfigureGitHubRepositoriesAnchor({ + slug, +}: ConfigureGitHubRepositoriesAnchorProps) { + const { t } = useTranslation(); + + return ( + + + {t(I18nKey.GITHUB$CONFIGURE_REPOS)} + + + ); +} diff --git a/frontend/src/components/features/settings/git-settings/github-settings-inputs-skeleton.tsx b/frontend/src/components/features/settings/git-settings/github-settings-inputs-skeleton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9885b98cec86250592b5f1b240087a9a3d24acf9 --- /dev/null +++ b/frontend/src/components/features/settings/git-settings/github-settings-inputs-skeleton.tsx @@ -0,0 +1,18 @@ +import { InputSkeleton } from "../input-skeleton"; +import { SubtextSkeleton } from "../subtext-skeleton"; + +export function GitSettingInputsSkeleton() { + return ( +
        +
        + + +
        + +
        + + +
        +
        + ); +} diff --git a/frontend/src/components/features/settings/git-settings/github-token-help-anchor.tsx b/frontend/src/components/features/settings/git-settings/github-token-help-anchor.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b953a5c446678aaad0efa6b79cc7e0fc896ffe07 --- /dev/null +++ b/frontend/src/components/features/settings/git-settings/github-token-help-anchor.tsx @@ -0,0 +1,30 @@ +import { Trans } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; + +export function GitHubTokenHelpAnchor() { + return ( +

        + , + , + ]} + /> +

        + ); +} diff --git a/frontend/src/components/features/settings/git-settings/github-token-input.tsx b/frontend/src/components/features/settings/git-settings/github-token-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..15b51b5ef59e032cc7c1308ca0b1f651f8b06317 --- /dev/null +++ b/frontend/src/components/features/settings/git-settings/github-token-input.tsx @@ -0,0 +1,64 @@ +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { SettingsInput } from "../settings-input"; +import { GitHubTokenHelpAnchor } from "./github-token-help-anchor"; +import { KeyStatusIcon } from "../key-status-icon"; + +interface GitHubTokenInputProps { + onChange: (value: string) => void; + onGitHubHostChange: (value: string) => void; + isGitHubTokenSet: boolean; + name: string; + githubHostSet: string | null | undefined; +} + +export function GitHubTokenInput({ + onChange, + onGitHubHostChange, + isGitHubTokenSet, + name, + githubHostSet, +}: GitHubTokenInputProps) { + const { t } = useTranslation(); + + return ( +
        + " : ""} + startContent={ + isGitHubTokenSet && ( + + ) + } + /> + + {})} + name="github-host-input" + testId="github-host-input" + label={t(I18nKey.GITHUB$HOST_LABEL)} + type="text" + className="w-full max-w-[680px]" + placeholder="github.com" + defaultValue={githubHostSet || undefined} + startContent={ + githubHostSet && + githubHostSet.trim() !== "" && ( + + ) + } + /> + + +
        + ); +} diff --git a/frontend/src/components/features/settings/git-settings/gitlab-token-help-anchor.tsx b/frontend/src/components/features/settings/git-settings/gitlab-token-help-anchor.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e538cbd4422e1c4e6477ae48191549cd3b3507a5 --- /dev/null +++ b/frontend/src/components/features/settings/git-settings/gitlab-token-help-anchor.tsx @@ -0,0 +1,30 @@ +import { Trans } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; + +export function GitLabTokenHelpAnchor() { + return ( +

        + , + , + ]} + /> +

        + ); +} diff --git a/frontend/src/components/features/settings/git-settings/gitlab-token-input.tsx b/frontend/src/components/features/settings/git-settings/gitlab-token-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fa0e1e77e3d2cf2086ab41c51cac01e461130e30 --- /dev/null +++ b/frontend/src/components/features/settings/git-settings/gitlab-token-input.tsx @@ -0,0 +1,64 @@ +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import { SettingsInput } from "../settings-input"; +import { GitLabTokenHelpAnchor } from "./gitlab-token-help-anchor"; +import { KeyStatusIcon } from "../key-status-icon"; + +interface GitLabTokenInputProps { + onChange: (value: string) => void; + onGitLabHostChange: (value: string) => void; + isGitLabTokenSet: boolean; + name: string; + gitlabHostSet: string | null | undefined; +} + +export function GitLabTokenInput({ + onChange, + onGitLabHostChange, + isGitLabTokenSet, + name, + gitlabHostSet, +}: GitLabTokenInputProps) { + const { t } = useTranslation(); + + return ( +
        + " : ""} + startContent={ + isGitLabTokenSet && ( + + ) + } + /> + + {})} + name="gitlab-host-input" + testId="gitlab-host-input" + label={t(I18nKey.GITLAB$HOST_LABEL)} + type="text" + className="w-full max-w-[680px]" + placeholder="gitlab.com" + defaultValue={gitlabHostSet || undefined} + startContent={ + gitlabHostSet && + gitlabHostSet.trim() !== "" && ( + + ) + } + /> + + +
        + ); +} diff --git a/frontend/src/components/features/settings/help-link.tsx b/frontend/src/components/features/settings/help-link.tsx new file mode 100644 index 0000000000000000000000000000000000000000..984f279de23068d63a50f90b0e103c670a49f2e6 --- /dev/null +++ b/frontend/src/components/features/settings/help-link.tsx @@ -0,0 +1,22 @@ +interface HelpLinkProps { + testId: string; + text: string; + linkText: string; + href: string; +} + +export function HelpLink({ testId, text, linkText, href }: HelpLinkProps) { + return ( +

        + {text}{" "} + + {linkText} + +

        + ); +} diff --git a/frontend/src/components/features/settings/input-skeleton.tsx b/frontend/src/components/features/settings/input-skeleton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dfd5d909cf442057d662728185947efbd7f450dd --- /dev/null +++ b/frontend/src/components/features/settings/input-skeleton.tsx @@ -0,0 +1,8 @@ +export function InputSkeleton() { + return ( +
        +
        +
        +
        + ); +} diff --git a/frontend/src/components/features/settings/key-status-icon.tsx b/frontend/src/components/features/settings/key-status-icon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..903d51091f65299d852146a8d5e767ba1f4b7992 --- /dev/null +++ b/frontend/src/components/features/settings/key-status-icon.tsx @@ -0,0 +1,15 @@ +import SuccessIcon from "#/icons/success.svg?react"; +import { cn } from "#/utils/utils"; + +interface KeyStatusIconProps { + testId?: string; + isSet: boolean; +} + +export function KeyStatusIcon({ testId, isSet }: KeyStatusIconProps) { + return ( + + + + ); +} diff --git a/frontend/src/components/features/settings/llm-settings/llm-settings-inputs-skeleton.tsx b/frontend/src/components/features/settings/llm-settings/llm-settings-inputs-skeleton.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3094495bf2854b6c6e5fc95de4b0463902daf4b7 --- /dev/null +++ b/frontend/src/components/features/settings/llm-settings/llm-settings-inputs-skeleton.tsx @@ -0,0 +1,21 @@ +import { InputSkeleton } from "../input-skeleton"; +import { SubtextSkeleton } from "../subtext-skeleton"; +import { SwitchSkeleton } from "../switch-skeleton"; + +export function LlmSettingsInputsSkeleton() { + return ( +
        + + + + + + + + +
        + ); +} diff --git a/frontend/src/components/features/settings/llm-settings/reset-settings-modal.tsx b/frontend/src/components/features/settings/llm-settings/reset-settings-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..85f4f8035097dcbb3ceb830e06702abd97bc8f24 --- /dev/null +++ b/frontend/src/components/features/settings/llm-settings/reset-settings-modal.tsx @@ -0,0 +1,41 @@ +import { useTranslation } from "react-i18next"; +import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop"; +import { I18nKey } from "#/i18n/declaration"; +import { BrandButton } from "../brand-button"; + +interface ResetSettingsModalProps { + onReset: () => void; +} + +export function ResetSettingsModal({ onReset }: ResetSettingsModalProps) { + const { t } = useTranslation(); + + return ( + +
        +

        {t(I18nKey.SETTINGS$RESET_CONFIRMATION)}

        +
        + + Reset + + + + Cancel + +
        +
        +
        + ); +} diff --git a/frontend/src/components/features/settings/mcp-settings/mcp-config-editor.tsx b/frontend/src/components/features/settings/mcp-settings/mcp-config-editor.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2bf040ef86780187416e1d1e6731b07aed5a2e4b --- /dev/null +++ b/frontend/src/components/features/settings/mcp-settings/mcp-config-editor.tsx @@ -0,0 +1,84 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { MCPConfig } from "#/types/settings"; +import { I18nKey } from "#/i18n/declaration"; +import { MCPSSEServers } from "./mcp-sse-servers"; +import { MCPStdioServers } from "./mcp-stdio-servers"; +import { MCPJsonEditor } from "./mcp-json-editor"; +import { BrandButton } from "../brand-button"; + +interface MCPConfigEditorProps { + mcpConfig?: MCPConfig; + onChange: (config: MCPConfig) => void; +} + +export function MCPConfigEditor({ mcpConfig, onChange }: MCPConfigEditorProps) { + const { t } = useTranslation(); + const [isEditing, setIsEditing] = useState(false); + const handleConfigChange = (newConfig: MCPConfig) => { + onChange(newConfig); + setIsEditing(false); + }; + + const config = mcpConfig || { sse_servers: [], stdio_servers: [] }; + + return ( +
        +
        +
        + {t(I18nKey.SETTINGS$MCP_TITLE)} +
        +

        + {t(I18nKey.SETTINGS$MCP_DESCRIPTION)} +

        +
        +
        +
        + e.stopPropagation()} + > + Documentation + + setIsEditing(!isEditing)} + > + {isEditing + ? t(I18nKey.SETTINGS$MCP_CANCEL) + : t(I18nKey.SETTINGS$MCP_EDIT_CONFIGURATION)} + +
        +
        + +
        + {isEditing ? ( + + ) : ( + <> +
        +
        + +
        + +
        + +
        +
        + + {config.sse_servers.length === 0 && + config.stdio_servers.length === 0 && ( +
        + {t(I18nKey.SETTINGS$MCP_NO_SERVERS_CONFIGURED)} +
        + )} + + )} +
        +
        + ); +} diff --git a/frontend/src/components/features/settings/mcp-settings/mcp-config-viewer.tsx b/frontend/src/components/features/settings/mcp-settings/mcp-config-viewer.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7f849692099b20c457681314b9eaf3a77666d83a --- /dev/null +++ b/frontend/src/components/features/settings/mcp-settings/mcp-config-viewer.tsx @@ -0,0 +1,141 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { MCPConfig, MCPSSEServer, MCPStdioServer } from "#/types/settings"; +import { I18nKey } from "#/i18n/declaration"; + +interface MCPConfigViewerProps { + mcpConfig?: MCPConfig; +} + +interface SSEServerDisplayProps { + server: string | MCPSSEServer; +} + +function SSEServerDisplay({ server }: SSEServerDisplayProps) { + const { t } = useTranslation(); + + if (typeof server === "string") { + return ( +
        +
        + {t(I18nKey.SETTINGS$MCP_URL)}:{" "} + {server} +
        +
        + ); + } + + return ( +
        +
        + {t(I18nKey.SETTINGS$MCP_URL)}:{" "} + {server.url} +
        + {server.api_key && ( +
        + + {t(I18nKey.SETTINGS$MCP_API_KEY)}: + {" "} + {server.api_key ? "Set" : t(I18nKey.SETTINGS$MCP_API_KEY_NOT_SET)} +
        + )} +
        + ); +} + +interface StdioServerDisplayProps { + server: MCPStdioServer; +} + +function StdioServerDisplay({ server }: StdioServerDisplayProps) { + const { t } = useTranslation(); + + return ( +
        +
        + {t(I18nKey.SETTINGS$MCP_NAME)}:{" "} + {server.name} +
        +
        + {t(I18nKey.SETTINGS$MCP_COMMAND)}:{" "} + {server.command} +
        + {server.args && server.args.length > 0 && ( +
        + {t(I18nKey.SETTINGS$MCP_ARGS)}:{" "} + {server.args.join(" ")} +
        + )} + {server.env && Object.keys(server.env).length > 0 && ( +
        + {t(I18nKey.SETTINGS$MCP_ENV)}:{" "} + {Object.entries(server.env) + .map(([key, value]) => `${key}=${value}`) + .join(", ")} +
        + )} +
        + ); +} + +export function MCPConfigViewer({ mcpConfig }: MCPConfigViewerProps) { + const { t } = useTranslation(); + + if ( + !mcpConfig || + (mcpConfig.sse_servers.length === 0 && mcpConfig.stdio_servers.length === 0) + ) { + return null; + } + + return ( +
        +
        +

        + {t(I18nKey.SETTINGS$MCP_CONFIGURATION)} +

        + e.stopPropagation()} + > + {t(I18nKey.SETTINGS$MCP_LEARN_MORE)} + +
        + +
        +
        + {mcpConfig.sse_servers.length > 0 && ( +
        +

        + {t(I18nKey.SETTINGS$MCP_SSE_SERVERS)}{" "} + + ({mcpConfig.sse_servers.length}) + +

        + {mcpConfig.sse_servers.map((server, index) => ( + + ))} +
        + )} + + {mcpConfig.stdio_servers.length > 0 && ( +
        +

        + {t(I18nKey.SETTINGS$MCP_STDIO_SERVERS)}{" "} + + ({mcpConfig.stdio_servers.length}) + +

        + {mcpConfig.stdio_servers.map((server, index) => ( + + ))} +
        + )} +
        +
        +
        + ); +} diff --git a/frontend/src/components/features/settings/mcp-settings/mcp-json-editor.tsx b/frontend/src/components/features/settings/mcp-settings/mcp-json-editor.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cfc84e545f8e41e3e7500b7652f74688fda56450 --- /dev/null +++ b/frontend/src/components/features/settings/mcp-settings/mcp-json-editor.tsx @@ -0,0 +1,97 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { MCPConfig } from "#/types/settings"; +import { I18nKey } from "#/i18n/declaration"; +import { BrandButton } from "../brand-button"; + +interface MCPJsonEditorProps { + mcpConfig?: MCPConfig; + onChange: (config: MCPConfig) => void; +} + +export function MCPJsonEditor({ mcpConfig, onChange }: MCPJsonEditorProps) { + const { t } = useTranslation(); + const [configText, setConfigText] = useState(() => + mcpConfig + ? JSON.stringify(mcpConfig, null, 2) + : t(I18nKey.SETTINGS$MCP_DEFAULT_CONFIG), + ); + const [error, setError] = useState(null); + + const handleTextChange = (e: React.ChangeEvent) => { + setConfigText(e.target.value); + }; + + const handleSave = () => { + try { + const newConfig = JSON.parse(configText); + + // Validate the structure + if (!newConfig.sse_servers || !Array.isArray(newConfig.sse_servers)) { + throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_SSE_ARRAY)); + } + + if (!newConfig.stdio_servers || !Array.isArray(newConfig.stdio_servers)) { + throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_STDIO_ARRAY)); + } + + // Validate SSE servers + for (const server of newConfig.sse_servers) { + if ( + typeof server !== "string" && + (!server.url || typeof server.url !== "string") + ) { + throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_SSE_URL)); + } + } + + // Validate stdio servers + for (const server of newConfig.stdio_servers) { + if (!server.name || !server.command) { + throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_STDIO_PROPS)); + } + } + + onChange(newConfig); + setError(null); + } catch (e) { + setError( + e instanceof Error + ? e.message + : t(I18nKey.SETTINGS$MCP_ERROR_INVALID_JSON), + ); + } + }; + + return ( +
        +
        + {t(I18nKey.SETTINGS$MCP_CONFIG_DESCRIPTION)} +
        +