mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 08:58:09 +08:00
The site field returned by HumanInputFormApi is inconsistent with the API docs (vibe-kanban e0fb38c9)
```javascript
Expected structure:
```json
{
"site": {
"app_id": "e9823576-d836-4f2b-b46f-bd4df1d82230",
"end_user_id": "b7aa295d-1560-4d87-a828-77b3f39b30d0",
"enable_site": true,
"site": {
"title": "wf",
"chat_color_theme": null,
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "\ud83e\udd16",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": null,
"copyright": null,
"privacy_policy": null,
"custom_disclaimer": "",
"default_language": "en-US",
"prompt_public": false,
"show_workflow_steps": true,
"use_icon_as_answer_icon": false
},
"model_config": null,
"plan": "basic",
"can_replace_logo": false,
"custom_config": null
},
// ... other fields
}
```
The current implementation of HumanInputFormApi returns the following structure:
```json
{
"site": {
"title": "hitl-chatflow",
"chat_color_theme": null,
"chat_color_theme_inverted": false,
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FFEAD5",
"icon_url": null,
"description": null,
"copyright": null,
"privacy_policy": null,
"custom_disclaimer": "",
"default_language": "en-US",
"prompt_public": false,
"show_workflow_steps": true,
"use_icon_as_answer_icon": false
},
// ... other fields
}
```
\`\`\`
This commit is contained in:
@ -11,7 +11,7 @@ from werkzeug.exceptions import Forbidden
|
||||
|
||||
from controllers.web import web_ns
|
||||
from controllers.web.error import NotFoundError
|
||||
from controllers.web.site import serialize_site
|
||||
from controllers.web.site import serialize_app_site_payload
|
||||
from extensions.ext_database import db
|
||||
from models.account import TenantStatus
|
||||
from models.human_input import RecipientType
|
||||
@ -56,9 +56,9 @@ class HumanInputFormApi(Resource):
|
||||
if form is None:
|
||||
raise NotFoundError("Form not found")
|
||||
|
||||
site = _get_site_from_form(form)
|
||||
app_model, site = _get_app_site_from_form(form)
|
||||
|
||||
return _jsonify_form_definition(form, site_payload=serialize_site(site))
|
||||
return _jsonify_form_definition(form, site_payload=serialize_app_site_payload(app_model, site, None))
|
||||
|
||||
# def post(self, _app_model: App, _end_user: EndUser, form_token: str):
|
||||
def post(self, form_token: str):
|
||||
@ -100,8 +100,8 @@ class HumanInputFormApi(Resource):
|
||||
return {}, 200
|
||||
|
||||
|
||||
def _get_site_from_form(form: Form) -> Site:
|
||||
"""Resolve Site for the form's app and validate tenant status."""
|
||||
def _get_app_site_from_form(form: Form) -> tuple[App, Site]:
|
||||
"""Resolve App/Site for the form's app and validate tenant status."""
|
||||
app_model = db.session.query(App).where(App.id == form.app_id).first()
|
||||
if app_model is None or app_model.tenant_id != form.tenant_id:
|
||||
raise NotFoundError("Form not found")
|
||||
@ -113,4 +113,4 @@ def _get_site_from_form(form: Form) -> Site:
|
||||
if app_model.tenant and app_model.tenant.status == TenantStatus.ARCHIVE:
|
||||
raise Forbidden()
|
||||
|
||||
return site
|
||||
return app_model, site
|
||||
|
||||
@ -7,7 +7,7 @@ from controllers.web.wraps import WebApiResource
|
||||
from extensions.ext_database import db
|
||||
from libs.helper import AppIconUrlField
|
||||
from models.account import TenantStatus
|
||||
from models.model import Site
|
||||
from models.model import App, Site
|
||||
from services.feature_service import FeatureService
|
||||
|
||||
|
||||
@ -104,12 +104,18 @@ class AppSiteInfo:
|
||||
if tenant.custom_config_dict.get("replace_webapp_logo")
|
||||
else None
|
||||
)
|
||||
self.custom_config = {
|
||||
"remove_webapp_brand": remove_webapp_brand,
|
||||
"replace_webapp_logo": replace_webapp_logo,
|
||||
}
|
||||
self.custom_config = {
|
||||
"remove_webapp_brand": remove_webapp_brand,
|
||||
"replace_webapp_logo": replace_webapp_logo,
|
||||
}
|
||||
|
||||
|
||||
def serialize_site(site: Site) -> dict:
|
||||
"""Serialize Site model using the same schema as AppSiteApi."""
|
||||
return marshal(site, AppSiteApi.site_fields)
|
||||
|
||||
|
||||
def serialize_app_site_payload(app_model: App, site: Site, end_user_id: str | None) -> dict:
|
||||
can_replace_logo = FeatureService.get_features(app_model.tenant_id).can_replace_logo
|
||||
app_site_info = AppSiteInfo(app_model.tenant, app_model, site, end_user_id, can_replace_logo)
|
||||
return marshal(app_site_info, AppSiteApi.app_fields)
|
||||
|
||||
@ -12,6 +12,7 @@ from flask import Flask
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
import controllers.web.human_input_form as human_input_module
|
||||
import controllers.web.site as site_module
|
||||
|
||||
HumanInputFormApi = human_input_module.HumanInputFormApi
|
||||
RecipientType = human_input_module.RecipientType
|
||||
@ -71,13 +72,18 @@ def test_get_form_includes_site(monkeypatch: pytest.MonkeyPatch, app: Flask):
|
||||
|
||||
form = _FakeForm()
|
||||
|
||||
tenant = SimpleNamespace(status=TenantStatus.NORMAL)
|
||||
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", tenant=tenant)
|
||||
tenant = SimpleNamespace(
|
||||
id="tenant-1",
|
||||
status=TenantStatus.NORMAL,
|
||||
plan="basic",
|
||||
custom_config_dict={"remove_webapp_brand": True, "replace_webapp_logo": False},
|
||||
)
|
||||
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", tenant=tenant, enable_site=True)
|
||||
workflow_run = SimpleNamespace(app_id="app-1")
|
||||
site_model = SimpleNamespace(
|
||||
title="My Site",
|
||||
icon_type="emoji",
|
||||
icon=None,
|
||||
icon="robot",
|
||||
icon_background="#fff",
|
||||
description="desc",
|
||||
default_language="en",
|
||||
@ -100,15 +106,46 @@ def test_get_form_includes_site(monkeypatch: pytest.MonkeyPatch, app: Flask):
|
||||
db_stub = _FakeDB(_FakeSession({"WorkflowRun": workflow_run, "App": app_model, "Site": site_model}))
|
||||
monkeypatch.setattr(human_input_module, "db", db_stub)
|
||||
|
||||
# Patch serialize_site to a predictable value.
|
||||
monkeypatch.setattr(human_input_module, "serialize_site", lambda site: {"title": site.title})
|
||||
monkeypatch.setattr(
|
||||
site_module.FeatureService,
|
||||
"get_features",
|
||||
lambda tenant_id: SimpleNamespace(can_replace_logo=True),
|
||||
)
|
||||
|
||||
with app.test_request_context("/api/form/human_input/token-1", method="GET"):
|
||||
response = HumanInputFormApi().get("token-1")
|
||||
|
||||
body = json.loads(response.get_data(as_text=True))
|
||||
assert body["form_content"] == "hello"
|
||||
assert body["site"] == {"title": "My Site"}
|
||||
assert body["site"] == {
|
||||
"app_id": "app-1",
|
||||
"end_user_id": None,
|
||||
"enable_site": True,
|
||||
"site": {
|
||||
"title": "My Site",
|
||||
"chat_color_theme": "light",
|
||||
"chat_color_theme_inverted": False,
|
||||
"icon_type": "emoji",
|
||||
"icon": "robot",
|
||||
"icon_background": "#fff",
|
||||
"icon_url": None,
|
||||
"description": "desc",
|
||||
"copyright": None,
|
||||
"privacy_policy": None,
|
||||
"custom_disclaimer": None,
|
||||
"default_language": "en",
|
||||
"prompt_public": False,
|
||||
"show_workflow_steps": True,
|
||||
"use_icon_as_answer_icon": False,
|
||||
},
|
||||
"model_config": None,
|
||||
"plan": "basic",
|
||||
"can_replace_logo": True,
|
||||
"custom_config": {
|
||||
"remove_webapp_brand": True,
|
||||
"replace_webapp_logo": None,
|
||||
},
|
||||
}
|
||||
service_mock.get_form_definition_by_token.assert_called_once_with(
|
||||
RecipientType.STANDALONE_WEB_APP,
|
||||
"token-1",
|
||||
@ -131,13 +168,18 @@ def test_get_form_allows_backstage_token(monkeypatch: pytest.MonkeyPatch, app: F
|
||||
return _FakeDefinition()
|
||||
|
||||
form = _FakeForm()
|
||||
tenant = SimpleNamespace(status=TenantStatus.NORMAL)
|
||||
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", tenant=tenant)
|
||||
tenant = SimpleNamespace(
|
||||
id="tenant-1",
|
||||
status=TenantStatus.NORMAL,
|
||||
plan="basic",
|
||||
custom_config_dict={"remove_webapp_brand": True, "replace_webapp_logo": False},
|
||||
)
|
||||
app_model = SimpleNamespace(id="app-1", tenant_id="tenant-1", tenant=tenant, enable_site=True)
|
||||
workflow_run = SimpleNamespace(app_id="app-1")
|
||||
site_model = SimpleNamespace(
|
||||
title="My Site",
|
||||
icon_type="emoji",
|
||||
icon=None,
|
||||
icon="robot",
|
||||
icon_background="#fff",
|
||||
description="desc",
|
||||
default_language="en",
|
||||
@ -158,14 +200,46 @@ def test_get_form_allows_backstage_token(monkeypatch: pytest.MonkeyPatch, app: F
|
||||
db_stub = _FakeDB(_FakeSession({"WorkflowRun": workflow_run, "App": app_model, "Site": site_model}))
|
||||
monkeypatch.setattr(human_input_module, "db", db_stub)
|
||||
|
||||
monkeypatch.setattr(human_input_module, "serialize_site", lambda site: {"title": site.title})
|
||||
monkeypatch.setattr(
|
||||
site_module.FeatureService,
|
||||
"get_features",
|
||||
lambda tenant_id: SimpleNamespace(can_replace_logo=True),
|
||||
)
|
||||
|
||||
with app.test_request_context("/api/form/human_input/token-1", method="GET"):
|
||||
response = HumanInputFormApi().get("token-1")
|
||||
|
||||
body = json.loads(response.get_data(as_text=True))
|
||||
assert body["form_content"] == "hello"
|
||||
assert body["site"] == {"title": "My Site"}
|
||||
assert body["site"] == {
|
||||
"app_id": "app-1",
|
||||
"end_user_id": None,
|
||||
"enable_site": True,
|
||||
"site": {
|
||||
"title": "My Site",
|
||||
"chat_color_theme": "light",
|
||||
"chat_color_theme_inverted": False,
|
||||
"icon_type": "emoji",
|
||||
"icon": "robot",
|
||||
"icon_background": "#fff",
|
||||
"icon_url": None,
|
||||
"description": "desc",
|
||||
"copyright": None,
|
||||
"privacy_policy": None,
|
||||
"custom_disclaimer": None,
|
||||
"default_language": "en",
|
||||
"prompt_public": False,
|
||||
"show_workflow_steps": True,
|
||||
"use_icon_as_answer_icon": False,
|
||||
},
|
||||
"model_config": None,
|
||||
"plan": "basic",
|
||||
"can_replace_logo": True,
|
||||
"custom_config": {
|
||||
"remove_webapp_brand": True,
|
||||
"replace_webapp_logo": None,
|
||||
},
|
||||
}
|
||||
assert service_mock.get_form_definition_by_token.call_args_list == [
|
||||
call(RecipientType.STANDALONE_WEB_APP, "token-1"),
|
||||
call(RecipientType.BACKSTAGE, "token-1"),
|
||||
|
||||
Reference in New Issue
Block a user