From 9b7adcd4d93b06182682293a2377250aa23d4c19 Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:06:46 +0800 Subject: [PATCH 01/21] update tidb batch get endpoint to basic mode (#11426) --- api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py b/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py index a6f3ad7fef..8dd5922ad0 100644 --- a/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py +++ b/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py @@ -162,7 +162,7 @@ class TidbService: clusters = [] tidb_serverless_list_map = {item.cluster_id: item for item in tidb_serverless_list} cluster_ids = [item.cluster_id for item in tidb_serverless_list] - params = {"clusterIds": cluster_ids, "view": "FULL"} + params = {"clusterIds": cluster_ids, "view": "BASIC"} response = requests.get( f"{api_url}/clusters:batchGet", params=params, auth=HTTPDigestAuth(public_key, private_key) ) From 1490a19fa1d3c105a743eecbab656eb91b8ecd56 Mon Sep 17 00:00:00 2001 From: KVOJJJin Date: Fri, 6 Dec 2024 17:35:35 +0800 Subject: [PATCH 02/21] Fix: compatible with outputs data structure (#11432) --- web/app/components/base/file-uploader/utils.ts | 4 ++-- web/app/components/workflow/run/output-panel.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/components/base/file-uploader/utils.ts b/web/app/components/base/file-uploader/utils.ts index 8c752fde8a..a04d6359d3 100644 --- a/web/app/components/base/file-uploader/utils.ts +++ b/web/app/components/base/file-uploader/utils.ts @@ -158,13 +158,13 @@ export const isAllowedFileExtension = (fileName: string, fileMimetype: string, a export const getFilesInLogs = (rawData: any) => { const result = Object.keys(rawData || {}).map((key) => { - if (typeof rawData[key] === 'object' && rawData[key].dify_model_identity === '__dify__file__') { + if (typeof rawData[key] === 'object' && rawData[key]?.dify_model_identity === '__dify__file__') { return { varName: key, list: getProcessedFilesFromResponse([rawData[key]]), } } - if (Array.isArray(rawData[key]) && rawData[key].some(item => item.dify_model_identity === '__dify__file__')) { + if (Array.isArray(rawData[key]) && rawData[key].some(item => item?.dify_model_identity === '__dify__file__')) { return { varName: key, list: getProcessedFilesFromResponse(rawData[key]), diff --git a/web/app/components/workflow/run/output-panel.tsx b/web/app/components/workflow/run/output-panel.tsx index 9904079eda..a1667d9b45 100644 --- a/web/app/components/workflow/run/output-panel.tsx +++ b/web/app/components/workflow/run/output-panel.tsx @@ -35,12 +35,12 @@ const OutputPanel: FC = ({ for (const key in outputs) { if (Array.isArray(outputs[key])) { outputs[key].map((output: any) => { - if (output.dify_model_identity === '__dify__file__') + if (output?.dify_model_identity === '__dify__file__') fileList.push(output) return null }) } - else if (outputs[key].dify_model_identity === '__dify__file__') { + else if (outputs[key]?.dify_model_identity === '__dify__file__') { fileList.push(outputs[key]) } } From 9277156b6c87626b8479da695c469ab2871ebe53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=E1=BB=B3nh=20Gia=20B=C3=B4i?= Date: Fri, 6 Dec 2024 17:55:59 +0700 Subject: [PATCH 03/21] fix(document_extractor): pptx file type and missing metadata_filename UnstructuredIO (#11364) Co-authored-by: Julian Huynh --- .../workflow/nodes/document_extractor/node.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/api/core/workflow/nodes/document_extractor/node.py b/api/core/workflow/nodes/document_extractor/node.py index d490a2eb03..59afe7ac87 100644 --- a/api/core/workflow/nodes/document_extractor/node.py +++ b/api/core/workflow/nodes/document_extractor/node.py @@ -1,6 +1,8 @@ import csv import io import json +import os +import tempfile import docx import pandas as pd @@ -264,14 +266,20 @@ def _extract_text_from_ppt(file_content: bytes) -> str: def _extract_text_from_pptx(file_content: bytes) -> str: try: - with io.BytesIO(file_content) as file: - if dify_config.UNSTRUCTURED_API_URL and dify_config.UNSTRUCTURED_API_KEY: - elements = partition_via_api( - file=file, - api_url=dify_config.UNSTRUCTURED_API_URL, - api_key=dify_config.UNSTRUCTURED_API_KEY, - ) - else: + if dify_config.UNSTRUCTURED_API_URL and dify_config.UNSTRUCTURED_API_KEY: + with tempfile.NamedTemporaryFile(suffix=".pptx", delete=False) as temp_file: + temp_file.write(file_content) + temp_file.flush() + with open(temp_file.name, "rb") as file: + elements = partition_via_api( + file=file, + metadata_filename=temp_file.name, + api_url=dify_config.UNSTRUCTURED_API_URL, + api_key=dify_config.UNSTRUCTURED_API_KEY, + ) + os.unlink(temp_file.name) + else: + with io.BytesIO(file_content) as file: elements = partition_pptx(file=file) return "\n".join([getattr(element, "text", "") for element in elements]) except Exception as e: From d9d5d35a7726beaf09f2feb41b49a5815878b0bd Mon Sep 17 00:00:00 2001 From: yihong Date: Sat, 7 Dec 2024 16:28:15 +0800 Subject: [PATCH 04/21] fix: issue #10596 by making the iteration node outputs right (#11394) Signed-off-by: yihong0618 Signed-off-by: -LAN- Co-authored-by: -LAN- --- api/core/app/entities/queue_entities.py | 14 +--- .../nodes/iteration/iteration_node.py | 76 +++++++++++-------- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/api/core/app/entities/queue_entities.py b/api/core/app/entities/queue_entities.py index 15543638fc..5e9b6517ba 100644 --- a/api/core/app/entities/queue_entities.py +++ b/api/core/app/entities/queue_entities.py @@ -2,7 +2,7 @@ from datetime import datetime from enum import Enum, StrEnum from typing import Any, Optional -from pydantic import BaseModel, field_validator +from pydantic import BaseModel from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk from core.workflow.entities.node_entities import NodeRunMetadataKey @@ -113,18 +113,6 @@ class QueueIterationNextEvent(AppQueueEvent): output: Optional[Any] = None # output for the current iteration duration: Optional[float] = None - @field_validator("output", mode="before") - @classmethod - def set_output(cls, v): - """ - Set output - """ - if v is None: - return None - if isinstance(v, int | float | str | bool | dict | list): - return v - raise ValueError("output must be a valid type") - class QueueIterationCompletedEvent(AppQueueEvent): """ diff --git a/api/core/workflow/nodes/iteration/iteration_node.py b/api/core/workflow/nodes/iteration/iteration_node.py index bba6ac20d3..74ec95deaa 100644 --- a/api/core/workflow/nodes/iteration/iteration_node.py +++ b/api/core/workflow/nodes/iteration/iteration_node.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any, Optional, cast from flask import Flask, current_app from configs import dify_config -from core.model_runtime.utils.encoders import jsonable_encoder +from core.variables import IntegerVariable from core.workflow.entities.node_entities import ( NodeRunMetadataKey, NodeRunResult, @@ -155,18 +155,19 @@ class IterationNode(BaseNode[IterationNodeData]): iteration_node_data=self.node_data, index=0, pre_iteration_output=None, + duration=None, ) iter_run_map: dict[str, float] = {} outputs: list[Any] = [None] * len(iterator_list_value) try: if self.node_data.is_parallel: futures: list[Future] = [] - q = Queue() + q: Queue = Queue() thread_pool = GraphEngineThreadPool(max_workers=self.node_data.parallel_nums, max_submit_count=100) for index, item in enumerate(iterator_list_value): future: Future = thread_pool.submit( self._run_single_iter_parallel, - current_app._get_current_object(), + current_app._get_current_object(), # type: ignore q, iterator_list_value, inputs, @@ -181,6 +182,7 @@ class IterationNode(BaseNode[IterationNodeData]): future.add_done_callback(thread_pool.task_done_callback) futures.append(future) succeeded_count = 0 + empty_count = 0 while True: try: event = q.get(timeout=1) @@ -208,17 +210,22 @@ class IterationNode(BaseNode[IterationNodeData]): else: for _ in range(len(iterator_list_value)): yield from self._run_single_iter( - iterator_list_value, - variable_pool, - inputs, - outputs, - start_at, - graph_engine, - iteration_graph, - iter_run_map, + iterator_list_value=iterator_list_value, + variable_pool=variable_pool, + inputs=inputs, + outputs=outputs, + start_at=start_at, + graph_engine=graph_engine, + iteration_graph=iteration_graph, + iter_run_map=iter_run_map, ) if self.node_data.error_handle_mode == ErrorHandleMode.REMOVE_ABNORMAL_OUTPUT: outputs = [output for output in outputs if output is not None] + + # Flatten the list of lists + if isinstance(outputs, list) and all(isinstance(output, list) for output in outputs): + outputs = [item for sublist in outputs for item in sublist] + yield IterationRunSucceededEvent( iteration_id=self.id, iteration_node_id=self.node_id, @@ -226,7 +233,7 @@ class IterationNode(BaseNode[IterationNodeData]): iteration_node_data=self.node_data, start_at=start_at, inputs=inputs, - outputs={"output": jsonable_encoder(outputs)}, + outputs={"output": outputs}, steps=len(iterator_list_value), metadata={"total_tokens": graph_engine.graph_runtime_state.total_tokens}, ) @@ -234,7 +241,7 @@ class IterationNode(BaseNode[IterationNodeData]): yield RunCompletedEvent( run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.SUCCEEDED, - outputs={"output": jsonable_encoder(outputs)}, + outputs={"output": outputs}, metadata={NodeRunMetadataKey.ITERATION_DURATION_MAP: iter_run_map}, ) ) @@ -248,7 +255,7 @@ class IterationNode(BaseNode[IterationNodeData]): iteration_node_data=self.node_data, start_at=start_at, inputs=inputs, - outputs={"output": jsonable_encoder(outputs)}, + outputs={"output": outputs}, steps=len(iterator_list_value), metadata={"total_tokens": graph_engine.graph_runtime_state.total_tokens}, error=str(e), @@ -280,7 +287,7 @@ class IterationNode(BaseNode[IterationNodeData]): :param node_data: node data :return: """ - variable_mapping = { + variable_mapping: dict[str, Sequence[str]] = { f"{node_id}.input_selector": node_data.iterator_selector, } @@ -308,7 +315,7 @@ class IterationNode(BaseNode[IterationNodeData]): sub_node_variable_mapping = node_cls.extract_variable_selector_to_variable_mapping( graph_config=graph_config, config=sub_node_config ) - sub_node_variable_mapping = cast(dict[str, list[str]], sub_node_variable_mapping) + sub_node_variable_mapping = cast(dict[str, Sequence[str]], sub_node_variable_mapping) except NotImplementedError: sub_node_variable_mapping = {} @@ -329,8 +336,12 @@ class IterationNode(BaseNode[IterationNodeData]): return variable_mapping def _handle_event_metadata( - self, event: BaseNodeEvent, iter_run_index: str, parallel_mode_run_id: str - ) -> NodeRunStartedEvent | BaseNodeEvent: + self, + *, + event: BaseNodeEvent | InNodeEvent, + iter_run_index: int, + parallel_mode_run_id: str | None, + ) -> NodeRunStartedEvent | BaseNodeEvent | InNodeEvent: """ add iteration metadata to event. """ @@ -355,6 +366,7 @@ class IterationNode(BaseNode[IterationNodeData]): def _run_single_iter( self, + *, iterator_list_value: list[str], variable_pool: VariablePool, inputs: dict[str, list], @@ -373,12 +385,12 @@ class IterationNode(BaseNode[IterationNodeData]): try: rst = graph_engine.run() # get current iteration index - current_index = variable_pool.get([self.node_id, "index"]).value + index_variable = variable_pool.get([self.node_id, "index"]) + if not isinstance(index_variable, IntegerVariable): + raise IterationIndexNotFoundError(f"iteration {self.node_id} current index not found") + current_index = index_variable.value iteration_run_id = parallel_mode_run_id if parallel_mode_run_id is not None else f"{current_index}" next_index = int(current_index) + 1 - - if current_index is None: - raise IterationIndexNotFoundError(f"iteration {self.node_id} current index not found") for event in rst: if isinstance(event, (BaseNodeEvent | BaseParallelBranchEvent)) and not event.in_iteration_id: event.in_iteration_id = self.node_id @@ -391,7 +403,9 @@ class IterationNode(BaseNode[IterationNodeData]): continue if isinstance(event, NodeRunSucceededEvent): - yield self._handle_event_metadata(event, current_index, parallel_mode_run_id) + yield self._handle_event_metadata( + event=event, iter_run_index=current_index, parallel_mode_run_id=parallel_mode_run_id + ) elif isinstance(event, BaseGraphEvent): if isinstance(event, GraphRunFailedEvent): # iteration run failed @@ -404,7 +418,7 @@ class IterationNode(BaseNode[IterationNodeData]): parallel_mode_run_id=parallel_mode_run_id, start_at=start_at, inputs=inputs, - outputs={"output": jsonable_encoder(outputs)}, + outputs={"output": outputs}, steps=len(iterator_list_value), metadata={"total_tokens": graph_engine.graph_runtime_state.total_tokens}, error=event.error, @@ -417,7 +431,7 @@ class IterationNode(BaseNode[IterationNodeData]): iteration_node_data=self.node_data, start_at=start_at, inputs=inputs, - outputs={"output": jsonable_encoder(outputs)}, + outputs={"output": outputs}, steps=len(iterator_list_value), metadata={"total_tokens": graph_engine.graph_runtime_state.total_tokens}, error=event.error, @@ -429,9 +443,11 @@ class IterationNode(BaseNode[IterationNodeData]): ) ) return - else: - event = cast(InNodeEvent, event) - metadata_event = self._handle_event_metadata(event, current_index, parallel_mode_run_id) + elif isinstance(event, InNodeEvent): + # event = cast(InNodeEvent, event) + metadata_event = self._handle_event_metadata( + event=event, iter_run_index=current_index, parallel_mode_run_id=parallel_mode_run_id + ) if isinstance(event, NodeRunFailedEvent): if self.node_data.error_handle_mode == ErrorHandleMode.CONTINUE_ON_ERROR: yield NodeInIterationFailedEvent( @@ -513,7 +529,7 @@ class IterationNode(BaseNode[IterationNodeData]): iteration_node_data=self.node_data, index=next_index, parallel_mode_run_id=parallel_mode_run_id, - pre_iteration_output=jsonable_encoder(current_iteration_output) if current_iteration_output else None, + pre_iteration_output=current_iteration_output or None, duration=duration, ) @@ -551,7 +567,7 @@ class IterationNode(BaseNode[IterationNodeData]): index: int, item: Any, iter_run_map: dict[str, float], - ) -> Generator[NodeEvent | InNodeEvent, None, None]: + ): """ run single iteration in parallel mode """ From cc8feaa483eb7b40d9531c828e3c8133ed2671ec Mon Sep 17 00:00:00 2001 From: Hash Brown Date: Sat, 7 Dec 2024 22:26:28 +0800 Subject: [PATCH 05/21] style: EmojiPicker component top padding (#11452) --- web/app/components/base/app-icon-picker/index.tsx | 4 +--- web/app/components/base/emoji-picker/Inner.tsx | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/web/app/components/base/app-icon-picker/index.tsx b/web/app/components/base/app-icon-picker/index.tsx index 8a10d28653..14c9802c77 100644 --- a/web/app/components/base/app-icon-picker/index.tsx +++ b/web/app/components/base/app-icon-picker/index.tsx @@ -127,9 +127,7 @@ const AppIconPicker: FC = ({ } - - - + diff --git a/web/app/components/base/emoji-picker/Inner.tsx b/web/app/components/base/emoji-picker/Inner.tsx index 36c146a2a0..5db223e3f4 100644 --- a/web/app/components/base/emoji-picker/Inner.tsx +++ b/web/app/components/base/emoji-picker/Inner.tsx @@ -68,7 +68,7 @@ const EmojiPickerInner: FC = ({ }, [onSelect, selectedEmoji, selectedBackground]) return
-
+
} - +
diff --git a/web/app/components/base/app-icon-picker/utils.ts b/web/app/components/base/app-icon-picker/utils.ts index 99154d56da..f63b75eaa1 100644 --- a/web/app/components/base/app-icon-picker/utils.ts +++ b/web/app/components/base/app-icon-picker/utils.ts @@ -116,12 +116,12 @@ export default async function getCroppedImg( }) } -export function checkIsAnimatedImage(file) { +export function checkIsAnimatedImage(file: File): Promise { return new Promise((resolve, reject) => { const fileReader = new FileReader() fileReader.onload = function (e) { - const arr = new Uint8Array(e.target.result) + const arr = new Uint8Array(e.target?.result as ArrayBuffer) // Check file extension const fileName = file.name.toLowerCase() @@ -148,7 +148,7 @@ export function checkIsAnimatedImage(file) { } // Function to check for WebP signature -function isWebP(arr) { +function isWebP(arr: Uint8Array) { return ( arr[0] === 0x52 && arr[1] === 0x49 && arr[2] === 0x46 && arr[3] === 0x46 && arr[8] === 0x57 && arr[9] === 0x45 && arr[10] === 0x42 && arr[11] === 0x50 @@ -156,7 +156,7 @@ function isWebP(arr) { } // Function to check if the WebP is animated (contains ANIM chunk) -function checkWebPAnimation(arr) { +function checkWebPAnimation(arr: Uint8Array) { // Search for the ANIM chunk in WebP to determine if it's animated for (let i = 12; i < arr.length - 4; i++) { if (arr[i] === 0x41 && arr[i + 1] === 0x4E && arr[i + 2] === 0x49 && arr[i + 3] === 0x4D) From 4d7cfd0de510f98da4753a6ad24293ca1647226c Mon Sep 17 00:00:00 2001 From: Kazuki Takamatsu Date: Sun, 8 Dec 2024 09:44:49 +0900 Subject: [PATCH 10/21] Fix model provider of vertex ai (#11437) --- .../model_runtime/model_providers/vertex_ai/llm/llm.py | 10 ++++++---- .../vertex_ai/text_embedding/text_embedding.py | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/api/core/model_runtime/model_providers/vertex_ai/llm/llm.py b/api/core/model_runtime/model_providers/vertex_ai/llm/llm.py index 1469de6055..934195cc3d 100644 --- a/api/core/model_runtime/model_providers/vertex_ai/llm/llm.py +++ b/api/core/model_runtime/model_providers/vertex_ai/llm/llm.py @@ -104,13 +104,14 @@ class VertexAiLargeLanguageModel(LargeLanguageModel): """ # use Anthropic official SDK references # - https://github.com/anthropics/anthropic-sdk-python - service_account_info = json.loads(base64.b64decode(credentials["vertex_service_account_key"])) + service_account_key = credentials.get("vertex_service_account_key", "") project_id = credentials["vertex_project_id"] SCOPES = ["https://www.googleapis.com/auth/cloud-platform"] token = "" # get access token from service account credential - if service_account_info: + if service_account_key: + service_account_info = json.loads(base64.b64decode(service_account_key)) credentials = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES) request = google.auth.transport.requests.Request() credentials.refresh(request) @@ -478,10 +479,11 @@ class VertexAiLargeLanguageModel(LargeLanguageModel): if stop: config_kwargs["stop_sequences"] = stop - service_account_info = json.loads(base64.b64decode(credentials["vertex_service_account_key"])) + service_account_key = credentials.get("vertex_service_account_key", "") project_id = credentials["vertex_project_id"] location = credentials["vertex_location"] - if service_account_info: + if service_account_key: + service_account_info = json.loads(base64.b64decode(service_account_key)) service_accountSA = service_account.Credentials.from_service_account_info(service_account_info) aiplatform.init(credentials=service_accountSA, project=project_id, location=location) else: diff --git a/api/core/model_runtime/model_providers/vertex_ai/text_embedding/text_embedding.py b/api/core/model_runtime/model_providers/vertex_ai/text_embedding/text_embedding.py index 9cd0c78d99..eb54941e08 100644 --- a/api/core/model_runtime/model_providers/vertex_ai/text_embedding/text_embedding.py +++ b/api/core/model_runtime/model_providers/vertex_ai/text_embedding/text_embedding.py @@ -48,10 +48,11 @@ class VertexAiTextEmbeddingModel(_CommonVertexAi, TextEmbeddingModel): :param input_type: input type :return: embeddings result """ - service_account_info = json.loads(base64.b64decode(credentials["vertex_service_account_key"])) + service_account_key = credentials.get("vertex_service_account_key", "") project_id = credentials["vertex_project_id"] location = credentials["vertex_location"] - if service_account_info: + if service_account_key: + service_account_info = json.loads(base64.b64decode(service_account_key)) service_accountSA = service_account.Credentials.from_service_account_info(service_account_info) aiplatform.init(credentials=service_accountSA, project=project_id, location=location) else: @@ -100,10 +101,11 @@ class VertexAiTextEmbeddingModel(_CommonVertexAi, TextEmbeddingModel): :return: """ try: - service_account_info = json.loads(base64.b64decode(credentials["vertex_service_account_key"])) + service_account_key = credentials.get("vertex_service_account_key", "") project_id = credentials["vertex_project_id"] location = credentials["vertex_location"] - if service_account_info: + if service_account_key: + service_account_info = json.loads(base64.b64decode(service_account_key)) service_accountSA = service_account.Credentials.from_service_account_info(service_account_info) aiplatform.init(credentials=service_accountSA, project=project_id, location=location) else: From 7ff42b1b7a0651892c0d992ab541d6f1ca87ac27 Mon Sep 17 00:00:00 2001 From: yihong Date: Mon, 9 Dec 2024 09:04:11 +0800 Subject: [PATCH 11/21] fix: unit tests env will need clear too (#11445) Signed-off-by: yihong0618 --- api/tests/unit_tests/configs/test_dify_config.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/tests/unit_tests/configs/test_dify_config.py b/api/tests/unit_tests/configs/test_dify_config.py index 0eb310a51a..385eb08c36 100644 --- a/api/tests/unit_tests/configs/test_dify_config.py +++ b/api/tests/unit_tests/configs/test_dify_config.py @@ -37,7 +37,11 @@ def test_dify_config_undefined_entry(example_env_file): assert config["LOG_LEVEL"] == "INFO" +# NOTE: If there is a `.env` file in your Workspace, this test might not succeed as expected. +# This is due to `pymilvus` loading all the variables from the `.env` file into `os.environ`. def test_dify_config(example_env_file): + # clear system environment variables + os.environ.clear() # load dotenv file with pydantic-settings config = DifyConfig(_env_file=example_env_file) From 41d90c24082814f05b3aa55b0532785b648988a5 Mon Sep 17 00:00:00 2001 From: Trey Dong <1346650911@qq.com> Date: Mon, 9 Dec 2024 09:10:59 +0800 Subject: [PATCH 12/21] fix(api): throw error when notion block can not find (#11433) --- api/libs/oauth_data_source.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/libs/oauth_data_source.py b/api/libs/oauth_data_source.py index 48249e4a35..1d39abd8fa 100644 --- a/api/libs/oauth_data_source.py +++ b/api/libs/oauth_data_source.py @@ -253,6 +253,8 @@ class NotionOAuth(OAuthDataSource): } response = requests.get(url=f"{self._NOTION_BLOCK_SEARCH}/{block_id}", headers=headers) response_json = response.json() + if response.status_code != 200: + raise ValueError(f"Error fetching block parent page ID: {response_json.message}") parent = response_json["parent"] parent_type = parent["type"] if parent_type == "block_id": From a594e256aef573518dad9dcf58cd3990179f70b2 Mon Sep 17 00:00:00 2001 From: VoidIsVoid <343750470@qq.com> Date: Mon, 9 Dec 2024 09:33:18 +0800 Subject: [PATCH 13/21] remove mermail render cache (#11470) Co-authored-by: Gimling --- web/app/components/base/mermaid/index.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/web/app/components/base/mermaid/index.tsx b/web/app/components/base/mermaid/index.tsx index 88c5386fb7..776abf3dff 100644 --- a/web/app/components/base/mermaid/index.tsx +++ b/web/app/components/base/mermaid/index.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import mermaid from 'mermaid' import { usePrevious } from 'ahooks' -import CryptoJS from 'crypto-js' import { useTranslation } from 'react-i18next' import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' @@ -38,7 +37,6 @@ const Flowchart = React.forwardRef((props: { const [svgCode, setSvgCode] = useState(null) const [look, setLook] = useState<'classic' | 'handDrawn'>('classic') - const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`) const prevPrimitiveCode = usePrevious(props.PrimitiveCode) const [isLoading, setIsLoading] = useState(true) const timeRef = useRef() @@ -51,12 +49,10 @@ const Flowchart = React.forwardRef((props: { try { if (typeof window !== 'undefined' && mermaidAPI) { - const svgGraph = await mermaidAPI.render(chartId.current, PrimitiveCode) + const svgGraph = await mermaidAPI.render('flowchart', PrimitiveCode) const base64Svg: any = await svgToBase64(svgGraph.svg) setSvgCode(base64Svg) setIsLoading(false) - if (chartId.current && base64Svg) - localStorage.setItem(chartId.current, base64Svg) } } catch (error) { @@ -79,19 +75,11 @@ const Flowchart = React.forwardRef((props: { }, }) - localStorage.removeItem(chartId.current) renderFlowchart(props.PrimitiveCode) } }, [look]) useEffect(() => { - const cachedSvg: any = localStorage.getItem(chartId.current) - - if (cachedSvg) { - setSvgCode(cachedSvg) - setIsLoading(false) - return - } if (timeRef.current) clearTimeout(timeRef.current) From 6c60ecb2376731115ef56bcb18cdf5ece90ebbd4 Mon Sep 17 00:00:00 2001 From: "Charlie.Wei" Date: Mon, 9 Dec 2024 09:47:58 +0800 Subject: [PATCH 15/21] Refactor: Remove redundant style and simplify Mermaid component (#11472) --- web/app/components/base/mermaid/index.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/web/app/components/base/mermaid/index.tsx b/web/app/components/base/mermaid/index.tsx index 776abf3dff..bcc30ca939 100644 --- a/web/app/components/base/mermaid/index.tsx +++ b/web/app/components/base/mermaid/index.tsx @@ -13,12 +13,6 @@ mermaidAPI = null if (typeof window !== 'undefined') mermaidAPI = mermaid.mermaidAPI -const style = { - minWidth: '480px', - height: 'auto', - overflow: 'auto', -} - const svgToBase64 = (svgGraph: string) => { const svgBytes = new TextEncoder().encode(svgGraph) const blob = new Blob([svgBytes], { type: 'image/svg+xml;charset=utf-8' }) @@ -118,8 +112,8 @@ const Flowchart = React.forwardRef((props: {
{ svgCode - &&
setImagePreviewUrl(svgCode)}> - {svgCode && mermaid_chart} + &&
setImagePreviewUrl(svgCode)}> + {svgCode && mermaid_chart}
} {isLoading From 32f8a98cf8af5746efef0a4c8a9de16ce019dac3 Mon Sep 17 00:00:00 2001 From: Yi Xiao <54782454+YIXIAO0@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:06:47 +0800 Subject: [PATCH 16/21] feat: ifelse condition variable editable after selection (#11431) --- .../nodes/_base/components/variable-tag.tsx | 2 +- .../variable/var-reference-vars.tsx | 2 +- .../condition-list/condition-item.tsx | 23 +++++++- .../condition-list/condition-var-selector.tsx | 58 +++++++++++++++++++ .../if-else/components/condition-value.tsx | 2 +- 5 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx diff --git a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx index 6e1b1ed143..fc8c1ce9c9 100644 --- a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx @@ -72,7 +72,7 @@ const VariableTag = ({ {isEnv && } {isChatVar && }
{variableName} diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 9c2cba6e41..eb28279c0c 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -274,7 +274,7 @@ const VarReferenceVars: FC = ({ { !hideSearch && ( <> -
e.stopPropagation()}> +
e.stopPropagation()}> { if (isSubVariableKey) @@ -190,6 +193,17 @@ const ConditionItem = ({ onRemoveCondition?.(caseId, condition.id) }, [caseId, condition, conditionId, isSubVariableKey, onRemoveCondition, onRemoveSubVariableCondition]) + const handleVarChange = useCallback((valueSelector: ValueSelector, varItem: Var) => { + const newCondition = produce(condition, (draft) => { + draft.variable_selector = valueSelector + draft.varType = varItem.type + draft.value = '' + draft.comparison_operator = getOperators(varItem.type)[0] + }) + doUpdateCondition(newCondition) + setOpen(false) + }, [condition, doUpdateCondition]) + return (
) : ( - )} diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx new file mode 100644 index 0000000000..68a012d1a0 --- /dev/null +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx @@ -0,0 +1,58 @@ +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' +import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag' +import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' +import type { Node, NodeOutPutVar, ValueSelector, Var, VarType } from '@/app/components/workflow/types' + +type ConditionVarSelectorProps = { + open: boolean + onOpenChange: (open: boolean) => void + valueSelector: ValueSelector + varType: VarType + availableNodes: Node[] + nodesOutputVars: NodeOutPutVar[] + onChange: (valueSelector: ValueSelector, varItem: Var) => void +} + +const ConditionVarSelector = ({ + open, + onOpenChange, + valueSelector, + varType, + availableNodes, + nodesOutputVars, + onChange, +}: ConditionVarSelectorProps) => { + return ( + + onOpenChange(!open)}> +
+ +
+
+ +
+ +
+
+
+ ) +} + +export default ConditionVarSelector diff --git a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx index 182e38f71e..792064e6ed 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx @@ -73,7 +73,7 @@ const ConditionValue = ({
Date: Mon, 9 Dec 2024 03:18:28 +0000 Subject: [PATCH 17/21] Fix the Japanese translation for 'Detail' (#11476) --- web/i18n/ja-JP/app-log.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/i18n/ja-JP/app-log.ts b/web/i18n/ja-JP/app-log.ts index 1d77a17b06..233d70f8e3 100644 --- a/web/i18n/ja-JP/app-log.ts +++ b/web/i18n/ja-JP/app-log.ts @@ -80,7 +80,7 @@ const translation = { title: '会話ログ', workflowTitle: 'ログの詳細', fileListLabel: 'ファイルの詳細', - fileListDetail: 'ディテール', + fileListDetail: '詳細', }, promptLog: 'プロンプトログ', agentLog: 'エージェントログ', From 230fa3286bb15d36c4a1f8904dba223f4148be97 Mon Sep 17 00:00:00 2001 From: kurokobo Date: Mon, 9 Dec 2024 13:04:03 +0900 Subject: [PATCH 18/21] feat: add 'Open in Explore' link for each apps on studio (#11402) --- .../console/explore/installed_app.py | 12 ++++++++- web/app/(commonLayout)/apps/AppCard.tsx | 27 ++++++++++++++++--- .../components/app/app-publisher/index.tsx | 26 +++++++++++++++++- web/i18n/en-US/app.ts | 1 + web/i18n/en-US/workflow.ts | 1 + web/i18n/ja-JP/app.ts | 1 + web/i18n/ja-JP/workflow.ts | 1 + web/service/explore.ts | 4 +-- 8 files changed, 66 insertions(+), 7 deletions(-) diff --git a/api/controllers/console/explore/installed_app.py b/api/controllers/console/explore/installed_app.py index b60c4e176b..3de179164d 100644 --- a/api/controllers/console/explore/installed_app.py +++ b/api/controllers/console/explore/installed_app.py @@ -1,5 +1,6 @@ from datetime import UTC, datetime +from flask import request from flask_login import current_user from flask_restful import Resource, inputs, marshal_with, reqparse from sqlalchemy import and_ @@ -20,8 +21,17 @@ class InstalledAppsListApi(Resource): @account_initialization_required @marshal_with(installed_app_list_fields) def get(self): + app_id = request.args.get("app_id", default=None, type=str) current_tenant_id = current_user.current_tenant_id - installed_apps = db.session.query(InstalledApp).filter(InstalledApp.tenant_id == current_tenant_id).all() + + if app_id: + installed_apps = ( + db.session.query(InstalledApp) + .filter(and_(InstalledApp.tenant_id == current_tenant_id, InstalledApp.app_id == app_id)) + .all() + ) + else: + installed_apps = db.session.query(InstalledApp).filter(InstalledApp.tenant_id == current_tenant_id).all() current_user.role = TenantService.get_user_role(current_user, current_user.current_tenant) installed_apps = [ diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index 1ffb132cf8..4d1537d38d 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -9,6 +9,7 @@ import s from './style.module.css' import cn from '@/utils/classnames' import type { App } from '@/types/app' import Confirm from '@/app/components/base/confirm' +import Toast from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast' import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps' import DuplicateAppModal from '@/app/components/app/duplicate-modal' @@ -31,6 +32,7 @@ import TagSelector from '@/app/components/base/tag-management/selector' import type { EnvironmentVariable } from '@/app/components/workflow/types' import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal' import { fetchWorkflowDraft } from '@/service/workflow' +import { fetchInstalledAppList } from '@/service/explore' export type AppCardProps = { app: App @@ -209,6 +211,21 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { e.preventDefault() setShowConfirmDelete(true) } + const onClickInstalledApp = async (e: React.MouseEvent) => { + e.stopPropagation() + props.onClick?.() + e.preventDefault() + try { + const { installed_apps }: any = await fetchInstalledAppList(app.id) || {} + if (installed_apps?.length > 0) + window.open(`/explore/installed/${installed_apps[0].id}`, '_blank') + else + throw new Error('No app found in Explore') + } + catch (e: any) { + Toast.notify({ type: 'error', message: `${e.message || e}` }) + } + } return (
+
{ } popupClassName={ (app.mode === 'completion' || app.mode === 'chat') - ? '!w-[238px] translate-x-[-110px]' - : '' + ? '!w-[256px] translate-x-[-224px]' + : '!w-[160px] translate-x-[-128px]' } - className={'!w-[128px] h-fit !z-20'} + className={'h-fit !z-20'} />
diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 0558e29956..3ba35a7336 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -5,7 +5,8 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import dayjs from 'dayjs' -import { RiArrowDownSLine } from '@remixicon/react' +import { RiArrowDownSLine, RiPlanetLine } from '@remixicon/react' +import Toast from '../../base/toast' import type { ModelAndParameter } from '../configuration/debug/types' import SuggestedAction from './suggested-action' import PublishWithMultipleModel from './publish-with-multiple-model' @@ -15,6 +16,7 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' +import { fetchInstalledAppList } from '@/service/explore' import EmbeddedModal from '@/app/components/app/overview/embedded' import { useStore as useAppStore } from '@/app/components/app/store' import { useGetLanguage } from '@/context/i18n' @@ -105,6 +107,19 @@ const AppPublisher = ({ setPublished(false) }, [disabled, onToggle, open]) + const handleOpenInExplore = useCallback(async () => { + try { + const { installed_apps }: any = await fetchInstalledAppList(appDetail?.id) || {} + if (installed_apps?.length > 0) + window.open(`/explore/installed/${installed_apps[0].id}`, '_blank') + else + throw new Error('No app found in Explore') + } + catch (e: any) { + Toast.notify({ type: 'error', message: `${e.message || e}` }) + } + }, [appDetail?.id]) + const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false) return ( @@ -205,6 +220,15 @@ const AppPublisher = ({ {t('workflow.common.embedIntoSite')} )} + { + handleOpenInExplore() + }} + disabled={!publishedAt} + icon={} + > + {t('workflow.common.openInExplore')} + }>{t('workflow.common.accessAPIReference')} {appDetail?.mode === 'workflow' && ( => { return get(`/explore/apps/${id}`) } -export const fetchInstalledAppList = () => { - return get('/installed-apps') +export const fetchInstalledAppList = (app_id?: string | null) => { + return get(`/installed-apps${app_id ? `?app_id=${app_id}` : ''}`) } export const installApp = (id: string) => { From 5c166b3f409f6e56b45aeffc7b058278de21e793 Mon Sep 17 00:00:00 2001 From: zhaobingshuang <1475195565@qq.com> Date: Mon, 9 Dec 2024 14:38:02 +0800 Subject: [PATCH 19/21] fix: tags could not be saved when the Workflow Tool was created (#11481) Co-authored-by: zhaobs --- api/controllers/console/workspace/tool_providers.py | 1 + api/services/tools/workflow_tools_manage_service.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/api/controllers/console/workspace/tool_providers.py b/api/controllers/console/workspace/tool_providers.py index 9ecda2126d..2cd6dcda3b 100644 --- a/api/controllers/console/workspace/tool_providers.py +++ b/api/controllers/console/workspace/tool_providers.py @@ -368,6 +368,7 @@ class ToolWorkflowProviderCreateApi(Resource): description=args["description"], parameters=args["parameters"], privacy_policy=args["privacy_policy"], + labels=args["labels"], ) diff --git a/api/services/tools/workflow_tools_manage_service.py b/api/services/tools/workflow_tools_manage_service.py index 833881b668..318107bebb 100644 --- a/api/services/tools/workflow_tools_manage_service.py +++ b/api/services/tools/workflow_tools_manage_service.py @@ -81,6 +81,10 @@ class WorkflowToolManageService: db.session.add(workflow_tool_provider) db.session.commit() + if labels is not None: + ToolLabelManager.update_tool_labels( + ToolTransformService.workflow_provider_to_controller(workflow_tool_provider), labels + ) return {"result": "success"} @classmethod From c3c6a480594a843244672496f6b551760c481ad0 Mon Sep 17 00:00:00 2001 From: "suzuki.sh" Date: Mon, 9 Dec 2024 16:02:04 +0900 Subject: [PATCH 20/21] Fix the token count at the iteration node (#11235) Co-authored-by: -LAN- --- api/core/workflow/nodes/iteration/iteration_node.py | 5 ++++- web/app/(commonLayout)/apps/AppCard.tsx | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/api/core/workflow/nodes/iteration/iteration_node.py b/api/core/workflow/nodes/iteration/iteration_node.py index 74ec95deaa..75a2fb78a2 100644 --- a/api/core/workflow/nodes/iteration/iteration_node.py +++ b/api/core/workflow/nodes/iteration/iteration_node.py @@ -242,7 +242,10 @@ class IterationNode(BaseNode[IterationNodeData]): run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.SUCCEEDED, outputs={"output": outputs}, - metadata={NodeRunMetadataKey.ITERATION_DURATION_MAP: iter_run_map}, + metadata={ + NodeRunMetadataKey.ITERATION_DURATION_MAP: iter_run_map, + NodeRunMetadataKey.TOTAL_TOKENS: graph_engine.graph_runtime_state.total_tokens, + }, ) ) except IterationNodeError as e: diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index 4d1537d38d..fa5bcb596a 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -9,8 +9,7 @@ import s from './style.module.css' import cn from '@/utils/classnames' import type { App } from '@/types/app' import Confirm from '@/app/components/base/confirm' -import Toast from '@/app/components/base/toast' -import { ToastContext } from '@/app/components/base/toast' +import Toast, { ToastContext } from '@/app/components/base/toast' import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps' import DuplicateAppModal from '@/app/components/app/duplicate-modal' import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' From 537068cfde3e089cb8f047beecc034d8b13a3e71 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 9 Dec 2024 15:41:20 +0800 Subject: [PATCH 21/21] refactor(iteration_node): use Sequence and Mapping in parameters (#11483) Signed-off-by: -LAN- --- .../workflow/graph_engine/entities/event.py | 17 +++++----- .../nodes/iteration/iteration_node.py | 31 ++++++++++--------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/api/core/workflow/graph_engine/entities/event.py b/api/core/workflow/graph_engine/entities/event.py index 3736e632c3..cb73da3cd6 100644 --- a/api/core/workflow/graph_engine/entities/event.py +++ b/api/core/workflow/graph_engine/entities/event.py @@ -1,3 +1,4 @@ +from collections.abc import Mapping from datetime import datetime from typing import Any, Optional @@ -140,8 +141,8 @@ class BaseIterationEvent(GraphEngineEvent): class IterationRunStartedEvent(BaseIterationEvent): start_at: datetime = Field(..., description="start at") - inputs: Optional[dict[str, Any]] = None - metadata: Optional[dict[str, Any]] = None + inputs: Optional[Mapping[str, Any]] = None + metadata: Optional[Mapping[str, Any]] = None predecessor_node_id: Optional[str] = None @@ -153,18 +154,18 @@ class IterationRunNextEvent(BaseIterationEvent): class IterationRunSucceededEvent(BaseIterationEvent): start_at: datetime = Field(..., description="start at") - inputs: Optional[dict[str, Any]] = None - outputs: Optional[dict[str, Any]] = None - metadata: Optional[dict[str, Any]] = None + inputs: Optional[Mapping[str, Any]] = None + outputs: Optional[Mapping[str, Any]] = None + metadata: Optional[Mapping[str, Any]] = None steps: int = 0 iteration_duration_map: Optional[dict[str, float]] = None class IterationRunFailedEvent(BaseIterationEvent): start_at: datetime = Field(..., description="start at") - inputs: Optional[dict[str, Any]] = None - outputs: Optional[dict[str, Any]] = None - metadata: Optional[dict[str, Any]] = None + inputs: Optional[Mapping[str, Any]] = None + outputs: Optional[Mapping[str, Any]] = None + metadata: Optional[Mapping[str, Any]] = None steps: int = 0 error: str = Field(..., description="failed reason") diff --git a/api/core/workflow/nodes/iteration/iteration_node.py b/api/core/workflow/nodes/iteration/iteration_node.py index 75a2fb78a2..5b3853c5d6 100644 --- a/api/core/workflow/nodes/iteration/iteration_node.py +++ b/api/core/workflow/nodes/iteration/iteration_node.py @@ -167,17 +167,17 @@ class IterationNode(BaseNode[IterationNodeData]): for index, item in enumerate(iterator_list_value): future: Future = thread_pool.submit( self._run_single_iter_parallel, - current_app._get_current_object(), # type: ignore - q, - iterator_list_value, - inputs, - outputs, - start_at, - graph_engine, - iteration_graph, - index, - item, - iter_run_map, + flask_app=current_app._get_current_object(), # type: ignore + q=q, + iterator_list_value=iterator_list_value, + inputs=inputs, + outputs=outputs, + start_at=start_at, + graph_engine=graph_engine, + iteration_graph=iteration_graph, + index=index, + item=item, + iter_run_map=iter_run_map, ) future.add_done_callback(thread_pool.task_done_callback) futures.append(future) @@ -370,9 +370,9 @@ class IterationNode(BaseNode[IterationNodeData]): def _run_single_iter( self, *, - iterator_list_value: list[str], + iterator_list_value: Sequence[str], variable_pool: VariablePool, - inputs: dict[str, list], + inputs: Mapping[str, list], outputs: list, start_at: datetime, graph_engine: "GraphEngine", @@ -559,10 +559,11 @@ class IterationNode(BaseNode[IterationNodeData]): def _run_single_iter_parallel( self, + *, flask_app: Flask, q: Queue, - iterator_list_value: list[str], - inputs: dict[str, list], + iterator_list_value: Sequence[str], + inputs: Mapping[str, list], outputs: list, start_at: datetime, graph_engine: "GraphEngine",