diff --git a/ruoyi-fastapi-backend/cli/groups/app/controller.py b/ruoyi-fastapi-backend/cli/groups/app/controller.py index 2e4a4e8..17f6582 100644 --- a/ruoyi-fastapi-backend/cli/groups/app/controller.py +++ b/ruoyi-fastapi-backend/cli/groups/app/controller.py @@ -1,7 +1,7 @@ from collections import defaultdict from typing import Any -from fastapi.routing import APIRoute +from fastapi.routing import APIRoute, iter_route_contexts from cli.bootstrap import APP_BOOTSTRAP, AppBootstrapService from cli.core import ( @@ -197,25 +197,26 @@ def _serialize_routes( """ normalized_method = method.upper().strip() routes = [] - for route in app_instance.routes: - if not isinstance(route, APIRoute): + for route_context in iter_route_contexts(app_instance.routes): + if not isinstance(route_context.original_route, APIRoute): continue - if not include_hidden and not route.include_in_schema: + if not include_hidden and not route_context.include_in_schema: continue - if path_prefix and not route.path.startswith(path_prefix): + route_path = route_context.path or '' + if path_prefix and not route_path.startswith(path_prefix): continue - route_methods = sorted(item for item in route.methods if item not in {'HEAD', 'OPTIONS'}) + route_methods = sorted(item for item in (route_context.methods or set()) if item not in {'HEAD', 'OPTIONS'}) if normalized_method and normalized_method not in route_methods: continue routes.append( { - 'path': route.path, + 'path': route_path, 'methods': route_methods, - 'name': route.name, - 'summary': route.summary or '', - 'operationId': route.operation_id or '', - 'tags': route.tags or [], - 'includeInSchema': route.include_in_schema, + 'name': route_context.name, + 'summary': route_context.summary or '', + 'operationId': route_context.operation_id or '', + 'tags': route_context.tags or [], + 'includeInSchema': route_context.include_in_schema, } ) return sorted(routes, key=lambda item: (item['path'], ','.join(item['methods']))) diff --git a/ruoyi-fastapi-backend/cli/runtime/app/service.py b/ruoyi-fastapi-backend/cli/runtime/app/service.py index 3f6b273..a26a03f 100644 --- a/ruoyi-fastapi-backend/cli/runtime/app/service.py +++ b/ruoyi-fastapi-backend/cli/runtime/app/service.py @@ -1,7 +1,7 @@ from collections import defaultdict from typing import Any -from fastapi.routing import APIRoute +from fastapi.routing import APIRoute, iter_route_contexts from cli.runtime.base import RUNTIME_ENVIRONMENT, RuntimeEnvironmentService @@ -89,25 +89,26 @@ def get_app_routes_snapshot( """ normalized_method = method.upper().strip() routes: list[dict[str, Any]] = [] - for route in self.build_app_instance().routes: - if not isinstance(route, APIRoute): + for route_context in iter_route_contexts(self.build_app_instance().routes): + if not isinstance(route_context.original_route, APIRoute): continue - if not include_hidden and not route.include_in_schema: + if not include_hidden and not route_context.include_in_schema: continue - if path_prefix and not route.path.startswith(path_prefix): + route_path = route_context.path or '' + if path_prefix and not route_path.startswith(path_prefix): continue - route_methods = sorted(item for item in route.methods if item not in {'HEAD', 'OPTIONS'}) + route_methods = sorted(item for item in (route_context.methods or set()) if item not in {'HEAD', 'OPTIONS'}) if normalized_method and normalized_method not in route_methods: continue routes.append( { - 'path': route.path, + 'path': route_path, 'methods': route_methods, - 'name': route.name, - 'summary': route.summary or '', - 'operationId': route.operation_id or '', - 'tags': route.tags or [], - 'includeInSchema': route.include_in_schema, + 'name': route_context.name, + 'summary': route_context.summary or '', + 'operationId': route_context.operation_id or '', + 'tags': route_context.tags or [], + 'includeInSchema': route_context.include_in_schema, } ) routes.sort(key=lambda item: (item['path'], ','.join(item['methods']))) diff --git a/ruoyi-fastapi-backend/plugins/ai/plugin.yaml b/ruoyi-fastapi-backend/plugins/ai/plugin.yaml index 281eb31..4e1da1c 100644 --- a/ruoyi-fastapi-backend/plugins/ai/plugin.yaml +++ b/ruoyi-fastapi-backend/plugins/ai/plugin.yaml @@ -74,25 +74,27 @@ permissions: dependencies: python: - - agno==2.4.8 - - anthropic==0.78.0 - - cerebras-cloud-sdk==1.67.0 - - cohere==5.20.4 - - google-genai==1.62.0 - - groq==1.0.0 - - litellm==1.81.8 + - agno==2.8.6 + - anthropic==0.120.2 + - cerebras-cloud-sdk==1.91.0 + - cohere==7.0.8 + - google-genai==2.16.0 + - groq==1.6.0 + - litellm==1.94.0 - llama-api-client==0.6.0 - - mistralai==1.12.0 - - ollama==0.6.1 - - openai==2.17.0 - - portkey-ai==2.1.0 + - mistralai==2.8.0 + - ollama==0.6.2 + - openai==2.52.0 + - portkey-ai==2.3.4 npm: - '@antv/infographic^0.2.13' + - '@terrastruct/d2>=0.1.33' - katex>=0.16.27 - - markstream-vue2^0.0.18 - - mermaid>=11.12.2 + - markstream-vue2^0.0.50 + - mermaid>=11.15.0 - shiki^3.21.0 - - stream-markdown>=0.0.14 + - stream-markdown>=0.0.16 + - stream-monaco>=0.0.48 npmDev: [] plugins: [] diff --git a/ruoyi-fastapi-backend/requirements-pg.txt b/ruoyi-fastapi-backend/requirements-pg.txt index 6d17713..e544a3e 100644 --- a/ruoyi-fastapi-backend/requirements-pg.txt +++ b/ruoyi-fastapi-backend/requirements-pg.txt @@ -1,24 +1,24 @@ aiofiles==25.1.0 -alembic==1.18.3 -APScheduler==3.11.2 -async-lru==2.1.0 +alembic==1.18.5 +APScheduler==3.11.3 +async-lru==2.3.0 asyncpg==0.31.0 bcrypt==5.0.0 -fastapi[all]==0.128.2 +fastapi[all]==0.141.1 loguru==0.7.3 openpyxl==3.1.5 packaging>=26.0 pandas==2.3.3 -Pillow==12.1.1 +Pillow==12.3.0 psutil==7.2.2 -psycopg2==2.9.11 +psycopg2==2.9.12 pydantic>=2.11.4 pydantic-validation-decorator==0.1.5 -PyJWT[crypto]==2.11.0 -redis==7.1.0 -ruff==0.15.0 -SQLAlchemy[asyncio]==2.0.46 -sqlglot[rs]==28.10.0 -textual==8.2.5 +PyJWT[crypto]==2.13.0 +redis==8.1.0 +ruff==0.16.1 +SQLAlchemy[asyncio]==2.0.51 +sqlglot[rs]==30.14.0 +textual==8.2.8 user-agents==2.2.0 . diff --git a/ruoyi-fastapi-backend/requirements.txt b/ruoyi-fastapi-backend/requirements.txt index 7dd6d09..1af954c 100644 --- a/ruoyi-fastapi-backend/requirements.txt +++ b/ruoyi-fastapi-backend/requirements.txt @@ -1,24 +1,24 @@ aiofiles==25.1.0 -alembic==1.18.3 -APScheduler==3.11.2 -async-lru==2.1.0 +alembic==1.18.5 +APScheduler==3.11.3 +async-lru==2.3.0 asyncmy==0.2.11 bcrypt==5.0.0 -fastapi[all]==0.128.2 +fastapi[all]==0.141.1 loguru==0.7.3 openpyxl==3.1.5 packaging>=26.0 pandas==2.3.3 -Pillow==12.1.1 +Pillow==12.3.0 psutil==7.2.2 pydantic>=2.11.4 pydantic-validation-decorator==0.1.5 -PyJWT[crypto]==2.11.0 -PyMySQL==1.1.2 -redis==7.1.0 -ruff==0.15.0 -SQLAlchemy[asyncio]==2.0.46 -sqlglot[rs]==28.10.0 -textual==8.2.5 +PyJWT[crypto]==2.13.0 +PyMySQL==1.2.0 +redis==8.1.0 +ruff==0.16.1 +SQLAlchemy[asyncio]==2.0.51 +sqlglot[rs]==30.14.0 +textual==8.2.8 user-agents==2.2.0 . diff --git a/ruoyi-fastapi-backend/tests/cli/runtime/test_app_runtime.py b/ruoyi-fastapi-backend/tests/cli/runtime/test_app_runtime.py index 7d6443d..d470776 100644 --- a/ruoyi-fastapi-backend/tests/cli/runtime/test_app_runtime.py +++ b/ruoyi-fastapi-backend/tests/cli/runtime/test_app_runtime.py @@ -1,7 +1,9 @@ from types import SimpleNamespace +from fastapi import APIRouter, FastAPI from pytest import MonkeyPatch +from cli.groups.app.controller import AppCommandController from cli.runtime.app import AppRuntimeService from cli.runtime.app.gateway import AppInfrastructureGateway from cli.runtime.app.support import AppSnapshotSupport @@ -10,6 +12,40 @@ REDIS_PORT = 6379 +def _build_app_with_included_router() -> FastAPI: + app = FastAPI() + router = APIRouter() + + @router.get( + '/visible', + name='visible_route', + summary='Visible route', + operation_id='visibleRoute', + tags=['route'], + ) + async def visible_route() -> dict[str, bool]: + return {'ok': True} + + @router.get('/hidden', include_in_schema=False) + async def hidden_route() -> dict[str, bool]: + return {'ok': True} + + app.include_router(router, prefix='/api', tags=['included']) + return app + + +def _expected_visible_route() -> dict[str, object]: + return { + 'path': '/api/visible', + 'methods': ['GET'], + 'name': 'visible_route', + 'summary': 'Visible route', + 'operationId': 'visibleRoute', + 'tags': ['included', 'route'], + 'includeInSchema': True, + } + + class FakeRuntimeEnvironment(RuntimeEnvironmentService): """ 模拟运行时环境服务。 @@ -159,3 +195,37 @@ def _fake_get_server_module() -> FakeServerModule: payload = service.build_app_instance() assert payload == {'app': 'ok'} + + +def test_app_runtime_service_expands_included_router_routes() -> None: + app = _build_app_with_included_router() + gateway = AppInfrastructureGateway() + service = AppRuntimeService( + runtime_environment=FakeRuntimeEnvironment(), + infrastructure_gateway=gateway, + ) + fake_server_module = SimpleNamespace(create_app=lambda: app) + + def _fake_get_server_module() -> SimpleNamespace: + return fake_server_module + + object.__setattr__(gateway, 'get_server_module', _fake_get_server_module) + + payload = service.get_app_routes_snapshot('dev') + hidden_payload = service.get_app_routes_snapshot('dev', include_hidden=True) + + assert payload['routes'] == [_expected_visible_route()] + assert payload['count'] == 1 + assert [route['path'] for route in hidden_payload['routes']] == ['/api/hidden', '/api/visible'] + assert [route['includeInSchema'] for route in hidden_payload['routes']] == [False, True] + + +def test_app_command_controller_serializes_included_router_routes() -> None: + app = _build_app_with_included_router() + + routes = AppCommandController._serialize_routes(app) + hidden_routes = AppCommandController._serialize_routes(app, include_hidden=True) + + assert routes == [_expected_visible_route()] + assert [route['path'] for route in hidden_routes] == ['/api/hidden', '/api/visible'] + assert [route['includeInSchema'] for route in hidden_routes] == [False, True] diff --git a/ruoyi-fastapi-backend/tests/plugins/sample_plugins/test_ai_plugin.py b/ruoyi-fastapi-backend/tests/plugins/sample_plugins/test_ai_plugin.py index 35299e3..aa35f96 100644 --- a/ruoyi-fastapi-backend/tests/plugins/sample_plugins/test_ai_plugin.py +++ b/ruoyi-fastapi-backend/tests/plugins/sample_plugins/test_ai_plugin.py @@ -28,35 +28,39 @@ 'ai:chat:list', } EXPECTED_AI_PYTHON_DEPENDENCIES = [ - 'agno==2.4.8', - 'anthropic==0.78.0', - 'cerebras-cloud-sdk==1.67.0', - 'cohere==5.20.4', - 'google-genai==1.62.0', - 'groq==1.0.0', - 'litellm==1.81.8', + 'agno==2.8.6', + 'anthropic==0.120.2', + 'cerebras-cloud-sdk==1.91.0', + 'cohere==7.0.8', + 'google-genai==2.16.0', + 'groq==1.6.0', + 'litellm==1.94.0', 'llama-api-client==0.6.0', - 'mistralai==1.12.0', - 'ollama==0.6.1', - 'openai==2.17.0', - 'portkey-ai==2.1.0', + 'mistralai==2.8.0', + 'ollama==0.6.2', + 'openai==2.52.0', + 'portkey-ai==2.3.4', ] EXPECTED_AI_VUE2_NPM_DEPENDENCIES = [ '@antv/infographic^0.2.13', + '@terrastruct/d2>=0.1.33', 'katex>=0.16.27', - 'markstream-vue2^0.0.18', - 'mermaid>=11.12.2', + 'markstream-vue2^0.0.50', + 'mermaid>=11.15.0', 'shiki^3.21.0', - 'stream-markdown>=0.0.14', + 'stream-markdown>=0.0.16', + 'stream-monaco>=0.0.48', ] EXPECTED_AI_VUE3_NPM_DEPENDENCIES = [ '@antv/infographic^0.2.13', + '@terrastruct/d2>=0.1.33', 'katex>=0.16.27', - 'markstream-vue>=0.0.7-beta.6', - 'mermaid>=11.12.2', + 'markstream-vue>=1.0.9-beta.2', + 'mermaid>=11.15.0', 'shiki^3.21.0', - 'stream-markdown>=0.0.14', - 'stream-monaco>=0.0.17', + 'stream-diffs>=0.0.2', + 'stream-markdown>=0.0.16', + 'stream-monaco>=0.0.48', ] EXPECTED_AI_VUE3_NPM_DEV_DEPENDENCIES = ['vite-plugin-monaco-editor-esm==2.0.2'] EXPECTED_DEFAULT_NUM_HISTORY_RUNS = 3 diff --git a/ruoyi-fastapi-frontend/package.json b/ruoyi-fastapi-frontend/package.json index 2cb285a..2343678 100644 --- a/ruoyi-fastapi-frontend/package.json +++ b/ruoyi-fastapi-frontend/package.json @@ -29,8 +29,9 @@ "@antv/g2plot": "^2.4.31", "@antv/infographic": "^0.2.13", "@riophae/vue-treeselect": "0.4.0", + "@terrastruct/d2": ">=0.1.33", "ant-design-vue": "^1.7.8", - "axios": "0.30.2", + "axios": "0.30.3", "clipboard": "2.0.8", "core-js": "3.37.1", "echarts": "5.4.0", @@ -42,14 +43,15 @@ "js-cookie": "3.0.1", "jsencrypt": "3.0.0-rc.1", "katex": ">=0.16.27", - "markstream-vue2": "^0.0.18", - "mermaid": ">=11.12.2", + "markstream-vue2": "^0.0.50", + "mermaid": ">=11.15.0", "nprogress": "0.2.0", "quill": "2.0.2", "screenfull": "5.0.2", "shiki": "^3.21.0", "sortablejs": "1.10.2", - "stream-markdown": ">=0.0.14", + "stream-markdown": ">=0.0.16", + "stream-monaco": ">=0.0.48", "uuid": "^8.3.2", "viser-vue": "^2.4.8", "vue": "2.7.16", diff --git a/ruoyi-fastapi-frontend/plugins/ai/README.md b/ruoyi-fastapi-frontend/plugins/ai/README.md index 0fd9a57..bc890f5 100644 --- a/ruoyi-fastapi-frontend/plugins/ai/README.md +++ b/ruoyi-fastapi-frontend/plugins/ai/README.md @@ -55,10 +55,12 @@ plugins/ai/ - `markstream-vue2` - `stream-markdown` +- `stream-monaco` - `shiki` - `mermaid` - `katex` - `@antv/infographic` +- `@terrastruct/d2` ## 开发约定 diff --git a/ruoyi-fastapi-frontend/plugins/ai/views/chat/components/AiMessage.vue b/ruoyi-fastapi-frontend/plugins/ai/views/chat/components/AiMessage.vue index 4a9a724..18372e4 100644 --- a/ruoyi-fastapi-frontend/plugins/ai/views/chat/components/AiMessage.vue +++ b/ruoyi-fastapi-frontend/plugins/ai/views/chat/components/AiMessage.vue @@ -14,11 +14,23 @@ >