From e0fb754e8008ab6e556f99d2d4f5b77994d9c2cb Mon Sep 17 00:00:00 2001 From: -LAN- Date: Sat, 27 Sep 2025 02:36:03 +0800 Subject: [PATCH] Use POST for more-like-this flow --- api/controllers/console/explore/message.py | 9 +- api/controllers/web/message.py | 15 +-- .../test_explore_message_more_like_this.py | 124 ++++++++++++++++++ .../web/test_message_more_like_this.py | 103 +++++++++++++++ web/service/share.ts | 4 +- 5 files changed, 243 insertions(+), 12 deletions(-) create mode 100644 api/tests/unit_tests/controllers/console/explore/test_explore_message_more_like_this.py create mode 100644 api/tests/unit_tests/controllers/web/test_message_more_like_this.py diff --git a/api/controllers/console/explore/message.py b/api/controllers/console/explore/message.py index b045e47846..217267031d 100644 --- a/api/controllers/console/explore/message.py +++ b/api/controllers/console/explore/message.py @@ -108,7 +108,7 @@ class MessageFeedbackApi(InstalledAppResource): endpoint="installed_app_more_like_this", ) class MessageMoreLikeThisApi(InstalledAppResource): - def get(self, installed_app, message_id): + def post(self, installed_app, message_id): app_model = installed_app.app if app_model.mode != "completion": raise NotCompletionAppError() @@ -117,7 +117,12 @@ class MessageMoreLikeThisApi(InstalledAppResource): parser = reqparse.RequestParser() parser.add_argument( - "response_mode", type=str, required=True, choices=["blocking", "streaming"], location="args" + "response_mode", + type=str, + required=False, + choices=["blocking", "streaming"], + default="blocking", + location="json", ) args = parser.parse_args() diff --git a/api/controllers/web/message.py b/api/controllers/web/message.py index a52cccac13..c74414c52b 100644 --- a/api/controllers/web/message.py +++ b/api/controllers/web/message.py @@ -169,12 +169,6 @@ class MessageMoreLikeThisApi(WebApiResource): @web_ns.doc( params={ "message_id": {"description": "Message UUID", "type": "string", "required": True}, - "response_mode": { - "description": "Response mode", - "type": "string", - "enum": ["blocking", "streaming"], - "required": True, - }, } ) @web_ns.doc( @@ -187,7 +181,7 @@ class MessageMoreLikeThisApi(WebApiResource): 500: "Internal Server Error", } ) - def get(self, app_model, end_user, message_id): + def post(self, app_model, end_user, message_id): if app_model.mode != "completion": raise NotCompletionAppError() @@ -195,7 +189,12 @@ class MessageMoreLikeThisApi(WebApiResource): parser = reqparse.RequestParser() parser.add_argument( - "response_mode", type=str, required=True, choices=["blocking", "streaming"], location="args" + "response_mode", + type=str, + required=False, + choices=["blocking", "streaming"], + default="blocking", + location="json", ) args = parser.parse_args() diff --git a/api/tests/unit_tests/controllers/console/explore/test_explore_message_more_like_this.py b/api/tests/unit_tests/controllers/console/explore/test_explore_message_more_like_this.py new file mode 100644 index 0000000000..ed072f37f8 --- /dev/null +++ b/api/tests/unit_tests/controllers/console/explore/test_explore_message_more_like_this.py @@ -0,0 +1,124 @@ +import inspect +import uuid +from types import SimpleNamespace +from unittest.mock import MagicMock + +import pytest +from flask import Flask + +from controllers.console.explore.error import NotCompletionAppError +from controllers.console.explore.message import MessageMoreLikeThisApi +from core.app.entities.app_invoke_entities import InvokeFrom +from models.account import Account + + +@pytest.fixture +def flask_app(): + app = Flask(__name__) + app.config["TESTING"] = True + return app + + +@pytest.fixture +def account_user(): + user = Account(name="Tester", email="tester@example.com") + user.id = "user-id" + return user + + +class TestConsoleExploreMessageMoreLikeThisApi: + def test_post_generates_with_blocking_default(self, flask_app, account_user, monkeypatch): + installed_app = SimpleNamespace(app=SimpleNamespace(mode="completion")) + response_payload = {"answer": "ok"} + generate_mock = MagicMock(return_value=object()) + compact_mock = MagicMock(return_value=response_payload) + + monkeypatch.setattr( + "controllers.console.explore.message.current_user", + account_user, + raising=False, + ) + monkeypatch.setattr( + "controllers.console.explore.message.AppGenerateService.generate_more_like_this", + generate_mock, + raising=False, + ) + monkeypatch.setattr( + "controllers.console.explore.message.helper.compact_generate_response", + compact_mock, + raising=False, + ) + + handler = inspect.unwrap(MessageMoreLikeThisApi.post) + controller = MessageMoreLikeThisApi() + message_id = uuid.uuid4() + + with flask_app.test_request_context( + f"/messages/{message_id}/more-like-this", + method="POST", + json={}, + ): + result = handler(controller, installed_app, message_id) + + assert result == response_payload + generate_mock.assert_called_once() + call_kwargs = generate_mock.call_args.kwargs + assert call_kwargs["streaming"] is False + assert call_kwargs["invoke_from"] == InvokeFrom.EXPLORE + assert call_kwargs["message_id"] == str(message_id) + compact_mock.assert_called_once_with(generate_mock.return_value) + + def test_post_allows_streaming_mode(self, flask_app, account_user, monkeypatch): + installed_app = SimpleNamespace(app=SimpleNamespace(mode="completion")) + generate_mock = MagicMock(return_value=object()) + + monkeypatch.setattr( + "controllers.console.explore.message.current_user", + account_user, + raising=False, + ) + monkeypatch.setattr( + "controllers.console.explore.message.AppGenerateService.generate_more_like_this", + generate_mock, + raising=False, + ) + monkeypatch.setattr( + "controllers.console.explore.message.helper.compact_generate_response", + MagicMock(return_value={}), + raising=False, + ) + + handler = inspect.unwrap(MessageMoreLikeThisApi.post) + controller = MessageMoreLikeThisApi() + message_id = uuid.uuid4() + + with flask_app.test_request_context( + f"/messages/{message_id}/more-like-this", + method="POST", + json={"response_mode": "streaming"}, + ): + handler(controller, installed_app, message_id) + + generate_mock.assert_called_once() + assert generate_mock.call_args.kwargs["streaming"] is True + + def test_non_completion_app_raises(self, flask_app, account_user, monkeypatch): + installed_app = SimpleNamespace(app=SimpleNamespace(mode="chat")) + + monkeypatch.setattr( + "controllers.console.explore.message.current_user", + account_user, + raising=False, + ) + + handler = inspect.unwrap(MessageMoreLikeThisApi.post) + controller = MessageMoreLikeThisApi() + message_id = uuid.uuid4() + + with flask_app.test_request_context( + f"/messages/{message_id}/more-like-this", + method="POST", + json={}, + ): + with pytest.raises(NotCompletionAppError): + handler(controller, installed_app, message_id) diff --git a/api/tests/unit_tests/controllers/web/test_message_more_like_this.py b/api/tests/unit_tests/controllers/web/test_message_more_like_this.py new file mode 100644 index 0000000000..c9ce2bc1ca --- /dev/null +++ b/api/tests/unit_tests/controllers/web/test_message_more_like_this.py @@ -0,0 +1,103 @@ +import inspect +import uuid +from types import SimpleNamespace +from unittest.mock import MagicMock + +import pytest +from flask import Flask + +from controllers.web.error import NotCompletionAppError +from controllers.web.message import MessageMoreLikeThisApi +from core.app.entities.app_invoke_entities import InvokeFrom + + +@pytest.fixture +def flask_app(): + app = Flask(__name__) + app.config["TESTING"] = True + return app + + +class TestWebMessageMoreLikeThisApi: + def test_post_uses_blocking_by_default(self, flask_app, monkeypatch): + app_model = SimpleNamespace(mode="completion") + end_user = SimpleNamespace() + response_payload = {"answer": "ok"} + + generate_mock = MagicMock(return_value=object()) + compact_mock = MagicMock(return_value=response_payload) + + monkeypatch.setattr( + "controllers.web.message.AppGenerateService.generate_more_like_this", + generate_mock, + raising=False, + ) + monkeypatch.setattr( + "controllers.web.message.helper.compact_generate_response", + compact_mock, + raising=False, + ) + + handler = inspect.unwrap(MessageMoreLikeThisApi.post) + controller = MessageMoreLikeThisApi() + message_id = uuid.uuid4() + + with flask_app.test_request_context( + f"/messages/{message_id}/more-like-this", + method="POST", + json={}, + ): + result = handler(controller, app_model, end_user, message_id) + + assert result == response_payload + generate_mock.assert_called_once() + call_kwargs = generate_mock.call_args.kwargs + assert call_kwargs["streaming"] is False + assert call_kwargs["invoke_from"] == InvokeFrom.WEB_APP + assert call_kwargs["message_id"] == str(message_id) + compact_mock.assert_called_once_with(generate_mock.return_value) + + def test_post_allows_streaming_mode(self, flask_app, monkeypatch): + app_model = SimpleNamespace(mode="completion") + end_user = SimpleNamespace() + + generate_mock = MagicMock(return_value=object()) + monkeypatch.setattr( + "controllers.web.message.AppGenerateService.generate_more_like_this", + generate_mock, + raising=False, + ) + monkeypatch.setattr( + "controllers.web.message.helper.compact_generate_response", + MagicMock(return_value={}), + raising=False, + ) + + handler = inspect.unwrap(MessageMoreLikeThisApi.post) + controller = MessageMoreLikeThisApi() + message_id = uuid.uuid4() + + with flask_app.test_request_context( + f"/messages/{message_id}/more-like-this", + method="POST", + json={"response_mode": "streaming"}, + ): + handler(controller, app_model, end_user, message_id) + + generate_mock.assert_called_once() + assert generate_mock.call_args.kwargs["streaming"] is True + + def test_non_completion_app_raises(self, flask_app): + app_model = SimpleNamespace(mode="chat") + end_user = SimpleNamespace() + handler = inspect.unwrap(MessageMoreLikeThisApi.post) + controller = MessageMoreLikeThisApi() + message_id = uuid.uuid4() + + with flask_app.test_request_context( + f"/messages/{message_id}/more-like-this", + method="POST", + json={}, + ): + with pytest.raises(NotCompletionAppError): + handler(controller, app_model, end_user, message_id) diff --git a/web/service/share.ts b/web/service/share.ts index f1e512564b..559bb2a158 100644 --- a/web/service/share.ts +++ b/web/service/share.ts @@ -251,8 +251,8 @@ export const updateFeedback = async ({ url, body }: { url: string; body: Feedbac } export const fetchMoreLikeThis = async (messageId: string, isInstalledApp: boolean, installedAppId = '') => { - return (getAction('get', isInstalledApp))(getUrl(`/messages/${messageId}/more-like-this`, isInstalledApp, installedAppId), { - params: { + return (getAction('post', isInstalledApp))(getUrl(`/messages/${messageId}/more-like-this`, isInstalledApp, installedAppId), { + body: { response_mode: 'blocking', }, })