From db7af52fccabf6f16821bc7a3032fad11f791a60 Mon Sep 17 00:00:00 2001 From: NFish Date: Fri, 25 Apr 2025 22:14:19 +0800 Subject: [PATCH 01/18] Hotfix/create from template category (#18807) --- .../app/create-app-dialog/app-list/index.tsx | 6 +- .../create-app-dialog/app-list/sidebar.tsx | 63 +++++-------------- 2 files changed, 20 insertions(+), 49 deletions(-) diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index 702a07397d..0b0b325d9a 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -191,14 +191,16 @@ const Apps = ({
{!searchKeywords &&
- { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> + { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} />
}
{searchFilteredList && searchFilteredList.length > 0 && <>
{searchKeywords ?

{searchFilteredList.length > 1 ? t('app.newApp.foundResults', { count: searchFilteredList.length }) : t('app.newApp.foundResult', { count: searchFilteredList.length })}

- : } + :
+ +
}
void + current: AppCategories | string + categories: string[] + onClick?: (category: AppCategories | string) => void onCreateFromBlank?: () => void } -export default function Sidebar({ current, onClick, onCreateFromBlank }: SidebarProps) { +export default function Sidebar({ current, categories, onClick, onCreateFromBlank }: SidebarProps) { const { t } = useTranslation() return
-
From 0e0ec4691ac027d9c4ea77575f8ebad3ce4ee777 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Sun, 27 Apr 2025 16:00:37 +0800 Subject: [PATCH 16/18] feat: add interfaces of OAuth handler methods for authorization (#18889) --- api/core/plugin/entities/plugin_daemon.py | 11 ++- api/core/plugin/impl/oauth.py | 96 ++++++++++++++++++- .../test_oauth_convert_request_to_raw_data.py | 20 ++++ 3 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 api/tests/unit_tests/utils/http_parser/test_oauth_convert_request_to_raw_data.py diff --git a/api/core/plugin/entities/plugin_daemon.py b/api/core/plugin/entities/plugin_daemon.py index 1588cbc3c7..2bea07bea0 100644 --- a/api/core/plugin/entities/plugin_daemon.py +++ b/api/core/plugin/entities/plugin_daemon.py @@ -1,6 +1,7 @@ +from collections.abc import Mapping from datetime import datetime from enum import StrEnum -from typing import Generic, Optional, TypeVar +from typing import Any, Generic, Optional, TypeVar from pydantic import BaseModel, ConfigDict, Field @@ -158,3 +159,11 @@ class PluginInstallTaskStartResponse(BaseModel): class PluginUploadResponse(BaseModel): unique_identifier: str = Field(description="The unique identifier of the plugin.") manifest: PluginDeclaration + + +class PluginOAuthAuthorizationUrlResponse(BaseModel): + authorization_url: str = Field(description="The URL of the authorization.") + + +class PluginOAuthCredentialsResponse(BaseModel): + credentials: Mapping[str, Any] = Field(description="The credentials of the OAuth.") diff --git a/api/core/plugin/impl/oauth.py b/api/core/plugin/impl/oauth.py index 1d40edb086..91774984c8 100644 --- a/api/core/plugin/impl/oauth.py +++ b/api/core/plugin/impl/oauth.py @@ -1,6 +1,98 @@ +from collections.abc import Mapping +from typing import Any + +from werkzeug import Request + +from core.plugin.entities.plugin_daemon import PluginOAuthAuthorizationUrlResponse, PluginOAuthCredentialsResponse from core.plugin.impl.base import BasePluginClient class OAuthHandler(BasePluginClient): - def get_authorization_url(self, tenant_id: str, user_id: str, provider_name: str) -> str: - return "1234567890" + def get_authorization_url( + self, + tenant_id: str, + user_id: str, + plugin_id: str, + provider: str, + system_credentials: Mapping[str, Any], + ) -> PluginOAuthAuthorizationUrlResponse: + return self._request_with_plugin_daemon_response( + "POST", + f"plugin/{tenant_id}/dispatch/oauth/get_authorization_url", + PluginOAuthAuthorizationUrlResponse, + data={ + "user_id": user_id, + "data": { + "provider": provider, + "system_credentials": system_credentials, + }, + }, + headers={ + "X-Plugin-ID": plugin_id, + "Content-Type": "application/json", + }, + ) + + def get_credentials( + self, + tenant_id: str, + user_id: str, + plugin_id: str, + provider: str, + system_credentials: Mapping[str, Any], + request: Request, + ) -> PluginOAuthCredentialsResponse: + """ + Get credentials from the given request. + """ + + # encode request to raw http request + raw_request_bytes = self._convert_request_to_raw_data(request) + + return self._request_with_plugin_daemon_response( + "POST", + f"plugin/{tenant_id}/dispatch/oauth/get_credentials", + PluginOAuthCredentialsResponse, + data={ + "user_id": user_id, + "data": { + "provider": provider, + "system_credentials": system_credentials, + "raw_request_bytes": raw_request_bytes, + }, + }, + headers={ + "X-Plugin-ID": plugin_id, + "Content-Type": "application/json", + }, + ) + + def _convert_request_to_raw_data(self, request: Request) -> bytes: + """ + Convert a Request object to raw HTTP data. + + Args: + request: The Request object to convert. + + Returns: + The raw HTTP data as bytes. + """ + # Start with the request line + method = request.method + path = request.path + protocol = request.headers.get("HTTP_VERSION", "HTTP/1.1") + raw_data = f"{method} {path} {protocol}\r\n".encode() + + # Add headers + for header_name, header_value in request.headers.items(): + raw_data += f"{header_name}: {header_value}\r\n".encode() + + # Add empty line to separate headers from body + raw_data += b"\r\n" + + # Add body if exists + body = request.get_data(as_text=False) + if body: + raw_data += body + + return raw_data diff --git a/api/tests/unit_tests/utils/http_parser/test_oauth_convert_request_to_raw_data.py b/api/tests/unit_tests/utils/http_parser/test_oauth_convert_request_to_raw_data.py new file mode 100644 index 0000000000..f788a9756b --- /dev/null +++ b/api/tests/unit_tests/utils/http_parser/test_oauth_convert_request_to_raw_data.py @@ -0,0 +1,20 @@ +from werkzeug import Request +from werkzeug.datastructures import Headers +from werkzeug.test import EnvironBuilder + +from core.plugin.impl.oauth import OAuthHandler + + +def test_oauth_convert_request_to_raw_data(): + oauth_handler = OAuthHandler() + builder = EnvironBuilder( + method="GET", + path="/test", + headers=Headers({"Content-Type": "application/json"}), + ) + request = Request(builder.get_environ()) + raw_request_bytes = oauth_handler._convert_request_to_raw_data(request) + + assert b"GET /test HTTP/1.1" in raw_request_bytes + assert b"Content-Type: application/json" in raw_request_bytes + assert b"\r\n\r\n" in raw_request_bytes From e3daef19e7ec9740286b3cbb2218b629201c5842 Mon Sep 17 00:00:00 2001 From: "Junjie.M" <118170653@qq.com> Date: Sun, 27 Apr 2025 16:12:02 +0800 Subject: [PATCH 17/18] chatflow/workflow add field required (#18892) --- .../components/workflow/nodes/_base/components/field.tsx | 6 +++++- web/app/components/workflow/nodes/agent/panel.tsx | 6 +++++- web/app/components/workflow/nodes/code/panel.tsx | 2 +- .../components/workflow/nodes/document-extractor/panel.tsx | 1 + web/app/components/workflow/nodes/http/panel.tsx | 2 ++ web/app/components/workflow/nodes/iteration/panel.tsx | 2 ++ .../components/workflow/nodes/knowledge-retrieval/panel.tsx | 2 ++ web/app/components/workflow/nodes/list-operator/panel.tsx | 1 + web/app/components/workflow/nodes/llm/panel.tsx | 1 + .../components/workflow/nodes/parameter-extractor/panel.tsx | 3 +++ .../components/workflow/nodes/question-classifier/panel.tsx | 3 +++ 11 files changed, 26 insertions(+), 3 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/field.tsx b/web/app/components/workflow/nodes/_base/components/field.tsx index 14e850b99a..aadcea1065 100644 --- a/web/app/components/workflow/nodes/_base/components/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/field.tsx @@ -17,6 +17,7 @@ type Props = { children?: React.JSX.Element | string | null operations?: React.JSX.Element inline?: boolean + required?: boolean } const Field: FC = ({ @@ -28,6 +29,7 @@ const Field: FC = ({ operations, inline, supportFold, + required, }) => { const [fold, { toggle: toggleFold, @@ -38,7 +40,9 @@ const Field: FC = ({ onClick={() => supportFold && toggleFold()} className={cn('flex items-center justify-between', supportFold && 'cursor-pointer')}>
-
{title}
+
+ {title} {required && *} +
{tooltip && ( > = (props) => { const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey) return
- + > = ({ operations={ } + required > - > = ({
<> > = ({
> = ({ > = ({
Array
)} @@ -91,6 +92,7 @@ const Panel: FC> = ({
Array
)} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx index 20fd24e50c..3b5eefd853 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx @@ -81,6 +81,7 @@ const Panel: FC> = ({ {/* {JSON.stringify(inputs, null, 2)} */} > = ({ > = ({
> = ({
> = ({
> = ({ <> > = ({ /> > = ({
> = ({ > = ({ /> Date: Sun, 27 Apr 2025 16:13:36 +0800 Subject: [PATCH 18/18] fix: annotation update need use http put method and annotation-reply api doc parms wrong (#18891) --- api/controllers/service_api/app/annotation.py | 2 +- .../develop/template/template.zh.mdx | 32 ++++++++----------- .../template/template_advanced_chat.en.mdx | 32 ++++++++----------- .../template/template_advanced_chat.zh.mdx | 32 ++++++++----------- 4 files changed, 43 insertions(+), 55 deletions(-) diff --git a/api/controllers/service_api/app/annotation.py b/api/controllers/service_api/app/annotation.py index 522a96b791..c50f551faf 100644 --- a/api/controllers/service_api/app/annotation.py +++ b/api/controllers/service_api/app/annotation.py @@ -79,7 +79,7 @@ class AnnotationListApi(Resource): class AnnotationUpdateDeleteApi(Resource): @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) @marshal_with(annotation_fields) - def post(self, app_model: App, end_user: EndUser, annotation_id): + def put(self, app_model: App, end_user: EndUser, annotation_id): if not current_user.is_editor: raise Forbidden() diff --git a/web/app/components/develop/template/template.zh.mdx b/web/app/components/develop/template/template.zh.mdx index 24abb481e3..447dc08396 100755 --- a/web/app/components/develop/template/template.zh.mdx +++ b/web/app/components/develop/template/template.zh.mdx @@ -643,13 +643,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` @@ -683,10 +681,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' title="Request" tag="PUT" label="/apps/annotations/{annotation_id}" - targetCode={`curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} + targetCode={`curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} > ```bash {{ title: 'cURL' }} - curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ --header 'Authorization: Bearer {api_key}' \ --header 'Content-Type: application/json' \ --data-raw '{ @@ -699,13 +697,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` @@ -763,10 +759,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 动作,只能是 'enable' 或 'disable' - + 指定的嵌入模型提供商, 必须先在系统内设定好接入的模型,对应的是provider字段 - + 指定的嵌入模型,对应的是model字段 diff --git a/web/app/components/develop/template/template_advanced_chat.en.mdx b/web/app/components/develop/template/template_advanced_chat.en.mdx index f645133030..0d10af6a71 100644 --- a/web/app/components/develop/template/template_advanced_chat.en.mdx +++ b/web/app/components/develop/template/template_advanced_chat.en.mdx @@ -1337,13 +1337,11 @@ Chat applications support session persistence, allowing previous chat history to ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` @@ -1377,10 +1375,10 @@ Chat applications support session persistence, allowing previous chat history to title="Request" tag="PUT" label="/apps/annotations/{annotation_id}" - targetCode={`curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} + targetCode={`curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} > ```bash {{ title: 'cURL' }} - curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + curl --location --request PUT '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ --header 'Authorization: Bearer {api_key}' \ --header 'Content-Type: application/json' \ --data-raw '{ @@ -1393,13 +1391,11 @@ Chat applications support session persistence, allowing previous chat history to ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` @@ -1457,10 +1453,10 @@ Chat applications support session persistence, allowing previous chat history to Action, can only be 'enable' or 'disable' - + Specified embedding model provider, must be set up in the system first, corresponding to the provider field(Optional) - + Specified embedding model, corresponding to the model field(Optional) diff --git a/web/app/components/develop/template/template_advanced_chat.zh.mdx b/web/app/components/develop/template/template_advanced_chat.zh.mdx index 240044a25d..e634130a4b 100755 --- a/web/app/components/develop/template/template_advanced_chat.zh.mdx +++ b/web/app/components/develop/template/template_advanced_chat.zh.mdx @@ -1361,13 +1361,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` @@ -1401,10 +1399,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' title="Request" tag="PUT" label="/apps/annotations/{annotation_id}" - targetCode={`curl --location --request POST '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} + targetCode={`curl --location --request PUT '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{"question": "What is your name?","answer": "I am Dify."}'`} > ```bash {{ title: 'cURL' }} - curl --location --request POST '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \ + curl --location --request PUT '${props.appDetail.api_base_url}/apps/annotations/{annotation_id}' \ --header 'Authorization: Bearer {api_key}' \ --header 'Content-Type: application/json' \ --data-raw '{ @@ -1417,13 +1415,11 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' ```json {{ title: 'Response' }} { - { - "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", - "question": "What is your name?", - "answer": "I am Dify.", - "hit_count": 0, - "created_at": 1735625869 - } + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 } ``` @@ -1481,10 +1477,10 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' 动作,只能是 'enable' 或 'disable' - + 指定的嵌入模型提供商, 必须先在系统内设定好接入的模型,对应的是provider字段 - + 指定的嵌入模型,对应的是model字段