aegis-stack 0.2.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aegis/__init__.py +5 -0
- aegis/__main__.py +51 -0
- aegis/cli/__init__.py +6 -0
- aegis/cli/callbacks.py +114 -0
- aegis/cli/interactive.py +611 -0
- aegis/cli/utils.py +70 -0
- aegis/cli/validators.py +34 -0
- aegis/commands/__init__.py +6 -0
- aegis/commands/add.py +353 -0
- aegis/commands/add_service.py +332 -0
- aegis/commands/components.py +35 -0
- aegis/commands/init.py +370 -0
- aegis/commands/remove.py +227 -0
- aegis/commands/services.py +52 -0
- aegis/commands/update.py +252 -0
- aegis/commands/version.py +12 -0
- aegis/config/__init__.py +1 -0
- aegis/config/shared_files.py +136 -0
- aegis/core/CLAUDE.md +377 -0
- aegis/core/__init__.py +6 -0
- aegis/core/component_files.py +228 -0
- aegis/core/component_utils.py +220 -0
- aegis/core/components.py +127 -0
- aegis/core/copier_manager.py +315 -0
- aegis/core/copier_updater.py +475 -0
- aegis/core/dependency_resolver.py +119 -0
- aegis/core/manual_updater.py +554 -0
- aegis/core/post_gen_tasks.py +547 -0
- aegis/core/service_resolver.py +261 -0
- aegis/core/services.py +157 -0
- aegis/core/template_generator.py +266 -0
- aegis/core/version_compatibility.py +259 -0
- aegis/templates/CLAUDE.md +591 -0
- aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
- aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai.py.j2 +700 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +418 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py.j2 +156 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +97 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +55 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/backup.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +93 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_auth_endpoints.py.j2 +307 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
- aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
- aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
- aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
- aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
- aegis_stack-0.2.0rc2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI service API router.
|
|
3
|
+
|
|
4
|
+
FastAPI router for AI chat endpoints implementing core chat functionality,
|
|
5
|
+
conversation management, and service status.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from collections.abc import AsyncIterator
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from app.core.config import settings
|
|
13
|
+
from app.services.ai.service import (
|
|
14
|
+
AIService,
|
|
15
|
+
AIServiceError,
|
|
16
|
+
ConversationError,
|
|
17
|
+
ProviderError,
|
|
18
|
+
)
|
|
19
|
+
from fastapi import APIRouter, HTTPException
|
|
20
|
+
from fastapi.responses import StreamingResponse
|
|
21
|
+
from pydantic import BaseModel
|
|
22
|
+
|
|
23
|
+
router = APIRouter(prefix="/ai", tags=["ai"])
|
|
24
|
+
|
|
25
|
+
# Initialize AI service
|
|
26
|
+
ai_service = AIService(settings)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Request/Response models
|
|
30
|
+
class ChatRequest(BaseModel):
|
|
31
|
+
"""Request model for chat messages."""
|
|
32
|
+
|
|
33
|
+
message: str
|
|
34
|
+
conversation_id: str | None = None
|
|
35
|
+
user_id: str = "api-user"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ChatResponse(BaseModel):
|
|
39
|
+
"""Response model for chat messages."""
|
|
40
|
+
|
|
41
|
+
message_id: str
|
|
42
|
+
content: str
|
|
43
|
+
conversation_id: str
|
|
44
|
+
response_time_ms: float | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ConversationSummary(BaseModel):
|
|
48
|
+
"""Summary model for conversation listing."""
|
|
49
|
+
|
|
50
|
+
id: str
|
|
51
|
+
title: str | None
|
|
52
|
+
message_count: int
|
|
53
|
+
last_activity: str
|
|
54
|
+
provider: str
|
|
55
|
+
model: str
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@router.post("/chat", response_model=ChatResponse)
|
|
59
|
+
async def chat(request: ChatRequest) -> ChatResponse:
|
|
60
|
+
"""
|
|
61
|
+
Send a chat message and get AI response.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
request: Chat request with message and optional conversation ID
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
ChatResponse: AI response with conversation details
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
HTTPException: If chat processing fails
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
response_message = await ai_service.chat(
|
|
74
|
+
message=request.message,
|
|
75
|
+
conversation_id=request.conversation_id,
|
|
76
|
+
user_id=request.user_id,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Get updated conversation for metadata
|
|
80
|
+
conversation_id = response_message.metadata.get("conversation_id")
|
|
81
|
+
conversation = (
|
|
82
|
+
ai_service.get_conversation(conversation_id) if conversation_id else None
|
|
83
|
+
)
|
|
84
|
+
response_time = None
|
|
85
|
+
if conversation and "last_response_time_ms" in conversation.metadata:
|
|
86
|
+
response_time = conversation.metadata["last_response_time_ms"]
|
|
87
|
+
|
|
88
|
+
return ChatResponse(
|
|
89
|
+
message_id=response_message.id,
|
|
90
|
+
content=response_message.content,
|
|
91
|
+
conversation_id=conversation.id if conversation else "unknown",
|
|
92
|
+
response_time_ms=response_time,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
except AIServiceError as e:
|
|
96
|
+
raise HTTPException(status_code=503, detail=f"AI service error: {e}")
|
|
97
|
+
except ProviderError as e:
|
|
98
|
+
raise HTTPException(status_code=502, detail=f"AI provider error: {e}")
|
|
99
|
+
except ConversationError as e:
|
|
100
|
+
raise HTTPException(status_code=400, detail=f"Conversation error: {e}")
|
|
101
|
+
except Exception as e:
|
|
102
|
+
raise HTTPException(status_code=500, detail=f"Unexpected error: {e}")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@router.post("/chat/stream")
|
|
106
|
+
async def chat_stream(request: ChatRequest) -> StreamingResponse:
|
|
107
|
+
"""
|
|
108
|
+
Stream a chat message with real-time Server-Sent Events.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
request: Chat request with message and optional conversation ID
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
StreamingResponse: SSE stream with real-time AI response
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
HTTPException: If streaming fails
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
async def generate_sse_stream() -> AsyncIterator[str]:
|
|
121
|
+
"""Generate Server-Sent Events stream for chat response."""
|
|
122
|
+
try:
|
|
123
|
+
# Send initial connection event
|
|
124
|
+
connect_data = {"status": "connected", "message": "Streaming started"}
|
|
125
|
+
yield f"event: connect\ndata: {json.dumps(connect_data)}\n\n"
|
|
126
|
+
|
|
127
|
+
# Stream the AI response
|
|
128
|
+
async for chunk in ai_service.stream_chat(
|
|
129
|
+
message=request.message,
|
|
130
|
+
conversation_id=request.conversation_id,
|
|
131
|
+
user_id=request.user_id,
|
|
132
|
+
stream_delta=True,
|
|
133
|
+
):
|
|
134
|
+
# Format chunk as SSE event
|
|
135
|
+
event_data = {
|
|
136
|
+
"content": chunk.content,
|
|
137
|
+
"is_final": chunk.is_final,
|
|
138
|
+
"is_delta": chunk.is_delta,
|
|
139
|
+
"message_id": chunk.message_id,
|
|
140
|
+
"conversation_id": chunk.conversation_id,
|
|
141
|
+
"timestamp": chunk.timestamp.isoformat(),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Add metadata for final chunk
|
|
145
|
+
if chunk.is_final:
|
|
146
|
+
event_data.update(chunk.metadata)
|
|
147
|
+
|
|
148
|
+
# Send chunk as SSE event
|
|
149
|
+
event_type = "final" if chunk.is_final else "chunk"
|
|
150
|
+
yield f"event: {event_type}\ndata: {json.dumps(event_data)}\n\n"
|
|
151
|
+
|
|
152
|
+
# Break after final chunk
|
|
153
|
+
if chunk.is_final:
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
# Send stream complete event
|
|
157
|
+
complete_data = {"status": "completed", "message": "Stream finished"}
|
|
158
|
+
yield f"event: complete\ndata: {json.dumps(complete_data)}\n\n"
|
|
159
|
+
|
|
160
|
+
except AIServiceError as e:
|
|
161
|
+
error_data = {"error": "AI service error", "detail": str(e)}
|
|
162
|
+
yield f"event: error\ndata: {json.dumps(error_data)}\n\n"
|
|
163
|
+
except ProviderError as e:
|
|
164
|
+
error_data = {"error": "AI provider error", "detail": str(e)}
|
|
165
|
+
yield f"event: error\ndata: {json.dumps(error_data)}\n\n"
|
|
166
|
+
except ConversationError as e:
|
|
167
|
+
error_data = {"error": "Conversation error", "detail": str(e)}
|
|
168
|
+
yield f"event: error\ndata: {json.dumps(error_data)}\n\n"
|
|
169
|
+
except Exception as e:
|
|
170
|
+
error_data = {"error": "Unexpected error", "detail": str(e)}
|
|
171
|
+
yield f"event: error\ndata: {json.dumps(error_data)}\n\n"
|
|
172
|
+
|
|
173
|
+
# Create streaming response with proper SSE headers
|
|
174
|
+
return StreamingResponse(
|
|
175
|
+
generate_sse_stream(),
|
|
176
|
+
media_type="text/event-stream",
|
|
177
|
+
headers={
|
|
178
|
+
"Cache-Control": "no-cache",
|
|
179
|
+
"Connection": "keep-alive",
|
|
180
|
+
"Access-Control-Allow-Origin": "*",
|
|
181
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
182
|
+
},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@router.get("/conversations", response_model=list[ConversationSummary])
|
|
187
|
+
async def list_conversations(
|
|
188
|
+
user_id: str = "api-user", limit: int = 50
|
|
189
|
+
) -> list[ConversationSummary]:
|
|
190
|
+
"""
|
|
191
|
+
List conversations for a user.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
user_id: User identifier
|
|
195
|
+
limit: Maximum number of conversations to return
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
List of conversation summaries
|
|
199
|
+
"""
|
|
200
|
+
try:
|
|
201
|
+
conversations = ai_service.list_conversations(user_id)[:limit]
|
|
202
|
+
|
|
203
|
+
return [
|
|
204
|
+
ConversationSummary(
|
|
205
|
+
id=conv.id,
|
|
206
|
+
title=conv.title,
|
|
207
|
+
message_count=conv.get_message_count(),
|
|
208
|
+
last_activity=conv.updated_at.isoformat(),
|
|
209
|
+
provider=conv.provider.value,
|
|
210
|
+
model=conv.model,
|
|
211
|
+
)
|
|
212
|
+
for conv in conversations
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
except Exception as e:
|
|
216
|
+
raise HTTPException(
|
|
217
|
+
status_code=500, detail=f"Failed to list conversations: {e}"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@router.get("/conversations/{conversation_id}")
|
|
222
|
+
async def get_conversation(
|
|
223
|
+
conversation_id: str, user_id: str = "api-user"
|
|
224
|
+
) -> dict[str, Any]:
|
|
225
|
+
"""
|
|
226
|
+
Get a specific conversation with full message history.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
conversation_id: The conversation identifier
|
|
230
|
+
user_id: User identifier for access control
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Full conversation details with messages
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
HTTPException: If conversation not found or access denied
|
|
237
|
+
"""
|
|
238
|
+
try:
|
|
239
|
+
conversation = ai_service.get_conversation(conversation_id)
|
|
240
|
+
|
|
241
|
+
if not conversation:
|
|
242
|
+
raise HTTPException(status_code=404, detail="Conversation not found")
|
|
243
|
+
|
|
244
|
+
# Check access (basic user matching)
|
|
245
|
+
if conversation.metadata.get("user_id") != user_id:
|
|
246
|
+
raise HTTPException(status_code=403, detail="Access denied")
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
"id": conversation.id,
|
|
250
|
+
"title": conversation.title,
|
|
251
|
+
"provider": conversation.provider.value,
|
|
252
|
+
"model": conversation.model,
|
|
253
|
+
"created_at": conversation.created_at.isoformat(),
|
|
254
|
+
"updated_at": conversation.updated_at.isoformat(),
|
|
255
|
+
"message_count": conversation.get_message_count(),
|
|
256
|
+
"messages": [
|
|
257
|
+
{
|
|
258
|
+
"id": msg.id,
|
|
259
|
+
"role": msg.role.value,
|
|
260
|
+
"content": msg.content,
|
|
261
|
+
"timestamp": msg.timestamp.isoformat(),
|
|
262
|
+
}
|
|
263
|
+
for msg in conversation.messages
|
|
264
|
+
],
|
|
265
|
+
"metadata": conversation.metadata,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
except HTTPException:
|
|
269
|
+
raise
|
|
270
|
+
except Exception as e:
|
|
271
|
+
raise HTTPException(status_code=500, detail=f"Failed to get conversation: {e}")
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@router.get("/health")
|
|
275
|
+
async def ai_health() -> dict[str, Any]:
|
|
276
|
+
"""
|
|
277
|
+
AI service health endpoint.
|
|
278
|
+
|
|
279
|
+
Returns comprehensive health status including configuration,
|
|
280
|
+
conversation count, and service availability.
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
status = ai_service.get_service_status()
|
|
284
|
+
validation_errors = ai_service.validate_service()
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
"service": "ai",
|
|
288
|
+
"status": "healthy" if not validation_errors else "unhealthy",
|
|
289
|
+
"enabled": status["enabled"],
|
|
290
|
+
"provider": status["provider"],
|
|
291
|
+
"model": status["model"],
|
|
292
|
+
"agent_ready": status["agent_initialized"],
|
|
293
|
+
"total_conversations": status["total_conversations"],
|
|
294
|
+
"configuration_valid": status["configuration_valid"],
|
|
295
|
+
"validation_errors": validation_errors,
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
except Exception as e:
|
|
299
|
+
return {
|
|
300
|
+
"service": "ai",
|
|
301
|
+
"status": "error",
|
|
302
|
+
"error": str(e),
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@router.get("/version")
|
|
307
|
+
async def ai_version() -> dict[str, Any]:
|
|
308
|
+
"""AI service version and feature information."""
|
|
309
|
+
return {
|
|
310
|
+
"service": "ai",
|
|
311
|
+
"engine": "pydantic-ai",
|
|
312
|
+
"version": "1.0",
|
|
313
|
+
"features": [
|
|
314
|
+
"chat",
|
|
315
|
+
"conversation_management",
|
|
316
|
+
"multi_provider_support",
|
|
317
|
+
"health_monitoring",
|
|
318
|
+
"api_endpoints",
|
|
319
|
+
"cli_commands",
|
|
320
|
+
],
|
|
321
|
+
"providers_supported": [
|
|
322
|
+
"openai",
|
|
323
|
+
"anthropic",
|
|
324
|
+
"google",
|
|
325
|
+
"groq",
|
|
326
|
+
"mistral",
|
|
327
|
+
"cohere",
|
|
328
|
+
],
|
|
329
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Auth API endpoints."""
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Authentication API routes."""
|
|
2
|
+
|
|
3
|
+
from app.components.backend.api.deps import get_async_db
|
|
4
|
+
from app.core.security import create_access_token, verify_password
|
|
5
|
+
from app.models.user import UserCreate, UserResponse
|
|
6
|
+
from app.services.auth.auth_service import get_current_user_from_token
|
|
7
|
+
from app.services.auth.user_service import UserService
|
|
8
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
9
|
+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
|
10
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
11
|
+
|
|
12
|
+
router = APIRouter(prefix="/auth", tags=["authentication"])
|
|
13
|
+
|
|
14
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.post("/register", response_model=UserResponse)
|
|
18
|
+
async def register(user_data: UserCreate, db: AsyncSession = Depends(get_async_db)):
|
|
19
|
+
"""Register a new user."""
|
|
20
|
+
user_service = UserService(db)
|
|
21
|
+
|
|
22
|
+
# Check if user already exists
|
|
23
|
+
existing_user = await user_service.get_user_by_email(user_data.email)
|
|
24
|
+
if existing_user:
|
|
25
|
+
raise HTTPException(
|
|
26
|
+
status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Create new user
|
|
30
|
+
user = await user_service.create_user(user_data)
|
|
31
|
+
return UserResponse.model_validate(user)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router.post("/token")
|
|
35
|
+
async def login(
|
|
36
|
+
form_data: OAuth2PasswordRequestForm = Depends(),
|
|
37
|
+
db: AsyncSession = Depends(get_async_db),
|
|
38
|
+
):
|
|
39
|
+
"""Login and get access token."""
|
|
40
|
+
user_service = UserService(db)
|
|
41
|
+
|
|
42
|
+
# Get user by email (username field in OAuth2 form)
|
|
43
|
+
user = await user_service.get_user_by_email(form_data.username)
|
|
44
|
+
|
|
45
|
+
if not user or not verify_password(form_data.password, user.hashed_password):
|
|
46
|
+
raise HTTPException(
|
|
47
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
48
|
+
detail="Incorrect email or password",
|
|
49
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Create access token
|
|
53
|
+
access_token = create_access_token(data={"sub": user.email})
|
|
54
|
+
return {"access_token": access_token, "token_type": "bearer"}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@router.get("/me", response_model=UserResponse)
|
|
58
|
+
async def get_current_user(
|
|
59
|
+
token: str = Depends(oauth2_scheme),
|
|
60
|
+
db: AsyncSession = Depends(get_async_db),
|
|
61
|
+
):
|
|
62
|
+
"""Get current authenticated user."""
|
|
63
|
+
user = await get_current_user_from_token(token, db)
|
|
64
|
+
return UserResponse.model_validate(user)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""FastAPI dependencies for the backend API."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncGenerator, Generator
|
|
4
|
+
|
|
5
|
+
from app.core.db import AsyncSessionLocal, SessionLocal
|
|
6
|
+
from sqlmodel import Session
|
|
7
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_db() -> Generator[Session, None, None]:
|
|
11
|
+
"""
|
|
12
|
+
Database dependency that provides a database session.
|
|
13
|
+
|
|
14
|
+
This dependency is used in FastAPI route functions to get access to
|
|
15
|
+
the database. It automatically handles session lifecycle - creating,
|
|
16
|
+
yielding, and closing the session properly.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
@router.get("/example")
|
|
20
|
+
def example_endpoint(db: Session = Depends(get_db)):
|
|
21
|
+
# Use db for database operations
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
Yields:
|
|
25
|
+
Session: SQLModel database session
|
|
26
|
+
"""
|
|
27
|
+
db = SessionLocal()
|
|
28
|
+
try:
|
|
29
|
+
yield db
|
|
30
|
+
finally:
|
|
31
|
+
db.close()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def get_async_db() -> AsyncGenerator[AsyncSession, None]:
|
|
35
|
+
"""
|
|
36
|
+
Async database dependency that provides an async database session.
|
|
37
|
+
|
|
38
|
+
This dependency is used in async FastAPI route functions to get access to
|
|
39
|
+
the database with non-blocking I/O operations. It automatically handles
|
|
40
|
+
session lifecycle - creating, yielding, committing and closing the session properly.
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
@router.get("/example")
|
|
44
|
+
async def example_endpoint(db: AsyncSession = Depends(get_async_db)):
|
|
45
|
+
# Use db for async database operations with await
|
|
46
|
+
result = await db.exec(select(MyModel))
|
|
47
|
+
return result.first()
|
|
48
|
+
|
|
49
|
+
Yields:
|
|
50
|
+
AsyncSession: SQLModel async database session
|
|
51
|
+
"""
|
|
52
|
+
async with AsyncSessionLocal() as session:
|
|
53
|
+
try:
|
|
54
|
+
yield session
|
|
55
|
+
await session.commit()
|
|
56
|
+
except Exception:
|
|
57
|
+
await session.rollback()
|
|
58
|
+
raise
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from app.services.system import (
|
|
4
|
+
DetailedHealthResponse,
|
|
5
|
+
HealthResponse,
|
|
6
|
+
get_system_status,
|
|
7
|
+
)
|
|
8
|
+
from fastapi import APIRouter, HTTPException
|
|
9
|
+
from starlette import status
|
|
10
|
+
|
|
11
|
+
router = APIRouter()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.get("/", response_model=HealthResponse)
|
|
15
|
+
async def health_check() -> HealthResponse:
|
|
16
|
+
"""
|
|
17
|
+
Quick health check endpoint.
|
|
18
|
+
|
|
19
|
+
Returns basic healthy/unhealthy status for load balancers and monitoring.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
system_status = await get_system_status()
|
|
23
|
+
return HealthResponse(
|
|
24
|
+
healthy=system_status.overall_healthy,
|
|
25
|
+
status="healthy" if system_status.overall_healthy else "unhealthy",
|
|
26
|
+
components=system_status.components,
|
|
27
|
+
timestamp=system_status.timestamp.isoformat(),
|
|
28
|
+
)
|
|
29
|
+
except Exception:
|
|
30
|
+
# If health checks fail completely, consider unhealthy
|
|
31
|
+
return HealthResponse(
|
|
32
|
+
healthy=False,
|
|
33
|
+
status="unhealthy",
|
|
34
|
+
components={},
|
|
35
|
+
timestamp="",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.get("/detailed", response_model=DetailedHealthResponse)
|
|
40
|
+
async def detailed_health() -> DetailedHealthResponse:
|
|
41
|
+
"""
|
|
42
|
+
Detailed health check with component information.
|
|
43
|
+
|
|
44
|
+
Returns comprehensive system status including individual component health,
|
|
45
|
+
system metrics, and diagnostic information.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
system_status = await get_system_status()
|
|
49
|
+
|
|
50
|
+
# Always return 200 OK - service is available even if components are unhealthy
|
|
51
|
+
return DetailedHealthResponse(
|
|
52
|
+
healthy=system_status.overall_healthy,
|
|
53
|
+
status="healthy" if system_status.overall_healthy else "unhealthy",
|
|
54
|
+
service="{{ cookiecutter.project_name }}",
|
|
55
|
+
version="0.1.0",
|
|
56
|
+
components=system_status.components,
|
|
57
|
+
system_info=system_status.system_info,
|
|
58
|
+
timestamp=system_status.timestamp.isoformat(),
|
|
59
|
+
healthy_components=system_status.healthy_components,
|
|
60
|
+
unhealthy_components=system_status.unhealthy_components,
|
|
61
|
+
health_percentage=system_status.health_percentage,
|
|
62
|
+
# Service-specific information
|
|
63
|
+
has_services=system_status.has_services,
|
|
64
|
+
service_names=system_status.service_names,
|
|
65
|
+
healthy_services=system_status.healthy_services,
|
|
66
|
+
unhealthy_services=system_status.unhealthy_services,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
except HTTPException:
|
|
70
|
+
# Re-raise HTTP exceptions from unexpected errors
|
|
71
|
+
raise
|
|
72
|
+
except Exception as e:
|
|
73
|
+
# Handle unexpected errors
|
|
74
|
+
raise HTTPException(
|
|
75
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
76
|
+
detail={
|
|
77
|
+
"message": "Health check failed",
|
|
78
|
+
"error": str(e),
|
|
79
|
+
"status": "unhealthy",
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@router.get("/dashboard")
|
|
85
|
+
async def system_dashboard() -> dict[str, Any]:
|
|
86
|
+
"""
|
|
87
|
+
System dashboard endpoint optimized for frontend consumption.
|
|
88
|
+
|
|
89
|
+
Returns system status with additional dashboard metadata like
|
|
90
|
+
alert counts, trend data, and formatted display information.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
system_status = await get_system_status()
|
|
94
|
+
|
|
95
|
+
# TODO: Implement alert tracking when alert management is enhanced
|
|
96
|
+
recent_alerts = 0
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
"status": "healthy" if system_status.overall_healthy else "unhealthy",
|
|
100
|
+
"service": "{{ cookiecutter.project_name }}",
|
|
101
|
+
"version": "0.1.0",
|
|
102
|
+
"dashboard_data": {
|
|
103
|
+
"overall_status": {
|
|
104
|
+
"healthy": system_status.overall_healthy,
|
|
105
|
+
"percentage": system_status.health_percentage,
|
|
106
|
+
"status_text": (
|
|
107
|
+
"System Healthy"
|
|
108
|
+
if system_status.overall_healthy
|
|
109
|
+
else "Issues Detected"
|
|
110
|
+
),
|
|
111
|
+
},
|
|
112
|
+
"components": {
|
|
113
|
+
name: {
|
|
114
|
+
"name": name,
|
|
115
|
+
"healthy": component.healthy,
|
|
116
|
+
"message": component.message,
|
|
117
|
+
"response_time_ms": component.response_time_ms,
|
|
118
|
+
"metadata": component.metadata,
|
|
119
|
+
}
|
|
120
|
+
for name, component in system_status.components.items()
|
|
121
|
+
},
|
|
122
|
+
"summary": {
|
|
123
|
+
"total_components": len(system_status.components),
|
|
124
|
+
"healthy_components": len(system_status.healthy_components),
|
|
125
|
+
"unhealthy_components": len(system_status.unhealthy_components),
|
|
126
|
+
"recent_alerts": recent_alerts,
|
|
127
|
+
# Service summary information
|
|
128
|
+
"has_services": system_status.has_services,
|
|
129
|
+
"total_services": len(system_status.service_names),
|
|
130
|
+
"healthy_services": len(system_status.healthy_services),
|
|
131
|
+
"unhealthy_services": len(system_status.unhealthy_services),
|
|
132
|
+
},
|
|
133
|
+
# Services section for frontend consumption
|
|
134
|
+
"services": {
|
|
135
|
+
"enabled": system_status.has_services,
|
|
136
|
+
"services": {
|
|
137
|
+
name: {
|
|
138
|
+
"name": name,
|
|
139
|
+
"healthy": service.healthy,
|
|
140
|
+
"message": service.message,
|
|
141
|
+
"response_time_ms": service.response_time_ms,
|
|
142
|
+
"metadata": service.metadata,
|
|
143
|
+
}
|
|
144
|
+
for name, service in (
|
|
145
|
+
system_status.services_status.sub_components.items()
|
|
146
|
+
if system_status.services_status
|
|
147
|
+
else {}
|
|
148
|
+
).items()
|
|
149
|
+
}
|
|
150
|
+
if system_status.has_services
|
|
151
|
+
else {},
|
|
152
|
+
},
|
|
153
|
+
"system_info": system_status.system_info,
|
|
154
|
+
"timestamp": system_status.timestamp.isoformat(),
|
|
155
|
+
"last_updated": system_status.timestamp.strftime("%H:%M:%S"),
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
raise HTTPException(
|
|
161
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
162
|
+
detail={"message": "Dashboard data unavailable", "error": str(e)},
|
|
163
|
+
)
|