diff --git a/.dockerignore b/.dockerignore index 7271739c..488f2014 100644 --- a/.dockerignore +++ b/.dockerignore @@ -55,3 +55,17 @@ temp_extracted_asar dist-electron/ dist-electron-build/ out/ + +.venv +.venv-local +.venv-build +.vscode +.idea +.pytest_cache +.coverage +.coverage.* +.coverage.*.* +.coverage.*.*.* +.coverage.*.*.*.* +.coverage.*.*.*.*.* +.coverage.*.*.*.*.*.* \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1ade672d..802e8fd5 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,17 @@ BUILD_STATUS.md dist-electron/ dist-electron-build/ out/ + +.venv +.venv-local +.venv-build +.vscode +.idea +.pytest_cache +.coverage +.coverage.* +.coverage.*.* +.coverage.*.*.* +.coverage.*.*.*.* +.coverage.*.*.*.*.* +.coverage.*.*.*.*.*.* \ No newline at end of file diff --git a/README.md b/README.md index 3bd8cc0e..7ab90b2b 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ and assets without wiring multiple tools together. It’s not a full IDE or asse - **Unity UI Elements**: Generate Unity UI prefabs (health bars, buttons, dialogue boxes, HUD layouts) for both uGUI and UI Toolkit - **Incremental Asset Generation**: Save assets directly into an active Unity project - **Pixel-Art Sprites**: Generate and process 2D sprite sheets with automatic cropping -- **Unity MCP Integration**: Real-time interaction with the Unity Editor using [Unity-MCP-SK-Plugin](https://github.com/Ozymandros/Unity-MCP-SK-Plugin) and [Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server) +- **Unity MCP integration**: Python bridge [`unity-mcp-plugin`](https://github.com/Ozymandros/Unity-MCP-SK-Plugin) (Semantic Kernel; see `backend/requirements.txt` for Git vs local editable install) plus MCP server [**Unity-MCP-Server**](https://github.com/Ozymandros/Unity-MCP-Server) — both are required; see [docs/UNITY_MCP_SERVER.md](docs/UNITY_MCP_SERVER.md) for server install/Git URLs/env vars, and [docs/UNITY_MCP_INTEGRATION.md](docs/UNITY_MCP_INTEGRATION.md) for the full SK setup. - Save and reuse provider settings and preferences locally - Configure global and per-request system key prompts for tailored generation - Keep output structured so Unity can open it right away @@ -80,6 +80,7 @@ Scaffolded and functional. See docs for development and packaging details. The p - [Architecture overview](docs/ARCHITECTURE.md) - [Development guide](docs/DEVELOPMENT.md) +- [Unity MCP Server (install, Git URL, env vars)](docs/UNITY_MCP_SERVER.md) - [Packaging and distribution](docs/PACKAGING.md) - [System Prompts guide](docs/SYSTEM_PROMPTS.md) @@ -358,9 +359,18 @@ Electron app. - If a provider request errors, check `logs/` for the failed request log. - If a Docker build is slow, ensure `node_modules/` and venvs are ignored. -## Unity MCP Integration +## Unity MCP integration -See [docs/UNITY_MCP_INTEGRATION.md](docs/UNITY_MCP_INTEGRATION.md) for details on the Semantic Kernel MCP integration, configuration, and usage. +Dependencies and setup (both repos are required): + +| Role | GitHub | +|------|--------| +| MCP server (Unity tools, stdio) | [Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server) | +| Python Semantic Kernel plugin (`unity_mcp`) | [Unity-MCP-SK-Plugin](https://github.com/Ozymandros/Unity-MCP-SK-Plugin) | + +- **Unity MCP Server only (not the Python plugin):** [docs/UNITY_MCP_SERVER.md](docs/UNITY_MCP_SERVER.md) +- **Semantic Kernel + paths:** [docs/UNITY_MCP_INTEGRATION.md](docs/UNITY_MCP_INTEGRATION.md) +- **Expanded vs router SK modes:** [docs/UNITY_MCP_SK_INTEGRATION.md](docs/UNITY_MCP_SK_INTEGRATION.md) ## Download diff --git a/backend/app/agents/SKILLS_USAGE.md b/backend/app/agents/SKILLS_USAGE.md index 41a062f7..7780dda4 100644 --- a/backend/app/agents/SKILLS_USAGE.md +++ b/backend/app/agents/SKILLS_USAGE.md @@ -33,7 +33,7 @@ Provides real-time integration with a running Unity Editor via the Unity MCP Ser - Plugin Source: [Unity-MCP-SK-Plugin](https://github.com/Ozymandros/Unity-MCP-SK-Plugin) - Server Source: [Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server) -Requires the `unity-mcp-plugin` package installed in the Python environment. For the full tool list and parameter details, see the server’s tool reference (e.g. Unity-MCP-Server `Skills/SKILL.md` or README). +Requires the `unity-mcp-plugin` package installed in the Python environment (see `backend/requirements.txt` and [docs/UNITY_MCP_INTEGRATION.md](../../../docs/UNITY_MCP_INTEGRATION.md) for the SK bridge). **MCP server install, clone URL, and `UNITY_MCP_*` locations:** [docs/UNITY_MCP_SERVER.md](../../../docs/UNITY_MCP_SERVER.md). For the full tool list and parameter details, see the server’s tool reference (e.g. Unity-MCP-Server `Skills/SKILL.md` or README). **Path handling:** The server requires **projectPath** (project root) and **fileName** (or **folderName** for folder/list tools) on all file, scene, and asset tools. Pass `projectPath` as the project root directory and `fileName` as the path under the project (e.g. `Assets/Scenes/MyScene.unity`). Do not duplicate path segments (e.g. do not put the project root inside `fileName`). diff --git a/backend/app/agents/unity_mcp_plugin.py b/backend/app/agents/unity_mcp_plugin.py index 79f850c6..21780bce 100644 --- a/backend/app/agents/unity_mcp_plugin.py +++ b/backend/app/agents/unity_mcp_plugin.py @@ -1,11 +1,15 @@ import asyncio import logging +import os from contextlib import asynccontextmanager from semantic_kernel import Kernel -from semantic_kernel.connectors.mcp import MCPStdioPlugin +from unity_mcp import UnityMcpOptions, UnityMCPPlugin LOGGER = logging.getLogger("unity_mcp_plugin") +_ENV_MCP_COMMAND = "UNITY_MCP_COMMAND" +_ENV_MCP_ARGS = "UNITY_MCP_ARGS" +_DEFAULT_MCP_EXECUTABLE = "unity-mcp" async def _check_unity_mcp_live() -> bool: @@ -20,6 +24,9 @@ async def _check_unity_mcp_live() -> bool: plugin = kernel.get_plugin("UnityMCP") if plugin and "ping" in plugin: await kernel.invoke(plugin["ping"]) + elif hasattr(mcp_plugin, "is_healthy"): + if not mcp_plugin.is_healthy(): + return False return True except Exception as e: LOGGER.debug("Unity MCP live check failed: %s", e) @@ -41,22 +48,36 @@ def unity_mcp_plugin_available_for_writing() -> bool: @asynccontextmanager async def create_unity_mcp_plugin(): """ - Async context manager for the Unity MCP plugin (global unity-mcp tool). + Async context manager for the Unity MCP plugin. + + Uses the local `unity-mcp-plugin` package implementation and initializes + the plugin against the configured Unity MCP executable. + Use with: async with create_unity_mcp_plugin() as mcp_plugin: ... """ LOGGER.info("Creating UnityMCP plugin...") + command = os.environ.get(_ENV_MCP_COMMAND, _DEFAULT_MCP_EXECUTABLE).strip() or _DEFAULT_MCP_EXECUTABLE + mcp_args = os.environ.get(_ENV_MCP_ARGS, "").strip() + if mcp_args: + LOGGER.warning( + "%s is set but ignored by unity-mcp-plugin local package implementation.", + _ENV_MCP_ARGS, + ) + try: - plugin = MCPStdioPlugin( - name="UnityMCP", - description="Unity Editor automation tools", - command="unity-mcp", - args=[], - load_tools=True, - request_timeout=30, + options = UnityMcpOptions( + executable_path=command, + connection_timeout_seconds=30, + request_timeout_seconds=30, + enable_message_logging=False, ) - async with plugin as mcp_plugin: - LOGGER.info("UnityMCP plugin created successfully.") - yield mcp_plugin + plugin = UnityMCPPlugin.create(options) + await plugin.initialize() + LOGGER.info("UnityMCP plugin created successfully.") + try: + yield plugin + finally: + await plugin.cleanup() except Exception as e: LOGGER.error("Failed to create UnityMCP plugin: %s", e) raise diff --git a/backend/package.json b/backend/package.json index dcef7efd..2fbc51dd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "unity-generator-backend", - "version": "1.7.0", + "version": "1.8.0", "description": "Backend for Unity Generator", "private": true, "scripts": { diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8e27159a..e794b0e4 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "unity-generator-backend" -version = "0.10.0" +version = "1.8.0" description = "Unity project generation and finalization backend with AI integration" requires-python = ">=3.11" license = {text = "MIT"} diff --git a/backend/requirements.txt b/backend/requirements.txt index e50d0ace..867e6bbb 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,10 +1,16 @@ fastapi uvicorn mcp[cli] -semantic-kernel[mcp]>=1.15.0 pydantic requests Pillow jinja2 google-genai -anthropic \ No newline at end of file +anthropic +semantic-kernel[mcp]>=1.15.0 +# unity-mcp-plugin (Semantic Kernel bridge for Unity MCP) — use ONE of: +# (A) No sibling folder: install from Git (HTTPS or SSH). Example if you have access: +# unity-mcp-plugin @ git+ssh://git@github.com/Ozymandros/Unity-MCP-SK-Plugin.git +# (Public HTTPS fails with 404 if the repo is private — use SSH, a fork, or a wheel.) +# (B) Default dev layout: clone Unity-MCP-SK-Plugin next to this repo as "unity-mcp-server-plugin": +unity-mcp-plugin @ git+https://github.com/Ozymandros/unity-mcp-server-plugin.git \ No newline at end of file diff --git a/backend/tests/test_unity_mcp_pluginwrapper.py b/backend/tests/test_unity_mcp_pluginwrapper.py index 38014676..936b017e 100644 --- a/backend/tests/test_unity_mcp_pluginwrapper.py +++ b/backend/tests/test_unity_mcp_pluginwrapper.py @@ -1,14 +1,42 @@ +from __future__ import annotations + +from unittest.mock import AsyncMock, MagicMock, patch + import pytest +from app.agents.unity_mcp_plugin import create_unity_mcp_plugin, unity_mcp_plugin_available_for_writing + -# When re-enabling: update tests to match the new Unity-MCP-Server contract -# (projectPath + fileName/folderName on tools, ping with no args). -@pytest.mark.skip(reason="Requires running MCP server or better mocking of async connection") @pytest.mark.asyncio -async def test_unity_mcp_pluginwrapper_connect_and_discover(monkeypatch): - pass +async def test_unity_mcp_pluginwrapper_create_initialize_and_cleanup() -> None: + """Ensure wrapper creates plugin via local package and performs lifecycle calls.""" + mock_plugin = MagicMock() + mock_plugin.initialize = AsyncMock() + mock_plugin.cleanup = AsyncMock() + + with patch("app.agents.unity_mcp_plugin.UnityMCPPlugin.create", return_value=mock_plugin) as mock_create: + async with create_unity_mcp_plugin() as plugin: + assert plugin is mock_plugin + mock_create.assert_called_once() + mock_plugin.initialize.assert_awaited_once() + mock_plugin.cleanup.assert_awaited_once() + -@pytest.mark.skip(reason="Requires running MCP server or better mocking of async connection") @pytest.mark.asyncio -async def test_unity_mcp_pluginwrapper_error(monkeypatch): - pass +async def test_unity_mcp_pluginwrapper_error_propagates() -> None: + """Ensure initialization errors are not swallowed by wrapper context manager.""" + mock_plugin = MagicMock() + mock_plugin.initialize = AsyncMock(side_effect=RuntimeError("boom")) + mock_plugin.cleanup = AsyncMock() + + with patch("app.agents.unity_mcp_plugin.UnityMCPPlugin.create", return_value=mock_plugin): + with pytest.raises(RuntimeError, match="boom"): + async with create_unity_mcp_plugin(): + pass + mock_plugin.cleanup.assert_not_awaited() + + +def test_unity_mcp_plugin_available_for_writing_false_on_failure() -> None: + """Availability helper must return False when plugin creation fails.""" + with patch("app.agents.unity_mcp_plugin.create_unity_mcp_plugin", side_effect=RuntimeError("down")): + assert unity_mcp_plugin_available_for_writing() is False diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 3a085564..bd21085a 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -340,7 +340,7 @@ Finalize supports two backends. In **Unity Engine Settings** (or the finalize re - **`batch`**: Always use injected Editor scripts and Unity batch mode. Use this for CI or headless environments where no Editor window is running. - **`mcp`**: Always use the MCP-Unity (Semantic Kernel) plugin. Fails with a clear error if the plugin is not configured. -To use MCP for local development, set **`UNITY_USE_MCP=1`** and either **`UNITY_MCP_SERVER_URL`** (HTTP) or ensure **`unity-mcp`** (or the command in **`UNITY_MCP_COMMAND`**) is on PATH for stdio. The backend connects via the MCP Python SDK and calls the Unity-MCP-Server contract tools. For CI pipelines, set `unity_automation_mode` to `"batch"` so finalize does not depend on MCP. See [Unity Engine Integration](UNITY_INTEGRATION.md) for the tool contract and configuration. +To use MCP for local development, set **`UNITY_USE_MCP=1`** and either **`UNITY_MCP_SERVER_URL`** (HTTP) or ensure **`unity-mcp`** (or the command in **`UNITY_MCP_COMMAND`**) is on PATH for stdio. The backend connects via the MCP Python SDK and calls the Unity-MCP-Server contract tools. For CI pipelines, set `unity_automation_mode` to `"batch"` so finalize does not depend on MCP. **Server install, Git clone URL, and where env vars are read:** [UNITY_MCP_SERVER.md](UNITY_MCP_SERVER.md). See [Unity Engine Integration](UNITY_INTEGRATION.md) for the tool contract and configuration. ### Debugging Unity batch execution diff --git a/docs/UNITY_MCP_INTEGRATION.md b/docs/UNITY_MCP_INTEGRATION.md index aeb3ef5e..48782bc5 100644 --- a/docs/UNITY_MCP_INTEGRATION.md +++ b/docs/UNITY_MCP_INTEGRATION.md @@ -1,6 +1,22 @@ # Unity MCP Integration with Semantic Kernel -This document describes the integration of Unity's Model Context Protocol (MCP) with Semantic Kernel using the official MCPSsePlugin, replacing legacy TCP/JSON-RPC code with a robust, maintainable, and auto-discoverable approach. +> **Unity MCP Server (install, Git URL, env vars — not the Python plugin):** see **[UNITY_MCP_SERVER.md](./UNITY_MCP_SERVER.md)**. + +This document describes the integration of Unity's Model Context Protocol (MCP) with Semantic Kernel using the Python **`unity-mcp-plugin`** package from **[Unity-MCP-SK-Plugin](https://github.com/Ozymandros/Unity-MCP-SK-Plugin)** (Git URL or local editable install—see `backend/requirements.txt`). The backend talks to the **Unity MCP Server** over stdio. Both pieces are required and are separate GitHub repositories. + +## Required dependencies (two repositories) + +| Component | Purpose | GitHub repository | How this project uses it | +|-----------|---------|-------------------|----------------------------| +| **Unity-MCP-Server** | Official MCP server that exposes Unity Editor automation tools (JSON-RPC over stdio when used as a global .NET tool). | **[github.com/Ozymandros/Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server)** | Install and run the `unity-mcp` global tool (or your built server executable). Point `UNITY_MCP_COMMAND` at that executable if it is not on `PATH`. | +| **unity-mcp-plugin** (Python distribution) | Semantic Kernel bridge: discovers MCP tools and registers them as SK functions. | **[github.com/Ozymandros/Unity-MCP-SK-Plugin](https://github.com/Ozymandros/Unity-MCP-SK-Plugin)** | `backend/requirements.txt`: default **`-e ../unity-mcp-server-plugin`**, or switch to **`unity-mcp-plugin @ git+...`** (SSH/fork if private). Imported as `from unity_mcp import UnityMCPPlugin, UnityMcpOptions`. | + +**Install order (typical dev setup):** + +1. Clone **[Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server)** and install the server per its README (often a global .NET tool named `unity-mcp` on your `PATH`; follow that repo’s install script or `dotnet tool` instructions). +2. Install **`unity-mcp-plugin`**: either follow the **default** in `backend/requirements.txt` (sibling clone + `-e`), **or** comment that out and use **`unity-mcp-plugin @ git+ssh://...`** (or your fork) so the app does not depend on a fixed folder next to this repo. If `git+https` fails with **404**, the GitHub repo is likely **private**—use SSH, a public fork, or a wheel. + +**Optional dev workflow:** clone **Unity-MCP-SK-Plugin** anywhere and use `pip install -e /path/to/clone` while developing the bridge. ## Path terminology (don’t confuse these) @@ -10,13 +26,13 @@ This document describes the integration of Unity's Model Context Protocol (MCP) ## Overview - **No Unity/C# code required**: This integration is entirely Python-side. - **Auto-discovery**: All Unity tools exposed by the MCP server are automatically available to Semantic Kernel. -- **No manual kernel_function decorators**: The MCPSsePlugin handles tool registration. +- **No manual kernel_function decorators**: The local Unity MCP plugin package handles tool registration. - **Robust error handling**: Connection and protocol errors are surfaced clearly. ## Configuration -- **Install requirements**: Ensure `semantic-kernel[mcp]` is in `backend/requirements.txt`. +- **Install requirements**: Ensure `backend/requirements.txt` installs **`unity-mcp-plugin`** (see comments in that file: Git URL vs `-e` sibling path). - **Set MCP server command** (this is the **Unity MCP Server executable**, not a Unity project path): - - `UNITY_MCP_COMMAND`: Path to the MCP server executable (default: `unity-mcp-server`) + - `UNITY_MCP_COMMAND`: Path to the MCP server executable (default: `unity-mcp`) - `UNITY_MCP_ARGS`: Arguments for the executable (default: empty) Example (path to the **server executable**, not to a Unity project): @@ -25,7 +41,7 @@ export UNITY_MCP_COMMAND="C:\Path\To\UnityMcp.Server.exe" ``` ## Usage -- The integration is implemented in `backend/app/agents/unity_mcp_plugin.py` as `UnityMCPPluginWrapper`. +- The integration is implemented in `backend/app/agents/unity_mcp_plugin.py` using `unity_mcp.UnityMCPPlugin` from the **`unity-mcp-plugin`** package. - The agent in `backend/app/agents/unity_agent.py` uses this wrapper to connect and interact with Unity tools. - All Unity automation tools (e.g., create_scene, create_script) are auto-discovered and callable from the agent. @@ -60,8 +76,11 @@ export UNITY_MCP_COMMAND="C:\Path\To\UnityMcp.Server.exe" - All Unity tool changes/additions are now automatically available to the agent—no Python code changes required. ## References -- [Semantic Kernel MCP Plugin Documentation](https://learn.microsoft.com/en-us/semantic-kernel/concepts/plugins/adding-mcp-plugins) -- [Model Context Protocol (MCP) Spec](https://modelcontextprotocol.io/) +- **[Unity MCP Server — install & config in this repo](./UNITY_MCP_SERVER.md)** (Git URLs, `UNITY_MCP_COMMAND`, file locations) +- [Unity-MCP-Server (GitHub)](https://github.com/Ozymandros/Unity-MCP-Server) +- [Unity-MCP-SK-Plugin / unity-mcp-plugin (GitHub)](https://github.com/Ozymandros/Unity-MCP-SK-Plugin) +- [Semantic Kernel — MCP plugins](https://learn.microsoft.com/en-us/semantic-kernel/concepts/plugins/adding-mcp-plugins) +- [Model Context Protocol (MCP) spec](https://modelcontextprotocol.io/) --- diff --git a/docs/UNITY_MCP_SERVER.md b/docs/UNITY_MCP_SERVER.md new file mode 100644 index 00000000..17060b50 --- /dev/null +++ b/docs/UNITY_MCP_SERVER.md @@ -0,0 +1,142 @@ +# Unity MCP Server (Unity-MCP-Server) + +This document is about the **MCP server that talks to the Unity Editor** — **not** the Python `unity-mcp-plugin` package. +For the Semantic Kernel Python bridge, see [UNITY_MCP_INTEGRATION.md](./UNITY_MCP_INTEGRATION.md) and [UNITY_MCP_SK_INTEGRATION.md](./UNITY_MCP_SK_INTEGRATION.md). + +## Source repository (install here) + +| Item | Value | +|------|--------| +| **GitHub** | **[github.com/Ozymandros/Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server)** | +| **Clone (HTTPS)** | `https://github.com/Ozymandros/Unity-MCP-Server.git` | +| **GHCR image (Docker)** | **`ghcr.io/ozymandros/unity-mcp-server`** — package page: **[github.com/Ozymandros/Unity-MCP-Server/pkgs/container/unity-mcp-server](https://github.com/Ozymandros/Unity-MCP-Server/pkgs/container/unity-mcp-server)** | +| **Typical CLI name** | `unity-mcp` (global .NET tool or built executable — follow upstream README) | + +**Installation** is defined **upstream**: clone the repo and follow **Unity-MCP-Server** `README.md` (e.g. `dotnet tool install`, Unity package, or build steps). This Unity-Generator repo does **not** ship the server binary; it expects the executable to exist locally or on `PATH`. + +### Docker install from GHCR + +Official container images are published here: + +**[Unity-MCP-Server — Package `unity-mcp-server` (GHCR)](https://github.com/Ozymandros/Unity-MCP-Server/pkgs/container/unity-mcp-server)** + +| Item | Value | +|------|--------| +| **Registry / image** | `ghcr.io/ozymandros/unity-mcp-server` | +| **Tags** | Use the package page for current tags (e.g. `latest`, version tags like `v3.2.1`, or `sha-` digests). Prefer a **version tag** or digest for reproducible installs. | + +**Pull** (replace `` with a tag from the [package page](https://github.com/Ozymandros/Unity-MCP-Server/pkgs/container/unity-mcp-server), e.g. `latest` or `v3.2.1`): + +```bash +docker pull ghcr.io/ozymandros/unity-mcp-server: +``` + +**Run** (example only — **port and env vars** must match **Unity-MCP-Server** docs for MCP HTTP vs stdio; adjust `-p` and any `-e` flags per upstream): + +```bash +docker run --rm -p : ghcr.io/ozymandros/unity-mcp-server: +``` + +- If the image is **private**, authenticate to GHCR before `pull` / `run`, e.g. [GitHub docs: Working with the Container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) (`docker login ghcr.io` with a PAT that has `read:packages`). +- For **Unity-Generator** integration over **HTTP**, publish the port the server uses for **Streamable HTTP**, then set **`UNITY_MCP_SERVER_URL`** (see table below). + +You can also build/run from a local **`Dockerfile`** / **`docker-compose.yml`** in the **[Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server)** repo if you prefer; the **GHCR** image is the usual binary-free install path. + +#### How GHCR Docker maps to this app + +| Integration path in this app | Transport | How Docker usually fits | +|------------------------------|-----------|-------------------------| +| **Finalize / orchestrator** (`unity_mcp_client.py`) | **Streamable HTTP** if you set **`UNITY_MCP_SERVER_URL`** | Run the container with a **published port** (e.g. `-p 8080:8080`). Set **`UNITY_USE_MCP=1`** and **`UNITY_MCP_SERVER_URL`** to the MCP HTTP endpoint the server exposes (must match upstream — often `http://127.0.0.1:/...`). In `unity_mcp_client.py`, if **`UNITY_MCP_SERVER_URL`** is set, the **HTTP** client is used and **stdio** (`UNITY_MCP_COMMAND`) is not used for that call. | +| **Semantic Kernel agent** (`unity_mcp_plugin.py`) | **stdio** (spawns **`UNITY_MCP_COMMAND`**) | A container is **not** a drop-in replacement unless you run a **host binary** or a small wrapper. Prefer installing the server on the host for the agent, or extend **`unity-mcp-plugin`** / server to support HTTP for the SK path (not configured in this repo today). | + +**Typical Docker flow for this app (HTTP-capable server):** + +1. **Pull** from GHCR and **run** the container (see [package tags](https://github.com/Ozymandros/Unity-MCP-Server/pkgs/container/unity-mcp-server)): + + ```bash + docker pull ghcr.io/ozymandros/unity-mcp-server: + docker run --rm -p : ghcr.io/ozymandros/unity-mcp-server: + ``` + + Alternatively, use **`docker compose`** or a **Dockerfile** from the **[Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server)** repo if upstream documents it. + +2. Confirm the Unity Editor / MCP server can still communicate (same machine or documented networking). + +3. For **finalize** MCP tools, set for the backend process: + + ```sh + set UNITY_USE_MCP=1 + set UNITY_MCP_SERVER_URL=http://127.0.0.1: + ``` + + Use the **same URL** your browser or `curl` would use for the server’s **Streamable HTTP** MCP endpoint per upstream docs. + +4. For HTTP-only finalize flows, **`UNITY_MCP_SERVER_URL`** alone is enough for `unity_mcp_client` (stdio command is skipped when the URL is set). + +**Security:** If the container exposes MCP on a network interface, restrict with firewall, bind to `127.0.0.1`, or upstream auth tokens — do not expose an unsecured MCP endpoint to the internet. + +--- + +## What this app uses it for + +- **Semantic Kernel agent** (`unity_mcp_plugin.py`): spawns/connects to the server process for **tool discovery** and Unity automation from the LLM. +- **Finalize / orchestrator** (`unity_mcp_client.py`, optional): when **`UNITY_USE_MCP=1`**, uses the MCP Python SDK to call **Unity-MCP-Server** tools (e.g. package install, scene setup) over **stdio** or **Streamable HTTP**. + +--- + +## Where configuration is defined in this repository + +### 1. Environment variables (runtime — backend) + +| Variable | Purpose | Default / notes | +|----------|-----------|-----------------| +| **`UNITY_MCP_COMMAND`** | Path or name of the **server executable** (not your Unity project folder). | `unity-mcp` | +| **`UNITY_MCP_ARGS`** | Extra arguments passed to that executable (space-separated in code). | empty | +| **`UNITY_MCP_SERVER_URL`** | Optional **HTTP** MCP endpoint (Streamable HTTP). If set, used when applicable; otherwise **stdio** via `UNITY_MCP_COMMAND`. | empty | +| **`UNITY_USE_MCP`** | **`1` / `true` / `yes`** enables MCP in **`unity_mcp_client.py`** (finalize/orchestrator path). | off (MCP client not used) | + +**Code references:** + +- `backend/app/agents/unity_mcp_plugin.py` — `UNITY_MCP_COMMAND`, `UNITY_MCP_ARGS` (SK plugin). +- `backend/app/services/unity_mcp_client.py` — `UNITY_USE_MCP`, `UNITY_MCP_SERVER_URL`, `UNITY_MCP_COMMAND`, `UNITY_MCP_ARGS`. + +### 2. Sample MCP JSON (optional) + +- **`backend/mcp_config.json`** — example `mcpServers.unity-mcp-server` with `command` / `args` (for tools that consume MCP-style config; **not** the only source of truth for FastAPI). + +### 3. Cursor / VS Code MCP (IDE only) + +- **`.vscode/mcp.json`** — **`mcpServers.unity-mcp-server`**: `command` + `args` for the **editor** MCP client. This configures **Cursor/VS Code**, not the Unity-Generator backend unless you mirror the same env vars. + +**Important:** `UNITY_MCP_*` env vars apply to **Python processes** (backend). The IDE file is separate; keep **`unity-mcp`** on `PATH` or align `command` with the same binary you use for the backend. + +### 4. Docs cross-references + +- **`docs/DEVELOPMENT.md`** — Automation mode (`UNITY_USE_MCP`, batch vs MCP). +- **`docs/UNITY_MCP_INTEGRATION.md`** — End-to-end SK + server overview. +- **`backend/app/agents/SKILLS_USAGE.md`** — Unity MCP skill notes and server link. + +--- + +## Path terminology (server vs Unity project) + +- **`UNITY_MCP_COMMAND`** must point to the **MCP server binary** (e.g. `unity-mcp` or `UnityMcp.Server.exe`), **not** to the Unity project root (`Assets/`). +- Your **Unity project path** is passed separately to tools (e.g. `projectPath` / session) — see [UNITY_MCP_INTEGRATION.md](./UNITY_MCP_INTEGRATION.md) § Path terminology. + +--- + +## Quick checklist + +1. Install **Unity-MCP-Server** from **[GitHub](https://github.com/Ozymandros/Unity-MCP-Server)** (upstream README), **or** run it from **[GHCR](https://github.com/Ozymandros/Unity-MCP-Server/pkgs/container/unity-mcp-server)** (`docker pull ghcr.io/ozymandros/unity-mcp-server:` — see [Docker install from GHCR](#docker-install-from-ghcr)). +2. Ensure the server is running with Unity / Editor as required by that project. +3. Set **`UNITY_MCP_COMMAND`** if `unity-mcp` is not on `PATH` (stdio / SK agent path). +4. For finalize MCP automation, set **`UNITY_USE_MCP=1`** and configure **stdio** (`UNITY_MCP_COMMAND`) or **`UNITY_MCP_SERVER_URL`** (Docker/HTTP) per `unity_mcp_client.py`. + +--- + +## Related repositories + +| Role | GitHub | +|------|--------| +| **MCP server (this page)** | [Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server) | +| **Python SK bridge (`unity_mcp` package)** | [Unity-MCP-SK-Plugin](https://github.com/Ozymandros/Unity-MCP-SK-Plugin) | diff --git a/docs/UNITY_MCP_SK_INTEGRATION.md b/docs/UNITY_MCP_SK_INTEGRATION.md index 7fe9826b..56c5f533 100644 --- a/docs/UNITY_MCP_SK_INTEGRATION.md +++ b/docs/UNITY_MCP_SK_INTEGRATION.md @@ -1,53 +1,60 @@ -# Integrating Unity-MCP with Semantic Kernel (SK) in .NET 10 +# Unity MCP + Semantic Kernel Integration (Python) -## Overview -To connect your existing Unity-MCP server to Semantic Kernel (SK), you do **not** need to rewrite the server or create a separate plugin project. Instead, use the official MCP connector package for SK, which allows the kernel to discover and use your MCP tools automatically. +> **Install/configure the Unity MCP *server* (Git URL, env vars):** [UNITY_MCP_SERVER.md](./UNITY_MCP_SERVER.md). -## Steps +This project relies on the **`unity-mcp-plugin`** Python package for Semantic Kernel integration. Source and releases live in **[Unity-MCP-SK-Plugin](https://github.com/Ozymandros/Unity-MCP-SK-Plugin)**. That plugin is a **client** of the MCP server **[Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server)** (stdio): you must have the server executable available (e.g. `unity-mcp` on `PATH`) or set `UNITY_MCP_COMMAND` (see [UNITY_MCP_INTEGRATION.md](./UNITY_MCP_INTEGRATION.md)). -### 1. Install the MCP Connector Package +| Piece | Repository | +|-------|------------| +| MCP server (tools, Unity contract) | [github.com/Ozymandros/Unity-MCP-Server](https://github.com/Ozymandros/Unity-MCP-Server) | +| Python SK plugin (`unity_mcp` package) | [github.com/Ozymandros/Unity-MCP-SK-Plugin](https://github.com/Ozymandros/Unity-MCP-SK-Plugin) | -```sh -dotnet add package Microsoft.SemanticKernel.Connectors.Mcp -``` +**Dependency:** `backend/requirements.txt` documents two ways to get **`unity-mcp-plugin`**: **Git URL** (no sibling folder) or **`-e ../unity-mcp-server-plugin`** (default sibling clone). Use **SSH** or a fork if the GitHub repo is private. For hacking the bridge, clone that repo anywhere and use `pip install -e `. + +## Expanded Mode (Recommended) + +```python +from unity_mcp import UnityMCPPlugin + +# Discovers MCP tools and registers them as individual SK functions +# under one plugin namespace ("unity" by default). +kernel = await UnityMCPPlugin.create_kernel_with_unity(plugin_name="unity") -### 2. Register the MCP Server as a Plugin in SK +# Example invocation: +result = await kernel.invoke("unity", "unity_create_scene", projectPath="C:/MyProj", fileName="Assets/Scenes/Main.unity") +``` -```csharp -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Connectors.Mcp; +## Router Mode (Backward Compatible) -var builder = Kernel.CreateBuilder(); +```python +from semantic_kernel import Kernel +from unity_mcp import UnityMCPPlugin -// Register the global unity-mcp tool via Stdio -var mcpPlugin = new McpStdioPlugin( - "UnityManager", - "unity-mcp" // The global tool name -); +plugin = UnityMCPPlugin.create() +await plugin.initialize() -builder.Plugins.Add(mcpPlugin); -var kernel = builder.Build(); +kernel = Kernel() +kernel.add_plugin(plugin, plugin_name="unity") -// Example: Call a tool -var result = await kernel.InvokeAsync("UnityManager", "create_scene", new() { - ["name"] = "MainScene" -}); +result = await kernel.invoke( + "unity", + "invoke_unity_tool", + tool_name="unity_create_scene", + arguments_json='{"projectPath":"C:/MyProj","fileName":"Assets/Scenes/Main.unity"}', +) ``` -### 3. How It Works -- The MCP server is a global tool (e.g., `unity-mcp`) and is discovered automatically. -- The connector queries the MCP server for available tools and exposes them as KernelFunctions. -- No .json/.yaml plugin project or manual tool registration is needed. +## Tradeoffs + +- **Expanded mode**: better discoverability and more reliable autonomous tool calling. +- **Router mode**: smaller tool-definition footprint but weaker discoverability. + +## Metadata Fidelity + +The `unity-mcp-plugin` package maps MCP tool schemas into SK metadata (parameter name, description, required/default, and base JSON types: `string`, `number`, `integer`, `boolean`, `array`, `object`). -### 4. Best Practices -- Ensure `unity-mcp` is installed and available in your system PATH. -- Use the latest version of the connector for .NET 10 compatibility. -- Tool descriptions and parameters from MCP are surfaced in SK automatically. +Python SK limitation: advanced JSON Schema constructs are partially surfaced (typically via schema metadata) but not always fully interpreted as first-class planner constraints. ---- +## Router / parameter safety -**Summary:** -- No plugin project required. -- Use the official connector package. -- Register your MCP server as shown above. -- All MCP tools become available in SK with no extra configuration. +Older in-repo experiments under `backend/app/agents/unity_mcp_sk/` (removed) duplicated router logic; that path no longer exists here. The canonical implementation lives in the `unity-mcp-plugin` package: expanded-mode handlers only forward kwargs whose names match the MCP tool schema, and `invoke_unity_tool` strips `tool_name` / `toolName` from parsed JSON so they are never sent as MCP parameters. diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 7575545d..cbb6a0de 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -56,3 +56,17 @@ Thumbs.db .cursor/ .qwen/ .kiro/ + +.venv +.venv-local +.venv-build +.vscode +.idea +.pytest_cache +.coverage +.coverage.* +.coverage.*.* +.coverage.*.*.* +.coverage.*.*.*.* +.coverage.*.*.*.*.* +.coverage.*.*.*.*.*.* diff --git a/frontend/package.json b/frontend/package.json index e7a9a431..5c01eb58 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,7 +2,7 @@ "name": "unity-generator-frontend", "description": "Frontend for Unity Generator", "private": true, - "version": "1.7.0", + "version": "1.8.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.css b/frontend/src/App.css index 17315357..dbc8bf86 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,70 +1,111 @@ -.layout { - display: grid; - grid-template-columns: 220px 1fr; - min-height: 100vh; - font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; -} -aside { - background: #0f172a; - color: white; - padding: 24px 16px; -} -.status { - margin-top: 6px; - font-size: 0.85rem; -} -.status.online { - color: #86efac; -} -.status.offline { - color: #fca5a5; -} -nav { - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 16px; -} -button { - padding: 10px 12px; - border: none; - border-radius: 6px; - background: #1e293b; - color: white; - cursor: pointer; - text-align: left; -} -button.active { - background: #2563eb; -} -main { - padding: 24px; - background: #f8fafc; -} - -/* Global icon button used next to provider selects */ -.icon-btn { - padding: 6px 10px; - border: 1px solid #d1d5db; - border-radius: 6px; - background: #ffffff; - color: #374151; - font-size: 1rem; - cursor: pointer; +/* Global App Styles */ +.line-height-1 { line-height: 1; - transition: background 0.15s, border-color 0.15s; - align-self: flex-end; - margin-bottom: 2px; -} -.icon-btn:hover:not(:disabled) { - background: #f3f4f6; - border-color: #2563eb; - color: #2563eb; -} -.icon-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} -.panel { - margin: 1rem; -} \ No newline at end of file +} + +/* Sidebar project name: borderless by default, border on hover/focus */ +.project-name-field :deep(.v-field__overlay) { + opacity: 0; +} + +.project-name-field:hover :deep(.v-field__overlay), +.project-name-field.v-field--focused :deep(.v-field__overlay) { + opacity: 1; +} + +.project-name-field :deep(.v-field) { + --v-field-border-opacity: 0; +} + +.project-name-field:hover :deep(.v-field), +.project-name-field.v-field--focused :deep(.v-field) { + --v-field-border-opacity: 0.6; +} + +.app-sidebar { + border-right: 1px solid rgba(255, 255, 255, 0.2) !important; +} + +.nav-item { + transition: all 0.2s ease; +} + +.nav-item.v-list-item--active { + background: rgba(var(--v-theme-primary), 0.15) !important; +} + +/* Increase contrast for outlined inputs */ +:deep(.v-field--variant-outlined) { + --v-field-border-opacity: 0.5 !important; +} + +:deep(.v-field--focused.v-field--variant-outlined) { + --v-field-border-opacity: 1 !important; +} + +:deep(.v-label.v-field-label) { + opacity: 0.9 !important; + color: rgb(var(--v-theme-on-surface)) !important; + font-weight: 500 !important; +} + +:deep(.v-field--focused .v-label.v-field-label) { + opacity: 1 !important; + color: rgb(var(--v-theme-primary)) !important; +} + +:deep(.v-field__input::placeholder) { + opacity: 0.5 !important; + color: rgb(var(--v-theme-on-surface)) !important; +} + +:deep(.v-checkbox .v-label) { + opacity: 1 !important; + color: rgb(var(--v-theme-on-surface)) !important; + font-weight: 500 !important; +} + +/* Global Data Table Adjustments */ +:deep(.v-data-table) { + background: transparent !important; +} + +:deep(.v-data-table-header__content) { + font-weight: 800 !important; + color: #ffffff !important; + text-transform: uppercase; + font-size: 0.7rem; + letter-spacing: 0.05em; +} + +.app-main { + background: transparent; +} + +/* Global Scrollbar Styles */ +:root { + scrollbar-width: thin; + scrollbar-color: rgba(var(--v-theme-primary), 0.2) transparent; +} + +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(var(--v-theme-primary), 0.2); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(var(--v-theme-primary), 0.4); +} + +.content-wrapper { + height: 100%; + overflow-y: auto; +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 0f738e86..34e951f1 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -27,16 +27,13 @@ const { t } = useI18n(); const { tabs, active, backendStatus, setActive } = useApp(); const { projectName, sessionProjectResetKey, resetSessionProject } = useSessionProject(); const { loadProject } = useOpenProject(); -// Register the system media-query listener at app root so it lives for the full app lifetime useTheme(); -// Register locale at app root so Electron IPC sync is initialised once useLocale(); const drawer = ref(true); const store = useIntelligenceStore(); const unityUi = useUnityProjectUiStore(); unityUi.enableAutoPersist(); -/** Map internal tab key → i18n nav key */ const TAB_NAV_KEY: Record = { Settings: "app.nav.settings", Scenes: "app.nav.scenes", @@ -52,28 +49,34 @@ const TAB_NAV_KEY: Record = { const getTabIcon = (tab: string) => { switch (tab) { - case 'Settings': return 'mdi-cog-outline'; - case 'Scenes': return 'mdi-view-dashboard-outline'; - case 'Code': return 'mdi-xml'; - case 'Text': return 'mdi-text-box-outline'; - case 'Image': return 'mdi-image-outline'; - case 'Sprites': return 'mdi-grid'; - case 'Audio': return 'mdi-volume-high'; - case 'Unity UI': return 'mdi-palette-outline'; - case 'Unity Physics': return 'mdi-atom'; - case 'Unity Project': return 'mdi-unity'; - default: return 'mdi-circle-medium'; + case "Settings": + return "mdi-cog-outline"; + case "Scenes": + return "mdi-view-dashboard-outline"; + case "Code": + return "mdi-xml"; + case "Text": + return "mdi-text-box-outline"; + case "Image": + return "mdi-image-outline"; + case "Sprites": + return "mdi-grid"; + case "Audio": + return "mdi-volume-high"; + case "Unity UI": + return "mdi-palette-outline"; + case "Unity Physics": + return "mdi-atom"; + case "Unity Project": + return "mdi-unity"; + default: + return "mdi-circle-medium"; } }; -// Menu event handlers let unsubscribeNewProject: (() => void) | undefined; let unsubscribeOpenProject: (() => void) | undefined; -/** - * Handle "New Project" menu action. - * Resets the frontend store and reloads all initial values from backend/DB. - */ async function handleNewProject() { try { resetSessionProject("UnityProject"); @@ -82,9 +85,9 @@ async function handleNewProject() { unityUi.reset(); unityUi.status = "New project started (UI state reset)."; unityUi.tone = "ok"; - setActive('Unity Project'); + setActive("Unity Project"); } catch { - setActive('Unity Project'); + setActive("Unity Project"); } try { store.$reset(); @@ -94,13 +97,9 @@ async function handleNewProject() { } } -/** - * Handle "Open Project" menu action. - * Delegates to the shared useOpenProject composable. - */ async function handleOpenProject(projectPath: string) { await loadProject(projectPath); - setActive('Unity Project'); + setActive("Unity Project"); } onMounted(() => { @@ -174,116 +173,4 @@ onUnmounted(() => { - + diff --git a/frontend/src/components/ManagementDashboard.css b/frontend/src/components/ManagementDashboard.css new file mode 100644 index 00000000..9211e2f4 --- /dev/null +++ b/frontend/src/components/ManagementDashboard.css @@ -0,0 +1,69 @@ +.glass-drawer { + background: rgba(15, 23, 42, 0.98) !important; + backdrop-filter: blur(20px); + border-right: 1px solid rgba(255, 255, 255, 0.2) !important; +} + +.glass-card { + background: rgba(30, 41, 59, 0.8) !important; + backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.2) !important; + border-radius: 20px !important; + transition: + transform 0.2s, + box-shadow 0.2s; +} + +.transparent-table :deep(table) { + background: transparent !important; +} + +.transparent-table :deep(thead th) { + font-weight: 800 !important; + color: #cbd5e1 !important; + /* Brighter gray */ + text-transform: uppercase; + font-size: 0.75rem; + letter-spacing: 0.05em; +} + +.transparent-table :deep(tr:hover) { + background: rgba(255, 255, 255, 0.03) !important; +} + +.code-font :deep(textarea) { + font-family: "JetBrains Mono", "Fira Code", "Roboto Mono", monospace !important; + font-size: 0.85rem !important; + line-height: 1.7 !important; + color: #e2e8f0 !important; +} + +.v-list-subheader { + font-size: 0.7rem !important; + font-weight: 900 !important; + letter-spacing: 0.1em !important; + color: #cbd5e1 !important; + /* Brighter gray */ +} + +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.15); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--v-primary-base); +} + +.h-100 { + height: 100%; +} diff --git a/frontend/src/components/ManagementDashboard.vue b/frontend/src/components/ManagementDashboard.vue index 2c2e67ba..55fa6689 100644 --- a/frontend/src/components/ManagementDashboard.vue +++ b/frontend/src/components/ManagementDashboard.vue @@ -1,58 +1,58 @@ - + diff --git a/frontend/src/components/UnityUIPanel/UnityUIPanel.css b/frontend/src/components/UnityUIPanel/UnityUIPanel.css new file mode 100644 index 00000000..16859d31 --- /dev/null +++ b/frontend/src/components/UnityUIPanel/UnityUIPanel.css @@ -0,0 +1,42 @@ +.panel { + max-width: 820px; + margin: 1rem; +} + +.header { + margin-bottom: 20px; +} + +h2 { + margin: 0 0 6px; +} + +.subtitle { + margin: 0; + color: #666; + font-size: 0.95em; +} + +.field-group { + margin-bottom: 12px; +} + +.options-row { + display: flex; + gap: 12px; + margin-bottom: 10px; +} + +.cursor-pointer { + cursor: pointer; +} + +.cursor-pointer:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.results { + margin-top: 20px; + padding-top: 16px; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} diff --git a/frontend/src/components/UnityUIPanel/UnityUIPanel.vue b/frontend/src/components/UnityUIPanel/UnityUIPanel.vue index 422af546..d99c4921 100644 --- a/frontend/src/components/UnityUIPanel/UnityUIPanel.vue +++ b/frontend/src/components/UnityUIPanel/UnityUIPanel.vue @@ -57,17 +57,6 @@ const anchorPresetOptions = [ { value: "stretch", label: "Stretch Horizontal" }, ]; -/** - * Handle quick action chip click — injects the chip's template prompt. - * - * @param action - The quick action that was clicked. - * - * @example - * ```typescript - * handleQuickActionClick(UI_QUICK_ACTIONS[0]); - * // prompt.value is now the health bar template - * ``` - */ function handleQuickActionClick(action: UIQuickAction): void { if (!action?.prompt) return; prompt.value = action.prompt; @@ -214,47 +203,4 @@ function handleQuickActionClick(action: UIQuickAction): void { - + diff --git a/frontend/src/components/generic/ModelManagerModal/ModelManagerModal.css b/frontend/src/components/generic/ModelManagerModal/ModelManagerModal.css new file mode 100644 index 00000000..ef64bc38 --- /dev/null +++ b/frontend/src/components/generic/ModelManagerModal/ModelManagerModal.css @@ -0,0 +1,105 @@ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + backdrop-filter: blur(4px); +} + +.modal-content { + background: #1e1e1e; + color: white; + padding: 2rem; + border-radius: 12px; + width: 500px; + max-width: 90%; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); +} + +header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +.close-btn { + background: transparent; + border: none; + font-size: 1.5rem; + color: #888; + cursor: pointer; +} + +.model-list { + margin-bottom: 2rem; +} + +ul { + list-style: none; + padding: 0; +} + +li { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid #333; +} + +.add-model h4 { + margin-bottom: 1rem; +} + +.field-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +input { + background: #2d2d2d; + border: 1px solid #444; + color: white; + padding: 0.6rem; + border-radius: 4px; +} + +.danger-btn { + background: #ff4444; + color: white; + border: none; + padding: 0.3rem 0.6rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.8rem; +} + +.primary { + background: #007bff; + color: white; + border: none; + padding: 0.7rem; + border-radius: 4px; + cursor: pointer; + margin-top: 0.5rem; +} + +.primary:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.error { + color: #ff4444; + margin-bottom: 1rem; +} diff --git a/frontend/src/components/generic/ModelManagerModal/ModelManagerModal.vue b/frontend/src/components/generic/ModelManagerModal/ModelManagerModal.vue index 8f78a96b..51e9ea85 100644 --- a/frontend/src/components/generic/ModelManagerModal/ModelManagerModal.vue +++ b/frontend/src/components/generic/ModelManagerModal/ModelManagerModal.vue @@ -1,10 +1,6 @@