From afca80f32ac1ad1ae4dfbc373d8c714108f349e0 Mon Sep 17 00:00:00 2001 From: guenhter Date: Wed, 11 Feb 2026 19:25:50 +0100 Subject: [PATCH 1/3] chore: more backend formatting --- .github/workflows/ci.yml | 62 +++++++++++++------ .../src/modai/modules/chat/web_chat_router.py | 2 +- .../modules/model_provider/central_router.py | 1 - backend/src/modai/modules/session/module.py | 2 +- .../simple_user_settings_module.py | 2 +- .../abstract_model_provider_store_test.py | 2 +- backend/tests/test_authentication.py | 4 +- .../test_central_model_provider_router.py | 2 - backend/tests/test_chat.py | 13 ++-- backend/tests/test_config_loader.py | 2 +- backend/tests/test_model_provider.py | 3 +- backend/tests/test_user_settings.py | 17 +++-- backend/tests/test_user_settings_store.py | 16 ++--- 13 files changed, 72 insertions(+), 56 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18f4a9f..b87ec39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,19 +8,25 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.13" + - name: Install uv - run: curl -LsSf https://astral.sh/uv/install.sh | sh - - name: Sync dependencies + uses: astral-sh/setup-uv@v7 + + - name: Set up Python + run: uv python install 3.13 + + - name: Install dependencies working-directory: backend - run: uv sync + run: uv sync --all-extras + - name: Check formatting working-directory: backend run: uv run ruff format --check . + - name: Lint + working-directory: backend + run: uv run ruff check . + frontend-format: runs-on: ubuntu-latest strategy: @@ -31,15 +37,19 @@ jobs: - e2e_tests/tests_omni_light steps: - uses: actions/checkout@v6 + - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: "20" + - name: Install pnpm run: npm install -g pnpm + - name: Install dependencies working-directory: ${{ matrix.working-dir }} run: pnpm install + - name: Check formatting and linting working-directory: ${{ matrix.working-dir }} run: pnpm check @@ -48,32 +58,38 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.13" + - name: Install uv - run: curl -LsSf https://astral.sh/uv/install.sh | sh - - name: Sync dependencies + uses: astral-sh/setup-uv@v7 + + - name: Set up Python + run: uv python install 3.13 + + - name: Install dependencies working-directory: backend - run: uv sync + run: uv sync --all-extras + - name: Run tests working-directory: backend - run: uv run pytest + run: uv run pytest -v frontend-unit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: "20" + - name: Install pnpm run: npm install -g pnpm + - name: Install dependencies working-directory: frontend_omni run: pnpm install + - name: Run unit tests working-directory: frontend_omni run: pnpm test @@ -87,27 +103,33 @@ jobs: - e2e_tests/tests_omni_light steps: - uses: actions/checkout@v6 - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.13" + - name: Install uv - run: curl -LsSf https://astral.sh/uv/install.sh | sh + uses: astral-sh/setup-uv@v7 + + - name: Set up Python + run: uv python install 3.13 + - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: "20" + - name: Install pnpm run: npm install -g pnpm + - name: Install dependencies for frontend working-directory: frontend_omni run: pnpm install + - name: Install dependencies for tests working-directory: ${{ matrix.working-dir }} run: pnpm install + - name: Install Playwright browsers working-directory: ${{ matrix.working-dir }} run: pnpm playwright install --with-deps + - name: Run Playwright tests working-directory: ${{ matrix.working-dir }} run: pnpm test diff --git a/backend/src/modai/modules/chat/web_chat_router.py b/backend/src/modai/modules/chat/web_chat_router.py index c24e4f5..b1d6611 100644 --- a/backend/src/modai/modules/chat/web_chat_router.py +++ b/backend/src/modai/modules/chat/web_chat_router.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Request, Body +from fastapi import Request, Body from fastapi.responses import StreamingResponse, JSONResponse from typing import Any, Dict, cast from modai.module import ModuleDependencies diff --git a/backend/src/modai/modules/model_provider/central_router.py b/backend/src/modai/modules/model_provider/central_router.py index c2b090c..c0d0874 100644 --- a/backend/src/modai/modules/model_provider/central_router.py +++ b/backend/src/modai/modules/model_provider/central_router.py @@ -11,7 +11,6 @@ from modai.modules.model_provider.module import ( ModelProviderResponse, ModelProviderModule, - ModelResponse, Model, ) diff --git a/backend/src/modai/modules/session/module.py b/backend/src/modai/modules/session/module.py index 0e7d092..a1233a4 100644 --- a/backend/src/modai/modules/session/module.py +++ b/backend/src/modai/modules/session/module.py @@ -8,7 +8,7 @@ import logging from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Any, Dict +from typing import Any from fastapi import Request, Response from modai.module import ModaiModule, ModuleDependencies diff --git a/backend/src/modai/modules/user_settings/simple_user_settings_module.py b/backend/src/modai/modules/user_settings/simple_user_settings_module.py index cb4eb3c..63c9194 100644 --- a/backend/src/modai/modules/user_settings/simple_user_settings_module.py +++ b/backend/src/modai/modules/user_settings/simple_user_settings_module.py @@ -3,7 +3,7 @@ This implementation delegates persistence to a UserSettingsStore module. """ -from typing import Any, Dict +from typing import Any from fastapi import Request, HTTPException from modai.module import ModuleDependencies diff --git a/backend/tests/abstract_model_provider_store_test.py b/backend/tests/abstract_model_provider_store_test.py index ef7128a..17dbd47 100644 --- a/backend/tests/abstract_model_provider_store_test.py +++ b/backend/tests/abstract_model_provider_store_test.py @@ -199,7 +199,7 @@ async def test_update_provider_with_duplicate_name_raises_exception( self, model_provider_store ): """Test that updating a provider to use an existing name raises an exception""" - provider1 = await model_provider_store.add_provider( + await model_provider_store.add_provider( name="Provider1", url="https://api1.com", properties={} ) provider2 = await model_provider_store.add_provider( diff --git a/backend/tests/test_authentication.py b/backend/tests/test_authentication.py index a35f496..2a0fb20 100644 --- a/backend/tests/test_authentication.py +++ b/backend/tests/test_authentication.py @@ -3,14 +3,14 @@ import pytest from unittest.mock import Mock, MagicMock, AsyncMock from fastapi.testclient import TestClient -from fastapi import FastAPI, Request, Response +from fastapi import FastAPI sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) from modai.module import ModuleDependencies from modai.modules.authentication.password_authentication_module import ( PasswordAuthenticationModule, ) -from modai.modules.session.module import SessionModule, Session +from modai.modules.session.module import SessionModule from modai.modules.user_store.module import UserStore, User, UserCredentials diff --git a/backend/tests/test_central_model_provider_router.py b/backend/tests/test_central_model_provider_router.py index b0eabe4..ef640af 100644 --- a/backend/tests/test_central_model_provider_router.py +++ b/backend/tests/test_central_model_provider_router.py @@ -5,8 +5,6 @@ import sys import os import pytest -from pathlib import Path -from unittest.mock import AsyncMock, MagicMock from fastapi.testclient import TestClient from fastapi import FastAPI diff --git a/backend/tests/test_chat.py b/backend/tests/test_chat.py index 5ad2123..d065c75 100644 --- a/backend/tests/test_chat.py +++ b/backend/tests/test_chat.py @@ -6,7 +6,6 @@ import pytest_asyncio from openai import AsyncOpenAI from unittest.mock import Mock, AsyncMock -from typing import AsyncGenerator sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) from modai.module import ModuleDependencies @@ -70,7 +69,7 @@ async def openai_client(request): async def test_llm_generate_response(): """Test LLM generate_response method directly.""" from fastapi import Request - from unittest.mock import Mock, AsyncMock + from unittest.mock import Mock from modai.modules.model_provider.module import ( ModelProvidersListResponse, ModelProviderResponse, @@ -139,7 +138,7 @@ async def test_llm_generate_response(): async def test_llm_generate_response_streaming(): """Test LLM generate_response method directly for streaming.""" from fastapi import Request - from unittest.mock import Mock, AsyncMock + from unittest.mock import Mock from modai.modules.model_provider.module import ( ModelProvidersListResponse, ModelProviderResponse, @@ -210,7 +209,7 @@ async def test_llm_generate_response_streaming(): async def test_chat_responses_api(openai_client: AsyncOpenAI, request): """Test chat responses API.""" - client_type = request.node.callspec.params["openai_client"] + request.node.callspec.params["openai_client"] model = "gpt-4o" # No backend_proxy # Make the request @@ -242,7 +241,7 @@ async def test_chat_responses_api(openai_client: AsyncOpenAI, request): async def test_chat_responses_api_streaming(openai_client: AsyncOpenAI, request): """Test streaming chat responses API.""" - client_type = request.node.callspec.params["openai_client"] + request.node.callspec.params["openai_client"] model = "gpt-4o" # No backend_proxy # Make the streaming request @@ -351,7 +350,7 @@ async def test_chat_web_module_routing_streaming(): async def test_openai_llm_invalid_model_format(): """Test OpenAILLMChatModule with invalid model format.""" from fastapi import Request - from unittest.mock import Mock, AsyncMock + from unittest.mock import Mock from modai.modules.model_provider.module import ( ModelProvidersListResponse, ModelProviderResponse, @@ -403,7 +402,7 @@ async def test_openai_llm_invalid_model_format(): async def test_openai_llm_provider_not_found(): """Test OpenAILLMChatModule when provider is not found.""" from fastapi import Request - from unittest.mock import Mock, AsyncMock + from unittest.mock import Mock from modai.modules.model_provider.module import ModelProvidersListResponse # Mock provider module with no providers diff --git a/backend/tests/test_config_loader.py b/backend/tests/test_config_loader.py index 46ab5f8..f108d0d 100644 --- a/backend/tests/test_config_loader.py +++ b/backend/tests/test_config_loader.py @@ -22,7 +22,7 @@ def test_config_loader_valid_config(tmp_path: Path): loader = YamlConfigModule(ModuleDependencies(), {"config_path": str(config_file)}) config = loader.get_config() - assert config["modules"]["health"]["enabled"] == True + assert config["modules"]["health"]["enabled"] def test_config_loader_invalid_yaml(tmp_path: Path): diff --git a/backend/tests/test_model_provider.py b/backend/tests/test_model_provider.py index eb162f0..095790f 100644 --- a/backend/tests/test_model_provider.py +++ b/backend/tests/test_model_provider.py @@ -7,13 +7,12 @@ import pytest from pathlib import Path from dotenv import find_dotenv, load_dotenv -from unittest.mock import AsyncMock, MagicMock +from unittest.mock import AsyncMock from fastapi.testclient import TestClient from fastapi import FastAPI sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) -from modai.modules.model_provider.module import ModelProviderModule from modai.modules.model_provider.openai_provider import OpenAIProviderModule from modai.modules.model_provider_store.module import ModelProvider, ModelProviderStore from modai.module import ModuleDependencies diff --git a/backend/tests/test_user_settings.py b/backend/tests/test_user_settings.py index 7978a60..a335f7e 100644 --- a/backend/tests/test_user_settings.py +++ b/backend/tests/test_user_settings.py @@ -4,9 +4,9 @@ """ import pytest -from unittest.mock import AsyncMock, MagicMock +from unittest.mock import MagicMock from fastapi import HTTPException, Request -from typing import Dict, Any +from typing import Any from modai.module import ModuleDependencies from modai.modules.user_settings.module import ( @@ -20,7 +20,6 @@ SimpleUserSettingsModule, ) from modai.modules.session.module import SessionModule -from modai.modules.user_settings_store.module import UserSettingsStore from modai.modules.user_settings_store.inmemory_user_settings_store import ( InMemoryUserSettingsStore, ) @@ -186,7 +185,7 @@ async def test_update_user_settings_new_user( assert isinstance(result, UserSettingsResponse) assert result.settings["theme"]["mode"] == "dark" assert result.settings["theme"]["primary_color"] == "#1976d2" - assert result.settings["notifications"]["email_enabled"] == True + assert result.settings["notifications"]["email_enabled"] @pytest.mark.asyncio async def test_update_user_settings_existing_user( @@ -219,8 +218,8 @@ async def test_update_user_settings_existing_user( assert result.settings["theme"]["mode"] == "dark" assert result.settings["theme"]["primary_color"] == "#red" # Notifications should remain unchanged - assert result.settings["notifications"]["email_enabled"] == False - assert result.settings["notifications"]["push_enabled"] == True + assert not result.settings["notifications"]["email_enabled"] + assert result.settings["notifications"]["push_enabled"] @pytest.mark.asyncio async def test_update_user_settings_unauthorized_modification( @@ -419,7 +418,7 @@ async def test_update_user_setting_type_existing_user( # Verify other settings are preserved full_settings = await module.get_user_settings(user_id, mock_request) - assert full_settings.settings["notifications"]["email_enabled"] == False + assert not full_settings.settings["notifications"]["email_enabled"] @pytest.mark.asyncio async def test_update_user_setting_type_unauthorized_modification( @@ -470,6 +469,6 @@ async def test_setting_type_flow_integration( assert isinstance(get_result, UserSettingTypeResponse) assert get_result.settings == update_result.settings - assert get_result.settings["email_enabled"] == True - assert get_result.settings["push_enabled"] == False + assert get_result.settings["email_enabled"] + assert not get_result.settings["push_enabled"] assert get_result.settings["frequency"] == "daily" diff --git a/backend/tests/test_user_settings_store.py b/backend/tests/test_user_settings_store.py index ccf2ae8..d5bf94e 100644 --- a/backend/tests/test_user_settings_store.py +++ b/backend/tests/test_user_settings_store.py @@ -81,7 +81,7 @@ async def test_update_user_settings_new_user(self, store: UserSettingsStore): assert isinstance(result, dict) assert result["theme"]["mode"] == "dark" assert result["theme"]["primary_color"] == "#1976d2" - assert result["notifications"]["email_enabled"] == True + assert result["notifications"]["email_enabled"] @pytest.mark.asyncio async def test_update_user_settings_existing_user(self, store: UserSettingsStore): @@ -104,8 +104,8 @@ async def test_update_user_settings_existing_user(self, store: UserSettingsStore assert result["theme"]["mode"] == "dark" assert result["theme"]["primary_color"] == "#red" # Notifications should remain unchanged - assert result["notifications"]["email_enabled"] == False - assert result["notifications"]["push_enabled"] == True + assert not result["notifications"]["email_enabled"] + assert result["notifications"]["push_enabled"] @pytest.mark.asyncio async def test_update_user_settings_invalid_params(self, store: UserSettingsStore): @@ -160,7 +160,7 @@ async def test_update_user_setting_by_module_existing_user( all_settings = await store.get_user_settings(user_id) assert all_settings["theme"] == setting_data # Other settings should remain unchanged - assert all_settings["notifications"]["email_enabled"] == False + assert not all_settings["notifications"]["email_enabled"] @pytest.mark.asyncio async def test_update_user_setting_by_module_invalid_params( @@ -243,7 +243,7 @@ async def test_delete_user_setting_by_module(self, store: UserSettingsStore): # Verify the setting was deleted remaining_settings = await store.get_user_settings(user_id) assert "theme" not in remaining_settings - assert remaining_settings["notifications"]["email_enabled"] == True + assert remaining_settings["notifications"]["email_enabled"] @pytest.mark.asyncio async def test_delete_last_setting_by_module(self, store: UserSettingsStore): @@ -279,7 +279,7 @@ async def test_user_has_settings(self, store: UserSettingsStore): # Initially no settings result = await store.user_has_settings(user_id) - assert result == False + assert not result # Add settings settings = {"theme": {"mode": "dark"}} @@ -287,14 +287,14 @@ async def test_user_has_settings(self, store: UserSettingsStore): # Should have settings now result = await store.user_has_settings(user_id) - assert result == True + assert result # Delete settings await store.delete_user_settings(user_id) # Should not have settings anymore result = await store.user_has_settings(user_id) - assert result == False + assert not result @pytest.mark.asyncio async def test_user_has_settings_invalid_user_id(self, store: UserSettingsStore): From 9bfaa14f63ab2a0ac985971149d8a8c84c729729 Mon Sep 17 00:00:00 2001 From: guenhter Date: Wed, 11 Feb 2026 19:41:18 +0100 Subject: [PATCH 2/3] chore: streamline default config --- backend/src/modai/default_config.yaml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/backend/src/modai/default_config.yaml b/backend/src/modai/default_config.yaml index 1ff0b9c..9e233f2 100644 --- a/backend/src/modai/default_config.yaml +++ b/backend/src/modai/default_config.yaml @@ -10,18 +10,21 @@ modules: chat_openai: chat_openai chat_openai: class: modai.modules.chat.openai_llm_chat.OpenAILLMChatModule - config: - openai_client: - api_key: ${OPENAI_API_KEY} - llm_provider_store: - class: modai.modules.llm_provider_store.sql_model_llm_provider_store.SQLAlchemyLLMProviderStore + module_dependencies: + llm_provider_module: openai_model_provider + model_provider_store: + class: modai.modules.model_provider_store.sql_model_provider_store.SQLAlchemyModelProviderStore config: database_url: "sqlite:///./llm.db" echo: false - openai_llm_provider: - class: modai.modules.llm_provider.openai_provider.OpenAIProviderModule + openai_model_provider: + class: modai.modules.model_provider.openai_provider.OpenAIProviderModule + module_dependencies: + llm_provider_store: "model_provider_store" + central_model_provider_router: + class: modai.modules.model_provider.central_router.CentralModelProviderRouter module_dependencies: - llm_provider_store: "llm_provider_store" + openai_provider: "openai_model_provider" user_store: class: modai.modules.user_store.sql_model_user_store.SQLAlchemyUserStore config: From ad9ad37101a008d1afb14741c7df3ad4429d96fa Mon Sep 17 00:00:00 2001 From: guenhter Date: Thu, 12 Feb 2026 16:59:01 +0100 Subject: [PATCH 3/3] docs: add changelog --- backend/CHANGELOG.md | 5 ++++- frontend_omni/CHANGELOG.md | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/CHANGELOG.md b/backend/CHANGELOG.md index 25ed73c..393c0f2 100644 --- a/backend/CHANGELOG.md +++ b/backend/CHANGELOG.md @@ -5,5 +5,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.0.1] - 2026-02-12 +### Added + +- ModAI inital Backend diff --git a/frontend_omni/CHANGELOG.md b/frontend_omni/CHANGELOG.md index 25ed73c..161dc01 100644 --- a/frontend_omni/CHANGELOG.md +++ b/frontend_omni/CHANGELOG.md @@ -5,5 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.0.1] - 2026-02-12 +### Added + +- Frontend backed by ModAI Backend +- Frontend without Backend