From 451ccb778d8af63e05dcdd8187c47e90d3398abd Mon Sep 17 00:00:00 2001 From: Xiao Ley Date: Mon, 11 Nov 2024 11:31:47 +0800 Subject: [PATCH 01/59] feat(tools/podcast_generator): add support for setting openai base url with the podcast_generationor tool (#10517) --- .../podcast_generator/podcast_generator.yaml | 12 ++++++++++++ .../tools/podcast_audio_generator.py | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/api/core/tools/provider/builtin/podcast_generator/podcast_generator.yaml b/api/core/tools/provider/builtin/podcast_generator/podcast_generator.yaml index bd02b32020..d4edb17b28 100644 --- a/api/core/tools/provider/builtin/podcast_generator/podcast_generator.yaml +++ b/api/core/tools/provider/builtin/podcast_generator/podcast_generator.yaml @@ -32,3 +32,15 @@ credentials_for_provider: placeholder: en_US: Enter your TTS service API key zh_Hans: 输入您的 TTS 服务 API 密钥 + openai_base_url: + type: text-input + required: false + label: + en_US: OpenAI base URL + zh_Hans: OpenAI base URL + help: + en_US: Please input your OpenAI base URL + zh_Hans: 请输入你的 OpenAI base URL + placeholder: + en_US: Please input your OpenAI base URL + zh_Hans: 请输入你的 OpenAI base URL diff --git a/api/core/tools/provider/builtin/podcast_generator/tools/podcast_audio_generator.py b/api/core/tools/provider/builtin/podcast_generator/tools/podcast_audio_generator.py index 476e2d01e1..165e93956e 100644 --- a/api/core/tools/provider/builtin/podcast_generator/tools/podcast_audio_generator.py +++ b/api/core/tools/provider/builtin/podcast_generator/tools/podcast_audio_generator.py @@ -5,6 +5,7 @@ import warnings from typing import Any, Literal, Optional, Union import openai +from yarl import URL from core.tools.entities.tool_entities import ToolInvokeMessage from core.tools.errors import ToolParameterValidationError, ToolProviderCredentialValidationError @@ -53,15 +54,24 @@ class PodcastAudioGeneratorTool(BuiltinTool): if not host1_voice or not host2_voice: raise ToolParameterValidationError("Host voices are required") - # Get OpenAI API key from credentials + # Ensure runtime and credentials if not self.runtime or not self.runtime.credentials: raise ToolProviderCredentialValidationError("Tool runtime or credentials are missing") + + # Get OpenAI API key from credentials api_key = self.runtime.credentials.get("api_key") if not api_key: raise ToolProviderCredentialValidationError("OpenAI API key is missing") + # Get OpenAI base URL + openai_base_url = self.runtime.credentials.get("openai_base_url", None) + openai_base_url = str(URL(openai_base_url) / "v1") if openai_base_url else None + # Initialize OpenAI client - client = openai.OpenAI(api_key=api_key) + client = openai.OpenAI( + api_key=api_key, + base_url=openai_base_url, + ) # Create a thread pool max_workers = 5 From 0587e24fdb0f189f63e3982360a5df2648439fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Mon, 11 Nov 2024 11:32:41 +0800 Subject: [PATCH 02/59] feat: support tool search also can search toolProvider's name (#10518) --- .../components/tools/add-tool-modal/index.tsx | 18 +++++++++++------- .../workflow/block-selector/all-tools.tsx | 15 ++++++++++----- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/web/app/components/tools/add-tool-modal/index.tsx b/web/app/components/tools/add-tool-modal/index.tsx index e285b1a099..f79201256f 100644 --- a/web/app/components/tools/add-tool-modal/index.tsx +++ b/web/app/components/tools/add-tool-modal/index.tsx @@ -15,7 +15,6 @@ import Category from './category' import Tools from './tools' import cn from '@/utils/classnames' import I18n from '@/context/i18n' -import { getLanguage } from '@/i18n/language' import Drawer from '@/app/components/base/drawer' import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' @@ -44,13 +43,15 @@ const AddToolModal: FC = ({ }) => { const { t } = useTranslation() const { locale } = useContext(I18n) - const language = getLanguage(locale) const [currentType, setCurrentType] = useState('builtin') const [currentCategory, setCurrentCategory] = useState('') const [keywords, setKeywords] = useState('') const handleKeywordsChange = (value: string) => { setKeywords(value) } + const isMatchingKeywords = (text: string, keywords: string) => { + return text.toLowerCase().includes(keywords.toLowerCase()) + } const [toolList, setToolList] = useState([]) const [listLoading, setListLoading] = useState(true) const getAllTools = async () => { @@ -82,13 +83,16 @@ const AddToolModal: FC = ({ else return toolWithProvider.labels.includes(currentCategory) }).filter((toolWithProvider) => { - return toolWithProvider.tools.some((tool) => { - return Object.values(tool.label).some((label) => { - return label.toLowerCase().includes(keywords.toLowerCase()) + return ( + isMatchingKeywords(toolWithProvider.name, keywords) + || toolWithProvider.tools.some((tool) => { + return Object.values(tool.label).some((label) => { + return isMatchingKeywords(label, keywords) + }) }) - }) + ) }) - }, [currentType, currentCategory, toolList, keywords, language]) + }, [currentType, currentCategory, toolList, keywords]) const { modelConfig, diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 8925649226..dc15313216 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -11,7 +11,6 @@ import { ToolTypeEnum } from './types' import Tools from './tools' import { useToolTabs } from './hooks' import cn from '@/utils/classnames' -import { useGetLanguage } from '@/context/i18n' type AllToolsProps = { searchText: string @@ -21,13 +20,16 @@ const AllTools = ({ searchText, onSelect, }: AllToolsProps) => { - const language = useGetLanguage() const tabs = useToolTabs() const [activeTab, setActiveTab] = useState(ToolTypeEnum.All) const buildInTools = useStore(s => s.buildInTools) const customTools = useStore(s => s.customTools) const workflowTools = useStore(s => s.workflowTools) + const isMatchingKeywords = (text: string, keywords: string) => { + return text.toLowerCase().includes(keywords.toLowerCase()) + } + const tools = useMemo(() => { let mergedTools: ToolWithProvider[] = [] if (activeTab === ToolTypeEnum.All) @@ -40,11 +42,14 @@ const AllTools = ({ mergedTools = workflowTools return mergedTools.filter((toolWithProvider) => { - return toolWithProvider.tools.some((tool) => { - return tool.label[language].toLowerCase().includes(searchText.toLowerCase()) + return isMatchingKeywords(toolWithProvider.name, searchText) + || toolWithProvider.tools.some((tool) => { + return Object.values(tool.label).some((label) => { + return isMatchingKeywords(label, searchText) + }) }) }) - }, [activeTab, buildInTools, customTools, workflowTools, searchText, language]) + }, [activeTab, buildInTools, customTools, workflowTools, searchText]) return (
From 55edd5047e6fcbc9bb56a4ea055fcce090f3eb5d Mon Sep 17 00:00:00 2001 From: "Charlie.Wei" Date: Mon, 11 Nov 2024 11:52:32 +0800 Subject: [PATCH 03/59] Support for incoming value modification (#10525) --- .../components/base/markdown-blocks/form.tsx | 71 +++++++++++-------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/web/app/components/base/markdown-blocks/form.tsx b/web/app/components/base/markdown-blocks/form.tsx index f87f2dcd91..7ce3e82b1d 100644 --- a/web/app/components/base/markdown-blocks/form.tsx +++ b/web/app/components/base/markdown-blocks/form.tsx @@ -1,3 +1,4 @@ +import React, { useEffect, useState } from 'react' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Textarea from '@/app/components/base/textarea' @@ -32,20 +33,31 @@ const MarkdownForm = ({ node }: any) => { // const { onSend } = useChatContext() - const getFormValues = (children: any) => { - const formValues: { [key: string]: any } = {} - children.forEach((child: any) => { - if (child.tagName === SUPPORTED_TAGS.INPUT) - formValues[child.properties.name] = child.properties.value - if (child.tagName === SUPPORTED_TAGS.TEXTAREA) - formValues[child.properties.name] = child.properties.value + const [formValues, setFormValues] = useState<{ [key: string]: any }>({}) + + useEffect(() => { + const initialValues: { [key: string]: any } = {} + node.children.forEach((child: any) => { + if ([SUPPORTED_TAGS.INPUT, SUPPORTED_TAGS.TEXTAREA].includes(child.tagName)) + initialValues[child.properties.name] = child.properties.value }) - return formValues + setFormValues(initialValues) + }, [node.children]) + + const getFormValues = (children: any) => { + const values: { [key: string]: any } = {} + children.forEach((child: any) => { + if ([SUPPORTED_TAGS.INPUT, SUPPORTED_TAGS.TEXTAREA].includes(child.tagName)) + values[child.properties.name] = formValues[child.properties.name] + }) + return values } + const onSubmit = (e: any) => { e.preventDefault() const format = node.properties.dataFormat || DATA_FORMAT.TEXT const result = getFormValues(node.children) + if (format === DATA_FORMAT.JSON) { onSend?.(JSON.stringify(result)) } @@ -77,25 +89,22 @@ const MarkdownForm = ({ node }: any) => { ) } - if (child.tagName === SUPPORTED_TAGS.INPUT) { - if (Object.values(SUPPORTED_TYPES).includes(child.properties.type)) { - return ( - { - e.preventDefault() - child.properties.value = e.target.value - }} - /> - ) - } - else { - return

Unsupported input type: {child.properties.type}

- } + if (child.tagName === SUPPORTED_TAGS.INPUT && Object.values(SUPPORTED_TYPES).includes(child.properties.type)) { + return ( + { + setFormValues(prevValues => ({ + ...prevValues, + [child.properties.name]: e.target.value, + })) + }} + /> + ) } if (child.tagName === SUPPORTED_TAGS.TEXTAREA) { return ( @@ -103,10 +112,12 @@ const MarkdownForm = ({ node }: any) => { key={index} name={child.properties.name} placeholder={child.properties.placeholder} - value={child.properties.value} + value={formValues[child.properties.name]} onChange={(e) => { - e.preventDefault() - child.properties.value = e.target.value + setFormValues(prevValues => ({ + ...prevValues, + [child.properties.name]: e.target.value, + })) }} /> ) From fbee41f8c7d359c9dd40063a3c483a4bcb5c7b44 Mon Sep 17 00:00:00 2001 From: "Charlie.Wei" Date: Mon, 11 Nov 2024 12:10:21 +0800 Subject: [PATCH 04/59] The list action node adds methods to extract specific list objects (#10421) Co-authored-by: luowei Co-authored-by: crazywoola <427733928@qq.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> --- .../workflow/nodes/list_operator/entities.py | 6 +++ api/core/workflow/nodes/list_operator/node.py | 14 +++++ .../core/workflow/nodes/test_list_operator.py | 10 +++- .../components/extract-input.tsx | 51 +++++++++++++++++++ .../workflow/nodes/list-operator/default.ts | 4 ++ .../workflow/nodes/list-operator/panel.tsx | 44 +++++++++++++--- .../workflow/nodes/list-operator/types.ts | 4 ++ .../nodes/list-operator/use-config.ts | 18 +++++++ web/i18n/en-US/workflow.ts | 2 + web/i18n/zh-Hans/workflow.ts | 2 + 10 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 web/app/components/workflow/nodes/list-operator/components/extract-input.tsx diff --git a/api/core/workflow/nodes/list_operator/entities.py b/api/core/workflow/nodes/list_operator/entities.py index 79cef1c27a..6a27de40fd 100644 --- a/api/core/workflow/nodes/list_operator/entities.py +++ b/api/core/workflow/nodes/list_operator/entities.py @@ -49,8 +49,14 @@ class Limit(BaseModel): size: int = -1 +class ExtractConfig(BaseModel): + enabled: bool = False + serial: str = "1" + + class ListOperatorNodeData(BaseNodeData): variable: Sequence[str] = Field(default_factory=list) filter_by: FilterBy order_by: OrderBy limit: Limit + extract_by: ExtractConfig diff --git a/api/core/workflow/nodes/list_operator/node.py b/api/core/workflow/nodes/list_operator/node.py index 49e7ca85fd..79066cece4 100644 --- a/api/core/workflow/nodes/list_operator/node.py +++ b/api/core/workflow/nodes/list_operator/node.py @@ -58,6 +58,10 @@ class ListOperatorNode(BaseNode[ListOperatorNodeData]): if self.node_data.filter_by.enabled: variable = self._apply_filter(variable) + # Extract + if self.node_data.extract_by.enabled: + variable = self._extract_slice(variable) + # Order if self.node_data.order_by.enabled: variable = self._apply_order(variable) @@ -140,6 +144,16 @@ class ListOperatorNode(BaseNode[ListOperatorNodeData]): result = variable.value[: self.node_data.limit.size] return variable.model_copy(update={"value": result}) + def _extract_slice( + self, variable: Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment] + ) -> Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment]: + value = int(self.graph_runtime_state.variable_pool.convert_template(self.node_data.extract_by.serial).text) - 1 + if len(variable.value) > int(value): + result = variable.value[value] + else: + result = "" + return variable.model_copy(update={"value": [result]}) + def _get_file_extract_number_func(*, key: str) -> Callable[[File], int]: match key: diff --git a/api/tests/unit_tests/core/workflow/nodes/test_list_operator.py b/api/tests/unit_tests/core/workflow/nodes/test_list_operator.py index 0f5c8bf51b..d20dfc5b31 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_list_operator.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_list_operator.py @@ -4,7 +4,14 @@ import pytest from core.file import File, FileTransferMethod, FileType from core.variables import ArrayFileSegment -from core.workflow.nodes.list_operator.entities import FilterBy, FilterCondition, Limit, ListOperatorNodeData, OrderBy +from core.workflow.nodes.list_operator.entities import ( + ExtractConfig, + FilterBy, + FilterCondition, + Limit, + ListOperatorNodeData, + OrderBy, +) from core.workflow.nodes.list_operator.exc import InvalidKeyError from core.workflow.nodes.list_operator.node import ListOperatorNode, _get_file_extract_string_func from models.workflow import WorkflowNodeExecutionStatus @@ -22,6 +29,7 @@ def list_operator_node(): ), "order_by": OrderBy(enabled=False, value="asc"), "limit": Limit(enabled=False, size=0), + "extract_by": ExtractConfig(enabled=False, serial="1"), "title": "Test Title", } node_data = ListOperatorNodeData(**config) diff --git a/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx b/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx new file mode 100644 index 0000000000..2c5b8467fb --- /dev/null +++ b/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx @@ -0,0 +1,51 @@ +'use client' +import type { FC } from 'react' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { VarType } from '../../../types' +import type { Var } from '../../../types' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import cn from '@/utils/classnames' +import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' + +type Props = { + nodeId: string + readOnly: boolean + value: string + onChange: (value: string) => void +} + +const ExtractInput: FC = ({ + nodeId, + readOnly, + value, + onChange, +}) => { + const { t } = useTranslation() + + const [isFocus, setIsFocus] = useState(false) + const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { + onlyLeafNodeVar: false, + filterVar: (varPayload: Var) => { + return [VarType.number].includes(varPayload.type) + }, + }) + + return ( +
+ +
+ ) +} +export default React.memo(ExtractInput) diff --git a/web/app/components/workflow/nodes/list-operator/default.ts b/web/app/components/workflow/nodes/list-operator/default.ts index a7d411420c..fe8773a914 100644 --- a/web/app/components/workflow/nodes/list-operator/default.ts +++ b/web/app/components/workflow/nodes/list-operator/default.ts @@ -12,6 +12,10 @@ const nodeDefault: NodeDefault = { enabled: false, conditions: [], }, + extract_by: { + enabled: false, + serial: '1', + }, order_by: { enabled: false, key: '', diff --git a/web/app/components/workflow/nodes/list-operator/panel.tsx b/web/app/components/workflow/nodes/list-operator/panel.tsx index a6b53196d9..f6e9213bbf 100644 --- a/web/app/components/workflow/nodes/list-operator/panel.tsx +++ b/web/app/components/workflow/nodes/list-operator/panel.tsx @@ -13,6 +13,7 @@ import FilterCondition from './components/filter-condition' import Field from '@/app/components/workflow/nodes/_base/components/field' import { type NodePanelProps } from '@/app/components/workflow/types' import Switch from '@/app/components/base/switch' +import ExtractInput from '@/app/components/workflow/nodes/list-operator/components/extract-input' const i18nPrefix = 'workflow.nodes.listFilter' @@ -32,6 +33,8 @@ const Panel: FC> = ({ filterVar, handleFilterEnabledChange, handleFilterChange, + handleExtractsEnabledChange, + handleExtractsChange, handleLimitChange, handleOrderByEnabledChange, handleOrderByKeyChange, @@ -79,6 +82,41 @@ const Panel: FC> = ({ : null} + + } + > + {inputs.extract_by?.enabled + ? ( +
+ {hasSubVariable && ( +
+ +
+ )} +
+ ) + : null} +
+ + + > = ({ : null} -
-
<> diff --git a/web/app/components/workflow/nodes/list-operator/types.ts b/web/app/components/workflow/nodes/list-operator/types.ts index dcd71b6956..770590329a 100644 --- a/web/app/components/workflow/nodes/list-operator/types.ts +++ b/web/app/components/workflow/nodes/list-operator/types.ts @@ -25,6 +25,10 @@ export type ListFilterNodeType = CommonNodeType & { enabled: boolean conditions: Condition[] } + extract_by: { + enabled: boolean + serial?: string + } order_by: { enabled: boolean key: ValueSelector | string diff --git a/web/app/components/workflow/nodes/list-operator/use-config.ts b/web/app/components/workflow/nodes/list-operator/use-config.ts index 694ce9d49a..00defe7a84 100644 --- a/web/app/components/workflow/nodes/list-operator/use-config.ts +++ b/web/app/components/workflow/nodes/list-operator/use-config.ts @@ -119,6 +119,22 @@ const useConfig = (id: string, payload: ListFilterNodeType) => { setInputs(newInputs) }, [inputs, setInputs]) + const handleExtractsEnabledChange = useCallback((enabled: boolean) => { + const newInputs = produce(inputs, (draft) => { + draft.extract_by.enabled = enabled + if (enabled) + draft.extract_by.serial = '1' + }) + setInputs(newInputs) + }, [inputs, setInputs]) + + const handleExtractsChange = useCallback((value: string) => { + const newInputs = produce(inputs, (draft) => { + draft.extract_by.serial = value + }) + setInputs(newInputs) + }, [inputs, setInputs]) + const handleOrderByEnabledChange = useCallback((enabled: boolean) => { const newInputs = produce(inputs, (draft) => { draft.order_by.enabled = enabled @@ -162,6 +178,8 @@ const useConfig = (id: string, payload: ListFilterNodeType) => { handleOrderByEnabledChange, handleOrderByKeyChange, handleOrderByTypeChange, + handleExtractsEnabledChange, + handleExtractsChange, } } diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 3c6ccd0a67..7bfad01f23 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -369,6 +369,7 @@ const translation = { inputVars: 'Input Variables', api: 'API', apiPlaceholder: 'Enter URL, type ‘/’ insert variable', + extractListPlaceholder: 'Enter list item index, type ‘/’ insert variable', notStartWithHttp: 'API should start with http:// or https://', key: 'Key', type: 'Type', @@ -605,6 +606,7 @@ const translation = { inputVar: 'Input Variable', filterCondition: 'Filter Condition', filterConditionKey: 'Filter Condition Key', + extractsCondition: 'Extract the N item', filterConditionComparisonOperator: 'Filter Condition Comparison Operator', filterConditionComparisonValue: 'Filter Condition value', selectVariableKeyPlaceholder: 'Select sub variable key', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 1229ba8c03..98f34672d3 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -369,6 +369,7 @@ const translation = { inputVars: '输入变量', api: 'API', apiPlaceholder: '输入 URL,输入变量时请键入‘/’', + extractListPlaceholder: '输入提取列表编号,输入变量时请键入‘/’', notStartWithHttp: 'API 应该以 http:// 或 https:// 开头', key: '键', type: '类型', @@ -608,6 +609,7 @@ const translation = { filterConditionComparisonOperator: '过滤条件比较操作符', filterConditionComparisonValue: '过滤条件比较值', selectVariableKeyPlaceholder: '选择子变量的 Key', + extractsCondition: '取第 N 项', limit: '取前 N 项', orderBy: '排序', asc: '升序', From b8b6cd409a430765aea145617c3ad338cd14317f Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 11 Nov 2024 13:10:39 +0800 Subject: [PATCH 05/59] refactor(code_executor): update input type annotations to use Mapping for better type safety (#10478) --- api/core/helper/code_executor/__init__.py | 3 +++ api/core/helper/code_executor/code_executor.py | 5 +++-- api/core/helper/code_executor/template_transformer.py | 8 +++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/api/core/helper/code_executor/__init__.py b/api/core/helper/code_executor/__init__.py index e69de29bb2..ec885c2218 100644 --- a/api/core/helper/code_executor/__init__.py +++ b/api/core/helper/code_executor/__init__.py @@ -0,0 +1,3 @@ +from .code_executor import CodeExecutor, CodeLanguage + +__all__ = ["CodeExecutor", "CodeLanguage"] diff --git a/api/core/helper/code_executor/code_executor.py b/api/core/helper/code_executor/code_executor.py index 4932284540..03c4b8d49d 100644 --- a/api/core/helper/code_executor/code_executor.py +++ b/api/core/helper/code_executor/code_executor.py @@ -1,7 +1,8 @@ import logging +from collections.abc import Mapping from enum import Enum from threading import Lock -from typing import Optional +from typing import Any, Optional from httpx import Timeout, post from pydantic import BaseModel @@ -117,7 +118,7 @@ class CodeExecutor: return response.data.stdout or "" @classmethod - def execute_workflow_code_template(cls, language: CodeLanguage, code: str, inputs: dict) -> dict: + def execute_workflow_code_template(cls, language: CodeLanguage, code: str, inputs: Mapping[str, Any]) -> dict: """ Execute code :param language: code language diff --git a/api/core/helper/code_executor/template_transformer.py b/api/core/helper/code_executor/template_transformer.py index 6f016f27bc..b7a07b21e1 100644 --- a/api/core/helper/code_executor/template_transformer.py +++ b/api/core/helper/code_executor/template_transformer.py @@ -2,6 +2,8 @@ import json import re from abc import ABC, abstractmethod from base64 import b64encode +from collections.abc import Mapping +from typing import Any class TemplateTransformer(ABC): @@ -10,7 +12,7 @@ class TemplateTransformer(ABC): _result_tag: str = "<>" @classmethod - def transform_caller(cls, code: str, inputs: dict) -> tuple[str, str]: + def transform_caller(cls, code: str, inputs: Mapping[str, Any]) -> tuple[str, str]: """ Transform code to python runner :param code: code @@ -48,13 +50,13 @@ class TemplateTransformer(ABC): pass @classmethod - def serialize_inputs(cls, inputs: dict) -> str: + def serialize_inputs(cls, inputs: Mapping[str, Any]) -> str: inputs_json_str = json.dumps(inputs, ensure_ascii=False).encode() input_base64_encoded = b64encode(inputs_json_str).decode("utf-8") return input_base64_encoded @classmethod - def assemble_runner_script(cls, code: str, inputs: dict) -> str: + def assemble_runner_script(cls, code: str, inputs: Mapping[str, Any]) -> str: # assemble runner script script = cls.get_runner_script() script = script.replace(cls._code_placeholder, code) From 0c1307b083c852533eafcbc89a42060a150cc887 Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:28:11 +0800 Subject: [PATCH 06/59] add jina rerank http timout parameter (#10476) --- api/core/model_runtime/model_providers/jina/rerank/rerank.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/core/model_runtime/model_providers/jina/rerank/rerank.py b/api/core/model_runtime/model_providers/jina/rerank/rerank.py index 0350207651..aacc8e75d3 100644 --- a/api/core/model_runtime/model_providers/jina/rerank/rerank.py +++ b/api/core/model_runtime/model_providers/jina/rerank/rerank.py @@ -55,6 +55,7 @@ class JinaRerankModel(RerankModel): base_url + "/rerank", json={"model": model, "query": query, "documents": docs, "top_n": top_n}, headers={"Authorization": f"Bearer {credentials.get('api_key')}"}, + timeout=20, ) response.raise_for_status() results = response.json() From f414d241c1a30777cda97072851f1343da5f1f56 Mon Sep 17 00:00:00 2001 From: Novice <857526207@qq.com> Date: Mon, 11 Nov 2024 14:47:52 +0800 Subject: [PATCH 07/59] Feat/iteration single run time (#10512) --- api/core/app/apps/workflow_app_runner.py | 1 + api/core/app/entities/queue_entities.py | 3 ++ api/core/app/entities/task_entities.py | 1 + .../task_pipeline/workflow_cycle_manage.py | 1 + api/core/workflow/entities/node_entities.py | 1 + .../workflow/graph_engine/entities/event.py | 2 + .../nodes/iteration/iteration_node.py | 22 +++++++- .../workflow/hooks/use-workflow-run.ts | 3 ++ .../components/workflow/nodes/_base/node.tsx | 2 +- .../workflow/panel/workflow-preview.tsx | 8 ++- web/app/components/workflow/run/index.tsx | 9 ++-- .../workflow/run/iteration-result-panel.tsx | 54 ++++++++++++++----- web/app/components/workflow/run/node.tsx | 6 +-- .../components/workflow/run/tracing-panel.tsx | 4 +- web/i18n/en-US/workflow.ts | 6 +-- web/types/workflow.ts | 7 +++ 16 files changed, 101 insertions(+), 29 deletions(-) diff --git a/api/core/app/apps/workflow_app_runner.py b/api/core/app/apps/workflow_app_runner.py index 9a01e8a253..2872390d46 100644 --- a/api/core/app/apps/workflow_app_runner.py +++ b/api/core/app/apps/workflow_app_runner.py @@ -361,6 +361,7 @@ class WorkflowBasedAppRunner(AppRunner): node_run_index=workflow_entry.graph_engine.graph_runtime_state.node_run_steps, output=event.pre_iteration_output, parallel_mode_run_id=event.parallel_mode_run_id, + duration=event.duration, ) ) elif isinstance(event, (IterationRunSucceededEvent | IterationRunFailedEvent)): diff --git a/api/core/app/entities/queue_entities.py b/api/core/app/entities/queue_entities.py index f1542ec5d8..69bc0d7f9e 100644 --- a/api/core/app/entities/queue_entities.py +++ b/api/core/app/entities/queue_entities.py @@ -111,6 +111,7 @@ class QueueIterationNextEvent(AppQueueEvent): """iteratoin run in parallel mode run id""" node_run_index: int output: Optional[Any] = None # output for the current iteration + duration: Optional[float] = None @field_validator("output", mode="before") @classmethod @@ -307,6 +308,8 @@ class QueueNodeSucceededEvent(AppQueueEvent): execution_metadata: Optional[dict[NodeRunMetadataKey, Any]] = None error: Optional[str] = None + """single iteration duration map""" + iteration_duration_map: Optional[dict[str, float]] = None class QueueNodeInIterationFailedEvent(AppQueueEvent): diff --git a/api/core/app/entities/task_entities.py b/api/core/app/entities/task_entities.py index 7e9aad54be..03cc6941a8 100644 --- a/api/core/app/entities/task_entities.py +++ b/api/core/app/entities/task_entities.py @@ -434,6 +434,7 @@ class IterationNodeNextStreamResponse(StreamResponse): parallel_id: Optional[str] = None parallel_start_node_id: Optional[str] = None parallel_mode_run_id: Optional[str] = None + duration: Optional[float] = None event: StreamEvent = StreamEvent.ITERATION_NEXT workflow_run_id: str diff --git a/api/core/app/task_pipeline/workflow_cycle_manage.py b/api/core/app/task_pipeline/workflow_cycle_manage.py index b89edf9079..042339969f 100644 --- a/api/core/app/task_pipeline/workflow_cycle_manage.py +++ b/api/core/app/task_pipeline/workflow_cycle_manage.py @@ -624,6 +624,7 @@ class WorkflowCycleManage: parallel_id=event.parallel_id, parallel_start_node_id=event.parallel_start_node_id, parallel_mode_run_id=event.parallel_mode_run_id, + duration=event.duration, ), ) diff --git a/api/core/workflow/entities/node_entities.py b/api/core/workflow/entities/node_entities.py index 7e10cddc71..a747266661 100644 --- a/api/core/workflow/entities/node_entities.py +++ b/api/core/workflow/entities/node_entities.py @@ -24,6 +24,7 @@ class NodeRunMetadataKey(str, Enum): PARENT_PARALLEL_ID = "parent_parallel_id" PARENT_PARALLEL_START_NODE_ID = "parent_parallel_start_node_id" PARALLEL_MODE_RUN_ID = "parallel_mode_run_id" + ITERATION_DURATION_MAP = "iteration_duration_map" # single iteration duration if iteration node runs class NodeRunResult(BaseModel): diff --git a/api/core/workflow/graph_engine/entities/event.py b/api/core/workflow/graph_engine/entities/event.py index bacea191dd..3736e632c3 100644 --- a/api/core/workflow/graph_engine/entities/event.py +++ b/api/core/workflow/graph_engine/entities/event.py @@ -148,6 +148,7 @@ class IterationRunStartedEvent(BaseIterationEvent): class IterationRunNextEvent(BaseIterationEvent): index: int = Field(..., description="index") pre_iteration_output: Optional[Any] = Field(None, description="pre iteration output") + duration: Optional[float] = Field(None, description="duration") class IterationRunSucceededEvent(BaseIterationEvent): @@ -156,6 +157,7 @@ class IterationRunSucceededEvent(BaseIterationEvent): outputs: Optional[dict[str, Any]] = None metadata: Optional[dict[str, Any]] = None steps: int = 0 + iteration_duration_map: Optional[dict[str, float]] = None class IterationRunFailedEvent(BaseIterationEvent): diff --git a/api/core/workflow/nodes/iteration/iteration_node.py b/api/core/workflow/nodes/iteration/iteration_node.py index e5863d771b..941ebde7a9 100644 --- a/api/core/workflow/nodes/iteration/iteration_node.py +++ b/api/core/workflow/nodes/iteration/iteration_node.py @@ -156,6 +156,7 @@ class IterationNode(BaseNode[IterationNodeData]): index=0, pre_iteration_output=None, ) + iter_run_map: dict[str, float] = {} outputs: list[Any] = [None] * len(iterator_list_value) try: if self.node_data.is_parallel: @@ -175,6 +176,7 @@ class IterationNode(BaseNode[IterationNodeData]): iteration_graph, index, item, + iter_run_map, ) future.add_done_callback(thread_pool.task_done_callback) futures.append(future) @@ -213,6 +215,7 @@ class IterationNode(BaseNode[IterationNodeData]): start_at, graph_engine, iteration_graph, + 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] @@ -230,7 +233,9 @@ class IterationNode(BaseNode[IterationNodeData]): yield RunCompletedEvent( run_result=NodeRunResult( - status=WorkflowNodeExecutionStatus.SUCCEEDED, outputs={"output": jsonable_encoder(outputs)} + status=WorkflowNodeExecutionStatus.SUCCEEDED, + outputs={"output": jsonable_encoder(outputs)}, + metadata={NodeRunMetadataKey.ITERATION_DURATION_MAP: iter_run_map}, ) ) except IterationNodeError as e: @@ -356,15 +361,19 @@ class IterationNode(BaseNode[IterationNodeData]): start_at: datetime, graph_engine: "GraphEngine", iteration_graph: Graph, + iter_run_map: dict[str, float], parallel_mode_run_id: Optional[str] = None, ) -> Generator[NodeEvent | InNodeEvent, None, None]: """ run single iteration """ + iter_start_at = datetime.now(timezone.utc).replace(tzinfo=None) + try: rst = graph_engine.run() # get current iteration index current_index = variable_pool.get([self.node_id, "index"]).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: @@ -431,6 +440,8 @@ class IterationNode(BaseNode[IterationNodeData]): variable_pool.add([self.node_id, "index"], next_index) if next_index < len(iterator_list_value): variable_pool.add([self.node_id, "item"], iterator_list_value[next_index]) + duration = (datetime.now(timezone.utc).replace(tzinfo=None) - iter_start_at).total_seconds() + iter_run_map[iteration_run_id] = duration yield IterationRunNextEvent( iteration_id=self.id, iteration_node_id=self.node_id, @@ -439,6 +450,7 @@ class IterationNode(BaseNode[IterationNodeData]): index=next_index, parallel_mode_run_id=parallel_mode_run_id, pre_iteration_output=None, + duration=duration, ) return elif self.node_data.error_handle_mode == ErrorHandleMode.REMOVE_ABNORMAL_OUTPUT: @@ -449,6 +461,8 @@ class IterationNode(BaseNode[IterationNodeData]): if next_index < len(iterator_list_value): variable_pool.add([self.node_id, "item"], iterator_list_value[next_index]) + duration = (datetime.now(timezone.utc).replace(tzinfo=None) - iter_start_at).total_seconds() + iter_run_map[iteration_run_id] = duration yield IterationRunNextEvent( iteration_id=self.id, iteration_node_id=self.node_id, @@ -457,6 +471,7 @@ class IterationNode(BaseNode[IterationNodeData]): index=next_index, parallel_mode_run_id=parallel_mode_run_id, pre_iteration_output=None, + duration=duration, ) return elif self.node_data.error_handle_mode == ErrorHandleMode.TERMINATED: @@ -485,6 +500,8 @@ class IterationNode(BaseNode[IterationNodeData]): if next_index < len(iterator_list_value): variable_pool.add([self.node_id, "item"], iterator_list_value[next_index]) + duration = (datetime.now(timezone.utc).replace(tzinfo=None) - iter_start_at).total_seconds() + iter_run_map[iteration_run_id] = duration yield IterationRunNextEvent( iteration_id=self.id, iteration_node_id=self.node_id, @@ -493,6 +510,7 @@ class IterationNode(BaseNode[IterationNodeData]): 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, + duration=duration, ) except IterationNodeError as e: @@ -528,6 +546,7 @@ class IterationNode(BaseNode[IterationNodeData]): iteration_graph: Graph, index: int, item: Any, + iter_run_map: dict[str, float], ) -> Generator[NodeEvent | InNodeEvent, None, None]: """ run single iteration in parallel mode @@ -546,6 +565,7 @@ class IterationNode(BaseNode[IterationNodeData]): start_at=start_at, graph_engine=graph_engine_copy, iteration_graph=iteration_graph, + iter_run_map=iter_run_map, parallel_mode_run_id=parallel_mode_run_id, ): q.put(event) diff --git a/web/app/components/workflow/hooks/use-workflow-run.ts b/web/app/components/workflow/hooks/use-workflow-run.ts index 26654ef71e..eab3535505 100644 --- a/web/app/components/workflow/hooks/use-workflow-run.ts +++ b/web/app/components/workflow/hooks/use-workflow-run.ts @@ -445,6 +445,7 @@ export const useWorkflowRun = () => { ...data, status: NodeRunningStatus.Running, details: [], + iterDurationMap: {}, } as any) })) @@ -496,6 +497,8 @@ export const useWorkflowRun = () => { setWorkflowRunningData(produce(workflowRunningData!, (draft) => { const iteration = draft.tracing!.find(trace => trace.node_id === data.node_id) if (iteration) { + if (iteration.iterDurationMap && data.duration) + iteration.iterDurationMap[data.parallel_mode_run_id ?? `${data.index - 1}`] = data.duration if (iteration.details!.length >= iteration.metadata.iterator_length!) return } diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index e864c419e2..c5b78c5c21 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -193,7 +193,7 @@ const BaseNode: FC = ({ { data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && (
- {data._iterationIndex}/{data._iterationLength} + {data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex}/{data._iterationLength}
) } diff --git a/web/app/components/workflow/panel/workflow-preview.tsx b/web/app/components/workflow/panel/workflow-preview.tsx index 361f9d6bf4..d560c0b2cb 100644 --- a/web/app/components/workflow/panel/workflow-preview.tsx +++ b/web/app/components/workflow/panel/workflow-preview.tsx @@ -28,7 +28,7 @@ import IterationResultPanel from '../run/iteration-result-panel' import InputsPanel from './inputs-panel' import cn from '@/utils/classnames' import Loading from '@/app/components/base/loading' -import type { NodeTracing } from '@/types/workflow' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' const WorkflowPreview = () => { const { t } = useTranslation() @@ -53,12 +53,14 @@ const WorkflowPreview = () => { }, [workflowRunningData]) const [iterationRunResult, setIterationRunResult] = useState([]) + const [iterDurationMap, setIterDurationMap] = useState({}) const [isShowIterationDetail, { setTrue: doShowIterationDetail, setFalse: doHideIterationDetail, }] = useBoolean(false) - const handleShowIterationDetail = useCallback((detail: NodeTracing[][]) => { + const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterationDurationMap: IterationDurationMap) => { + setIterDurationMap(iterationDurationMap) setIterationRunResult(detail) doShowIterationDetail() }, [doShowIterationDetail]) @@ -72,6 +74,7 @@ const WorkflowPreview = () => { list={iterationRunResult} onHide={doHideIterationDetail} onBack={doHideIterationDetail} + iterDurationMap={iterDurationMap} />
) @@ -94,6 +97,7 @@ const WorkflowPreview = () => { list={iterationRunResult} onHide={doHideIterationDetail} onBack={doHideIterationDetail} + iterDurationMap={iterDurationMap} /> ) : ( diff --git a/web/app/components/workflow/run/index.tsx b/web/app/components/workflow/run/index.tsx index 89db43fa35..5267cf257d 100644 --- a/web/app/components/workflow/run/index.tsx +++ b/web/app/components/workflow/run/index.tsx @@ -13,7 +13,7 @@ import cn from '@/utils/classnames' import { ToastContext } from '@/app/components/base/toast' import Loading from '@/app/components/base/loading' import { fetchRunDetail, fetchTracingList } from '@/service/log' -import type { NodeTracing } from '@/types/workflow' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' import type { WorkflowRunDetailResponse } from '@/models/log' import { useStore as useAppStore } from '@/app/components/app/store' @@ -172,15 +172,17 @@ const RunPanel: FC = ({ hideResult, activeTab = 'RESULT', runID, getRe }, [loading]) const [iterationRunResult, setIterationRunResult] = useState([]) + const [iterDurationMap, setIterDurationMap] = useState({}) const [isShowIterationDetail, { setTrue: doShowIterationDetail, setFalse: doHideIterationDetail, }] = useBoolean(false) - const handleShowIterationDetail = useCallback((detail: NodeTracing[][]) => { + const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => { setIterationRunResult(detail) doShowIterationDetail() - }, [doShowIterationDetail]) + setIterDurationMap(iterDurationMap) + }, [doShowIterationDetail, setIterationRunResult, setIterDurationMap]) if (isShowIterationDetail) { return ( @@ -189,6 +191,7 @@ const RunPanel: FC = ({ hideResult, activeTab = 'RESULT', runID, getRe list={iterationRunResult} onHide={doHideIterationDetail} onBack={doHideIterationDetail} + iterDurationMap={iterDurationMap} />
) diff --git a/web/app/components/workflow/run/iteration-result-panel.tsx b/web/app/components/workflow/run/iteration-result-panel.tsx index c4cd909f2e..b13eadec99 100644 --- a/web/app/components/workflow/run/iteration-result-panel.tsx +++ b/web/app/components/workflow/run/iteration-result-panel.tsx @@ -6,12 +6,14 @@ import { RiArrowRightSLine, RiCloseLine, RiErrorWarningLine, + RiLoader2Line, } from '@remixicon/react' import { ArrowNarrowLeft } from '../../base/icons/src/vender/line/arrows' +import { NodeRunningStatus } from '../types' import TracingPanel from './tracing-panel' import { Iteration } from '@/app/components/base/icons/src/vender/workflow' import cn from '@/utils/classnames' -import type { NodeTracing } from '@/types/workflow' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' const i18nPrefix = 'workflow.singleRun' type Props = { @@ -19,6 +21,7 @@ type Props = { onHide: () => void onBack: () => void noWrap?: boolean + iterDurationMap?: IterationDurationMap } const IterationResultPanel: FC = ({ @@ -26,6 +29,7 @@ const IterationResultPanel: FC = ({ onHide, onBack, noWrap, + iterDurationMap, }) => { const { t } = useTranslation() const [expandedIterations, setExpandedIterations] = useState>({}) @@ -36,6 +40,40 @@ const IterationResultPanel: FC = ({ [index]: !prev[index], })) }, []) + const countIterDuration = (iteration: NodeTracing[], iterDurationMap: IterationDurationMap): string => { + const IterRunIndex = iteration[0].execution_metadata.iteration_index as number + const iterRunId = iteration[0].execution_metadata.parallel_mode_run_id + const iterItem = iterDurationMap[iterRunId || IterRunIndex] + const duration = iterItem + return `${(duration && duration > 0.01) ? duration.toFixed(2) : 0.01}s` + } + const iterationStatusShow = (index: number, iteration: NodeTracing[], iterDurationMap?: IterationDurationMap) => { + const hasFailed = iteration.some(item => item.status === NodeRunningStatus.Failed) + const isRunning = iteration.some(item => item.status === NodeRunningStatus.Running) + const hasDurationMap = iterDurationMap && Object.keys(iterDurationMap).length !== 0 + + if (hasFailed) + return + + if (isRunning) + return + + return ( + <> + {hasDurationMap && ( +
+ {countIterDuration(iteration, iterDurationMap)} +
+ )} + + + ) + } const main = ( <> @@ -72,19 +110,7 @@ const IterationResultPanel: FC = ({ {t(`${i18nPrefix}.iteration`)} {index + 1} - { - iteration.some(item => item.status === 'failed') - ? ( - - ) - : (< RiArrowRightSLine className={ - cn( - 'w-4 h-4 text-text-tertiary transition-transform duration-200 flex-shrink-0', - expandedIterations[index] && 'transform rotate-90', - )} /> - ) - } - + {iterationStatusShow(index, iteration, iterDurationMap)} {expandedIterations[index] &&
void + onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void notShowIterationNav?: boolean justShowIterationNavArrow?: boolean } @@ -90,7 +90,7 @@ const NodePanel: FC = ({ const handleOnShowIterationDetail = (e: React.MouseEvent) => { e.stopPropagation() e.nativeEvent.stopImmediatePropagation() - onShowIterationDetail?.(nodeInfo.details || []) + onShowIterationDetail?.(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {}) } return (
diff --git a/web/app/components/workflow/run/tracing-panel.tsx b/web/app/components/workflow/run/tracing-panel.tsx index 613c10198d..57b3a5cf5f 100644 --- a/web/app/components/workflow/run/tracing-panel.tsx +++ b/web/app/components/workflow/run/tracing-panel.tsx @@ -16,11 +16,11 @@ import NodePanel from './node' import { BlockEnum, } from '@/app/components/workflow/types' -import type { NodeTracing } from '@/types/workflow' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' type TracingPanelProps = { list: NodeTracing[] - onShowIterationDetail?: (detail: NodeTracing[][]) => void + onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void className?: string hideNodeInfo?: boolean hideNodeProcessDetail?: boolean diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 7bfad01f23..b3fece702a 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -569,9 +569,9 @@ const translation = { MaxParallelismDesc: 'The maximum parallelism is used to control the number of tasks executed simultaneously in a single iteration.', errorResponseMethod: 'Error response method', ErrorMethod: { - operationTerminated: 'terminated', - continueOnError: 'continue on error', - removeAbnormalOutput: 'remove abnormal output', + operationTerminated: 'Terminated', + continueOnError: 'Continue on Error', + removeAbnormalOutput: 'Remove Abnormal Output', }, answerNodeWarningDesc: 'Parallel mode warning: Answer nodes, conversation variable assignments, and persistent read/write operations within iterations may cause exceptions.', }, diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 3c0675b605..34b08e878e 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -33,6 +33,7 @@ export type NodeTracing = { parent_parallel_id?: string parent_parallel_start_node_id?: string parallel_mode_run_id?: string + iteration_duration_map?: IterationDurationMap } metadata: { iterator_length: number @@ -44,6 +45,7 @@ export type NodeTracing = { name: string email: string } + iterDurationMap?: IterationDurationMap finished_at: number extras?: any expand?: boolean // for UI @@ -207,7 +209,10 @@ export type IterationNextResponse = { parallel_mode_run_id: string execution_metadata: { parallel_id?: string + iteration_index: number + parallel_mode_run_id?: string } + duration?: number } } @@ -323,3 +328,5 @@ export type ConversationVariableResponse = { total: number page: number } + +export type IterationDurationMap = Record From 508f84893fb0e30d1cddf9c2f12e07e077849cf8 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Mon, 11 Nov 2024 14:57:28 +0800 Subject: [PATCH 08/59] fix: workflow start node form optional value (#10529) --- web/app/components/base/chat/chat-with-history/hooks.tsx | 2 +- web/app/components/base/chat/embedded-chatbot/hooks.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index d4fa170e4c..a67cc3cd88 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -173,7 +173,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const conversationInputs: Record = {} inputsForms.forEach((item: any) => { - conversationInputs[item.variable] = item.default || '' + conversationInputs[item.variable] = item.default || null }) handleNewConversationInputsChange(conversationInputs) }, [handleNewConversationInputsChange, inputsForms]) diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 631d3b56bc..0a8bc0993f 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -159,7 +159,7 @@ export const useEmbeddedChatbot = () => { const conversationInputs: Record = {} inputsForms.forEach((item: any) => { - conversationInputs[item.variable] = item.default || '' + conversationInputs[item.variable] = item.default || null }) handleNewConversationInputsChange(conversationInputs) }, [handleNewConversationInputsChange, inputsForms]) From 9018ef30feabd0d95ab63db1c80631a9aa29b0ae Mon Sep 17 00:00:00 2001 From: Novice <857526207@qq.com> Date: Mon, 11 Nov 2024 15:02:33 +0800 Subject: [PATCH 09/59] chore: (dockerfile) upgrade perl version (#10534) --- api/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/Dockerfile b/api/Dockerfile index 51e2a10506..ed981e46d6 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -55,7 +55,7 @@ RUN apt-get update \ && echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list \ && apt-get update \ # For Security - && apt-get install -y --no-install-recommends expat=2.6.3-2 libldap-2.5-0=2.5.18+dfsg-3+b1 perl=5.40.0-6 libsqlite3-0=3.46.1-1 zlib1g=1:1.3.dfsg+really1.3.1-1+b1 \ + && apt-get install -y --no-install-recommends expat=2.6.3-2 libldap-2.5-0=2.5.18+dfsg-3+b1 perl=5.40.0-7 libsqlite3-0=3.46.1-1 zlib1g=1:1.3.dfsg+really1.3.1-1+b1 \ # install a chinese font to support the use of tools like matplotlib && apt-get install -y fonts-noto-cjk \ && apt-get autoremove -y \ From 867bf70f1a62463ff5fec38edd4c0f190503ea5b Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 11 Nov 2024 16:06:53 +0800 Subject: [PATCH 10/59] fix(model_runtime): ensure compatibility with O1 models by adjusting token parameters (#10537) --- api/core/model_runtime/model_providers/openai/llm/llm.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/core/model_runtime/model_providers/openai/llm/llm.py b/api/core/model_runtime/model_providers/openai/llm/llm.py index 922e5e1314..68317d7179 100644 --- a/api/core/model_runtime/model_providers/openai/llm/llm.py +++ b/api/core/model_runtime/model_providers/openai/llm/llm.py @@ -617,6 +617,10 @@ class OpenAILargeLanguageModel(_CommonOpenAI, LargeLanguageModel): # o1 compatibility block_as_stream = False if model.startswith("o1"): + if "max_tokens" in model_parameters: + model_parameters["max_completion_tokens"] = model_parameters["max_tokens"] + del model_parameters["max_tokens"] + if stream: block_as_stream = True stream = False From be33875199c4c292b757ec340f0ec19bd0f67a53 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 11 Nov 2024 16:23:11 +0800 Subject: [PATCH 11/59] fix(gitee_ai): update English description for clarity and accuracy (#10540) --- api/core/tools/provider/builtin/gitee_ai/gitee_ai.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/tools/provider/builtin/gitee_ai/gitee_ai.yaml b/api/core/tools/provider/builtin/gitee_ai/gitee_ai.yaml index 2e18f8a7fc..d0475665dd 100644 --- a/api/core/tools/provider/builtin/gitee_ai/gitee_ai.yaml +++ b/api/core/tools/provider/builtin/gitee_ai/gitee_ai.yaml @@ -5,7 +5,7 @@ identity: en_US: Gitee AI zh_Hans: Gitee AI description: - en_US: 快速体验大模型,领先探索 AI 开源世界 + en_US: Quickly experience large models and explore the leading AI open source world zh_Hans: 快速体验大模型,领先探索 AI 开源世界 icon: icon.svg tags: From 90087160c6bbb509bfe2010c7e7e184e09ef646a Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 11 Nov 2024 16:41:47 +0800 Subject: [PATCH 12/59] chore (vanna): update form parameter from 'form' to 'llm' in vanna.yaml (#10488) --- api/core/tools/provider/builtin/vanna/tools/vanna.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/tools/provider/builtin/vanna/tools/vanna.yaml b/api/core/tools/provider/builtin/vanna/tools/vanna.yaml index 12ca8a862e..3520ba5570 100644 --- a/api/core/tools/provider/builtin/vanna/tools/vanna.yaml +++ b/api/core/tools/provider/builtin/vanna/tools/vanna.yaml @@ -32,7 +32,7 @@ parameters: en_US: RAG Model for your database DDL zh_Hans: 存储数据库训练数据的RAG模型 llm_description: RAG Model for generating SQL - form: form + form: llm - name: db_type type: select required: true From a1543b7da053657041489f34540942a312f89ffa Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 11 Nov 2024 17:31:27 +0800 Subject: [PATCH 13/59] fix(extractor): temporary file (#10543) --- api/core/rag/extractor/word_extractor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/core/rag/extractor/word_extractor.py b/api/core/rag/extractor/word_extractor.py index b59e7f94fd..8e084ab4ff 100644 --- a/api/core/rag/extractor/word_extractor.py +++ b/api/core/rag/extractor/word_extractor.py @@ -50,9 +50,9 @@ class WordExtractor(BaseExtractor): self.web_path = self.file_path # TODO: use a better way to handle the file - with tempfile.NamedTemporaryFile(delete=False) as self.temp_file: - self.temp_file.write(r.content) - self.file_path = self.temp_file.name + self.temp_file = tempfile.NamedTemporaryFile() # noqa: SIM115 + self.temp_file.write(r.content) + self.file_path = self.temp_file.name elif not os.path.isfile(self.file_path): raise ValueError(f"File path {self.file_path} is not a valid file or url") From 4b45ef62ed7ac95bdc6c40724a30a1d4b8336781 Mon Sep 17 00:00:00 2001 From: Novice <857526207@qq.com> Date: Mon, 11 Nov 2024 17:34:48 +0800 Subject: [PATCH 14/59] fix: iteration invalid output selector doesn't throw an error (#10544) --- api/core/workflow/nodes/iteration/iteration_node.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/core/workflow/nodes/iteration/iteration_node.py b/api/core/workflow/nodes/iteration/iteration_node.py index 941ebde7a9..d5428f0286 100644 --- a/api/core/workflow/nodes/iteration/iteration_node.py +++ b/api/core/workflow/nodes/iteration/iteration_node.py @@ -489,7 +489,10 @@ class IterationNode(BaseNode[IterationNodeData]): ) yield metadata_event - current_iteration_output = variable_pool.get(self.node_data.output_selector).value + current_output_segment = variable_pool.get(self.node_data.output_selector) + if current_output_segment is None: + raise IterationNodeError("iteration output selector not found") + current_iteration_output = current_output_segment.value outputs[current_index] = current_iteration_output # remove all nodes outputs from variable pool for node_id in iteration_graph.node_ids: From 9550b884f71450ac380a71cdf13ab67816777e0f Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 11 Nov 2024 18:32:28 +0800 Subject: [PATCH 15/59] chore: update version to 0.11.1 across all configurations and Docker images (#10539) --- api/app.py | 4 ++++ api/configs/packaging/__init__.py | 2 +- docker-legacy/docker-compose.yaml | 6 +++--- docker/docker-compose.yaml | 6 +++--- web/package.json | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/api/app.py b/api/app.py index 60cd622ef4..ead60e98d7 100644 --- a/api/app.py +++ b/api/app.py @@ -1,4 +1,5 @@ import os +import sys from configs import dify_config @@ -29,6 +30,9 @@ from models import account, dataset, model, source, task, tool, tools, web # no # DO NOT REMOVE ABOVE +if sys.version_info[:2] == (3, 10): + print("Warning: Python 3.10 will not be supported in the next version.") + warnings.simplefilter("ignore", ResourceWarning) diff --git a/api/configs/packaging/__init__.py b/api/configs/packaging/__init__.py index b5cb1f06d9..65065efbc0 100644 --- a/api/configs/packaging/__init__.py +++ b/api/configs/packaging/__init__.py @@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings): CURRENT_VERSION: str = Field( description="Dify version", - default="0.11.0", + default="0.11.1", ) COMMIT_SHA: str = Field( diff --git a/docker-legacy/docker-compose.yaml b/docker-legacy/docker-compose.yaml index 90110f49a2..9c2a1fe980 100644 --- a/docker-legacy/docker-compose.yaml +++ b/docker-legacy/docker-compose.yaml @@ -2,7 +2,7 @@ version: '3' services: # API service api: - image: langgenius/dify-api:0.11.0 + image: langgenius/dify-api:0.11.1 restart: always environment: # Startup mode, 'api' starts the API server. @@ -227,7 +227,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:0.11.0 + image: langgenius/dify-api:0.11.1 restart: always environment: CONSOLE_WEB_URL: '' @@ -397,7 +397,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:0.11.0 + image: langgenius/dify-web:0.11.1 restart: always environment: # The base URL of console application api server, refers to the Console base URL of WEB service if console domain is diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index fcc0c56216..d9ff965473 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -280,7 +280,7 @@ x-shared-env: &shared-api-worker-env services: # API service api: - image: langgenius/dify-api:0.11.0 + image: langgenius/dify-api:0.11.1 restart: always environment: # Use the shared environment variables. @@ -300,7 +300,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:0.11.0 + image: langgenius/dify-api:0.11.1 restart: always environment: # Use the shared environment variables. @@ -319,7 +319,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:0.11.0 + image: langgenius/dify-web:0.11.1 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} diff --git a/web/package.json b/web/package.json index de01eb4d48..d863ba13d3 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "dify-web", - "version": "0.11.0", + "version": "0.11.1", "private": true, "engines": { "node": ">=18.17.0" From 570f10d91ce3df5a38ff4284fac99bd24bb3cf0c Mon Sep 17 00:00:00 2001 From: liuhaoran <75237518+liuhaoran1212@users.noreply.github.com> Date: Mon, 11 Nov 2024 21:43:37 +0800 Subject: [PATCH 16/59] fix issues:Image file not deleted when a doc is removed #9541 (#10465) Signed-off-by: root Co-authored-by: root --- api/core/tools/utils/web_reader_tool.py | 16 ++++++++++++++++ api/tasks/clean_dataset_task.py | 11 +++++++++++ api/tasks/clean_document_task.py | 11 +++++++++++ 3 files changed, 38 insertions(+) diff --git a/api/core/tools/utils/web_reader_tool.py b/api/core/tools/utils/web_reader_tool.py index 5807d61b94..3aae31e93a 100644 --- a/api/core/tools/utils/web_reader_tool.py +++ b/api/core/tools/utils/web_reader_tool.py @@ -356,3 +356,19 @@ def content_digest(element): digest.update(child.encode("utf-8")) digest = digest.hexdigest() return digest + + +def get_image_upload_file_ids(content): + pattern = r"!\[image\]\((http?://.*?(file-preview|image-preview))\)" + matches = re.findall(pattern, content) + image_upload_file_ids = [] + for match in matches: + if match[1] == "file-preview": + content_pattern = r"files/([^/]+)/file-preview" + else: + content_pattern = r"files/([^/]+)/image-preview" + content_match = re.search(content_pattern, match[0]) + if content_match: + image_upload_file_id = content_match.group(1) + image_upload_file_ids.append(image_upload_file_id) + return image_upload_file_ids diff --git a/api/tasks/clean_dataset_task.py b/api/tasks/clean_dataset_task.py index 3624903801..4d45df4d2a 100644 --- a/api/tasks/clean_dataset_task.py +++ b/api/tasks/clean_dataset_task.py @@ -5,6 +5,7 @@ import click from celery import shared_task from core.rag.index_processor.index_processor_factory import IndexProcessorFactory +from core.tools.utils.web_reader_tool import get_image_upload_file_ids from extensions.ext_database import db from extensions.ext_storage import storage from models.dataset import ( @@ -67,6 +68,16 @@ def clean_dataset_task( db.session.delete(document) for segment in segments: + image_upload_file_ids = get_image_upload_file_ids(segment.content) + for upload_file_id in image_upload_file_ids: + image_file = db.session.query(UploadFile).filter(UploadFile.id == upload_file_id).first() + try: + storage.delete(image_file.key) + except Exception: + logging.exception( + "Delete image_files failed when storage deleted, \ + image_upload_file_is: {}".format(upload_file_id) + ) db.session.delete(segment) db.session.query(DatasetProcessRule).filter(DatasetProcessRule.dataset_id == dataset_id).delete() diff --git a/api/tasks/clean_document_task.py b/api/tasks/clean_document_task.py index ae2855aa2e..54c89450c9 100644 --- a/api/tasks/clean_document_task.py +++ b/api/tasks/clean_document_task.py @@ -6,6 +6,7 @@ import click from celery import shared_task from core.rag.index_processor.index_processor_factory import IndexProcessorFactory +from core.tools.utils.web_reader_tool import get_image_upload_file_ids from extensions.ext_database import db from extensions.ext_storage import storage from models.dataset import Dataset, DocumentSegment @@ -40,6 +41,16 @@ def clean_document_task(document_id: str, dataset_id: str, doc_form: str, file_i index_processor.clean(dataset, index_node_ids) for segment in segments: + image_upload_file_ids = get_image_upload_file_ids(segment.content) + for upload_file_id in image_upload_file_ids: + image_file = db.session.query(UploadFile).filter(UploadFile.id == upload_file_id).first() + try: + storage.delete(image_file.key) + except Exception: + logging.exception( + "Delete image_files failed when storage deleted, \ + image_upload_file_is: {}".format(upload_file_id) + ) db.session.delete(segment) db.session.commit() From f19c18dc1412a9026ed8babacc71016309badf4f Mon Sep 17 00:00:00 2001 From: smyhw Date: Mon, 11 Nov 2024 21:50:32 +0800 Subject: [PATCH 17/59] Fixes `you have not added provider None` (#10501) --- api/core/tools/tool_manager.py | 3 ++- api/services/tools/api_tools_manage_service.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index bf2ad13620..d2723df7b2 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -555,6 +555,7 @@ class ToolManager: """ get tool provider """ + provider_name = provider provider: ApiToolProvider = ( db.session.query(ApiToolProvider) .filter( @@ -565,7 +566,7 @@ class ToolManager: ) if provider is None: - raise ValueError(f"you have not added provider {provider}") + raise ValueError(f"you have not added provider {provider_name}") try: credentials = json.loads(provider.credentials_str) or {} diff --git a/api/services/tools/api_tools_manage_service.py b/api/services/tools/api_tools_manage_service.py index 4a93891855..ed0cebf460 100644 --- a/api/services/tools/api_tools_manage_service.py +++ b/api/services/tools/api_tools_manage_service.py @@ -113,6 +113,8 @@ class ApiToolManageService: if schema_type not in [member.value for member in ApiProviderSchemaType]: raise ValueError(f"invalid schema type {schema}") + provider_name = provider_name.strip() + # check if the provider exists provider: ApiToolProvider = ( db.session.query(ApiToolProvider) @@ -203,6 +205,7 @@ class ApiToolManageService: """ list api tool provider tools """ + provider_name = provider provider: ApiToolProvider = ( db.session.query(ApiToolProvider) .filter( @@ -213,7 +216,7 @@ class ApiToolManageService: ) if provider is None: - raise ValueError(f"you have not added provider {provider}") + raise ValueError(f"you have not added provider {provider_name}") controller = ToolTransformService.api_provider_to_controller(db_provider=provider) labels = ToolLabelManager.get_tool_labels(controller) @@ -246,6 +249,8 @@ class ApiToolManageService: if schema_type not in [member.value for member in ApiProviderSchemaType]: raise ValueError(f"invalid schema type {schema}") + provider_name = provider_name.strip() + # check if the provider exists provider: ApiToolProvider = ( db.session.query(ApiToolProvider) From bd4a61addd5729af094b6ea7233b3018bf31d086 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 11 Nov 2024 23:32:40 +0800 Subject: [PATCH 18/59] fix: set default factory for extract_by in ListOperatorNodeData (#10561) --- api/core/workflow/nodes/list_operator/entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/workflow/nodes/list_operator/entities.py b/api/core/workflow/nodes/list_operator/entities.py index 6a27de40fd..75df784a92 100644 --- a/api/core/workflow/nodes/list_operator/entities.py +++ b/api/core/workflow/nodes/list_operator/entities.py @@ -59,4 +59,4 @@ class ListOperatorNodeData(BaseNodeData): filter_by: FilterBy order_by: OrderBy limit: Limit - extract_by: ExtractConfig + extract_by: ExtractConfig = Field(default_factory=ExtractConfig) From 16db2c4e573e4a8d24b70d15688b92f7c39581ae Mon Sep 17 00:00:00 2001 From: fdb02983rhy <91766386+fdb02983rhy@users.noreply.github.com> Date: Tue, 12 Nov 2024 00:53:12 +0900 Subject: [PATCH 19/59] Fix: Set Celery LOG_File only when available, always log to console (#10563) --- api/extensions/ext_celery.py | 6 +++++- api/extensions/ext_logging.py | 12 +++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/api/extensions/ext_celery.py b/api/extensions/ext_celery.py index c5de7395b8..42012eee8e 100644 --- a/api/extensions/ext_celery.py +++ b/api/extensions/ext_celery.py @@ -46,7 +46,6 @@ def init_app(app: Flask) -> Celery: broker_connection_retry_on_startup=True, worker_log_format=dify_config.LOG_FORMAT, worker_task_log_format=dify_config.LOG_FORMAT, - worker_logfile=dify_config.LOG_FILE, worker_hijack_root_logger=False, timezone=pytz.timezone(dify_config.LOG_TZ), ) @@ -56,6 +55,11 @@ def init_app(app: Flask) -> Celery: broker_use_ssl=ssl_options, # Add the SSL options to the broker configuration ) + if dify_config.LOG_FILE: + celery_app.conf.update( + worker_logfile=dify_config.LOG_FILE, + ) + celery_app.set_default() app.extensions["celery"] = celery_app diff --git a/api/extensions/ext_logging.py b/api/extensions/ext_logging.py index 56b1d6bd28..a15c73bd71 100644 --- a/api/extensions/ext_logging.py +++ b/api/extensions/ext_logging.py @@ -9,19 +9,21 @@ from configs import dify_config def init_app(app: Flask): - log_handlers = None + log_handlers = [] log_file = dify_config.LOG_FILE if log_file: log_dir = os.path.dirname(log_file) os.makedirs(log_dir, exist_ok=True) - log_handlers = [ + log_handlers.append( RotatingFileHandler( filename=log_file, maxBytes=dify_config.LOG_FILE_MAX_SIZE * 1024 * 1024, backupCount=dify_config.LOG_FILE_BACKUP_COUNT, - ), - logging.StreamHandler(sys.stdout), - ] + ) + ) + + # Always add StreamHandler to log to console + log_handlers.append(logging.StreamHandler(sys.stdout)) logging.basicConfig( level=dify_config.LOG_LEVEL, From e63c0e3cbb477c7ddf22b8b80a77459b067baffa Mon Sep 17 00:00:00 2001 From: Hiroshi Fujita Date: Tue, 12 Nov 2024 00:53:43 +0900 Subject: [PATCH 20/59] feat(settings): add chat color theme inverted toggle in settings modal (#10558) --- web/app/components/app/overview/settings/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/app/components/app/overview/settings/index.tsx b/web/app/components/app/overview/settings/index.tsx index a8ab456f43..e7cc4148ef 100644 --- a/web/app/components/app/overview/settings/index.tsx +++ b/web/app/components/app/overview/settings/index.tsx @@ -261,6 +261,10 @@ const SettingsModal: FC = ({ onChange={onChange('chatColorTheme')} placeholder='E.g #A020F0' /> +
+

{t(`${prefixSettings}.chatColorThemeInverted`)}

+ setInputInfo({ ...inputInfo, chatColorThemeInverted: v })}> +
} {systemFeatures.enable_web_sso_switch_component &&

{t(`${prefixSettings}.sso.label`)}

From b7238caea51753112ec8765399f240dafa34cd69 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 12 Nov 2024 00:00:27 +0800 Subject: [PATCH 21/59] chore(vanna): update form parameter from 'form' to 'llm' in vanna.yaml (#10548) --- api/core/tools/provider/builtin/vanna/tools/vanna.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/core/tools/provider/builtin/vanna/tools/vanna.yaml b/api/core/tools/provider/builtin/vanna/tools/vanna.yaml index 3520ba5570..309681321b 100644 --- a/api/core/tools/provider/builtin/vanna/tools/vanna.yaml +++ b/api/core/tools/provider/builtin/vanna/tools/vanna.yaml @@ -136,7 +136,7 @@ parameters: human_description: en_US: DDL statements for training data zh_Hans: 用于训练RAG Model的建表语句 - form: form + form: llm - name: question type: string required: false @@ -146,7 +146,7 @@ parameters: human_description: en_US: Question-SQL Pairs zh_Hans: Question-SQL中的问题 - form: form + form: llm - name: sql type: string required: false @@ -156,7 +156,7 @@ parameters: human_description: en_US: SQL queries to your training data zh_Hans: 用于训练RAG Model的SQL语句 - form: form + form: llm - name: memos type: string required: false @@ -166,7 +166,7 @@ parameters: human_description: en_US: Sometimes you may want to add documentation about your business terminology or definitions zh_Hans: 添加更多关于数据库的业务说明 - form: form + form: llm - name: enable_training type: boolean required: false From 16b9665033b38da4975f6e8cd4793a83a675b468 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Tue, 12 Nov 2024 00:08:04 +0800 Subject: [PATCH 22/59] refactor(api): improve handling of `tools` field and cleanup variable usage (#10553) --- api/core/tools/entities/api_entities.py | 9 +++++++-- api/services/tools/api_tools_manage_service.py | 15 +++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/api/core/tools/entities/api_entities.py b/api/core/tools/entities/api_entities.py index b1db559441..ddb1481276 100644 --- a/api/core/tools/entities/api_entities.py +++ b/api/core/tools/entities/api_entities.py @@ -1,6 +1,6 @@ from typing import Literal, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Field, field_validator from core.model_runtime.utils.encoders import jsonable_encoder from core.tools.entities.common_entities import I18nObject @@ -32,9 +32,14 @@ class UserToolProvider(BaseModel): original_credentials: Optional[dict] = None is_team_authorization: bool = False allow_delete: bool = True - tools: list[UserTool] | None = None + tools: list[UserTool] = Field(default_factory=list) labels: list[str] | None = None + @field_validator("tools", mode="before") + @classmethod + def convert_none_to_empty_list(cls, v): + return v if v is not None else [] + def to_dict(self) -> dict: # ------------- # overwrite tool parameter types for temp fix diff --git a/api/services/tools/api_tools_manage_service.py b/api/services/tools/api_tools_manage_service.py index ed0cebf460..b6b0143fac 100644 --- a/api/services/tools/api_tools_manage_service.py +++ b/api/services/tools/api_tools_manage_service.py @@ -116,7 +116,7 @@ class ApiToolManageService: provider_name = provider_name.strip() # check if the provider exists - provider: ApiToolProvider = ( + provider = ( db.session.query(ApiToolProvider) .filter( ApiToolProvider.tenant_id == tenant_id, @@ -201,16 +201,15 @@ class ApiToolManageService: return {"schema": schema} @staticmethod - def list_api_tool_provider_tools(user_id: str, tenant_id: str, provider: str) -> list[UserTool]: + def list_api_tool_provider_tools(user_id: str, tenant_id: str, provider_name: str) -> list[UserTool]: """ list api tool provider tools """ - provider_name = provider - provider: ApiToolProvider = ( + provider = ( db.session.query(ApiToolProvider) .filter( ApiToolProvider.tenant_id == tenant_id, - ApiToolProvider.name == provider, + ApiToolProvider.name == provider_name, ) .first() ) @@ -252,7 +251,7 @@ class ApiToolManageService: provider_name = provider_name.strip() # check if the provider exists - provider: ApiToolProvider = ( + provider = ( db.session.query(ApiToolProvider) .filter( ApiToolProvider.tenant_id == tenant_id, @@ -319,7 +318,7 @@ class ApiToolManageService: """ delete tool provider """ - provider: ApiToolProvider = ( + provider = ( db.session.query(ApiToolProvider) .filter( ApiToolProvider.tenant_id == tenant_id, @@ -369,7 +368,7 @@ class ApiToolManageService: if tool_bundle is None: raise ValueError(f"invalid tool name {tool_name}") - db_provider: ApiToolProvider = ( + db_provider = ( db.session.query(ApiToolProvider) .filter( ApiToolProvider.tenant_id == tenant_id, From e4d175780e2eab98fafcc82aa6a42f73b265cba2 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 12 Nov 2024 14:38:24 +0800 Subject: [PATCH 23/59] fix: retrieval setting validate (#10454) --- .../configuration/dataset-config/index.tsx | 6 +- .../params-config/config-content.tsx | 2 +- .../dataset-config/params-config/index.tsx | 6 +- .../components/app/configuration/index.tsx | 11 +- .../nodes/knowledge-retrieval/default.ts | 13 +- .../nodes/knowledge-retrieval/types.ts | 2 + .../nodes/knowledge-retrieval/use-config.ts | 24 +++- .../nodes/knowledge-retrieval/utils.ts | 115 ++++++++++++------ 8 files changed, 130 insertions(+), 49 deletions(-) diff --git a/web/app/components/app/configuration/dataset-config/index.tsx b/web/app/components/app/configuration/dataset-config/index.tsx index 0d9d575c1e..78b49f81d0 100644 --- a/web/app/components/app/configuration/dataset-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/index.tsx @@ -47,12 +47,16 @@ const DatasetConfig: FC = () => { const { currentModel: currentRerankModel, + currentProvider: currentRerankProvider, } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) const onRemove = (id: string) => { const filteredDataSets = dataSet.filter(item => item.id !== id) setDataSet(filteredDataSets) - const retrievalConfig = getMultipleRetrievalConfig(datasetConfigs as any, filteredDataSets, dataSet, !!currentRerankModel) + const retrievalConfig = getMultipleRetrievalConfig(datasetConfigs as any, filteredDataSets, dataSet, { + provider: currentRerankProvider?.provider, + model: currentRerankModel?.model, + }) setDatasetConfigs({ ...(datasetConfigs as any), ...retrievalConfig, diff --git a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx index 5bd748382e..dcb2b1a3fd 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx @@ -172,7 +172,7 @@ const ConfigContent: FC = ({ return false return datasetConfigs.reranking_enable - }, [canManuallyToggleRerank, datasetConfigs.reranking_enable]) + }, [canManuallyToggleRerank, datasetConfigs.reranking_enable, isRerankDefaultModelValid]) const handleDisabledSwitchClick = useCallback(() => { if (!currentRerankModel && !showRerankModel) diff --git a/web/app/components/app/configuration/dataset-config/params-config/index.tsx b/web/app/components/app/configuration/dataset-config/params-config/index.tsx index 94920fbd39..7f7a4799d1 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/index.tsx @@ -43,6 +43,7 @@ const ParamsConfig = ({ const { defaultModel: rerankDefaultModel, currentModel: isRerankDefaultModelValid, + currentProvider: rerankDefaultProvider, } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) const isValid = () => { @@ -91,7 +92,10 @@ const ParamsConfig = ({ reranking_mode: restConfigs.reranking_mode, weights: restConfigs.weights, reranking_enable: restConfigs.reranking_enable, - }, selectedDatasets, selectedDatasets, !!isRerankDefaultModelValid) + }, selectedDatasets, selectedDatasets, { + provider: rerankDefaultProvider?.provider, + model: isRerankDefaultModelValid?.model, + }) setTempDataSetConfigs({ ...retrievalConfig, diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index 2bb11a870c..b5b7e98d43 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -226,6 +226,7 @@ const Configuration: FC = () => { const [rerankSettingModalOpen, setRerankSettingModalOpen] = useState(false) const { currentModel: currentRerankModel, + currentProvider: currentRerankProvider, } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) const handleSelect = (data: DataSet[]) => { if (isEqual(data.map(item => item.id), dataSets.map(item => item.id))) { @@ -279,7 +280,10 @@ const Configuration: FC = () => { reranking_mode: restConfigs.reranking_mode, weights: restConfigs.weights, reranking_enable: restConfigs.reranking_enable, - }, newDatasets, dataSets, !!currentRerankModel) + }, newDatasets, dataSets, { + provider: currentRerankProvider?.provider, + model: currentRerankModel?.model, + }) setDatasetConfigs({ ...retrievalConfig, @@ -620,7 +624,10 @@ const Configuration: FC = () => { syncToPublishedConfig(config) setPublishedConfig(config) - const retrievalConfig = getMultipleRetrievalConfig(modelConfig.dataset_configs, datasets, datasets, !!currentRerankModel) + const retrievalConfig = getMultipleRetrievalConfig(modelConfig.dataset_configs, datasets, datasets, { + provider: currentRerankProvider?.provider, + model: currentRerankModel?.model, + }) setDatasetConfigs({ retrieval_model: RETRIEVE_TYPE.multiWay, ...modelConfig.dataset_configs, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts index 03591dd527..e902d29b96 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts @@ -1,7 +1,7 @@ import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import type { KnowledgeRetrievalNodeType } from './types' -import { RerankingModeEnum } from '@/models/datasets' +import { checkoutRerankModelConfigedInRetrievalSettings } from './utils' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants' import { DATASET_DEFAULT } from '@/config' import { RETRIEVE_TYPE } from '@/types/app' @@ -36,12 +36,17 @@ const nodeDefault: NodeDefault = { if (!errorMessages && (!payload.dataset_ids || payload.dataset_ids.length === 0)) errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.knowledgeRetrieval.knowledge`) }) - if (!errorMessages && payload.retrieval_mode === RETRIEVE_TYPE.multiWay && payload.multiple_retrieval_config?.reranking_mode === RerankingModeEnum.RerankingModel && !payload.multiple_retrieval_config?.reranking_model?.provider && payload.multiple_retrieval_config?.reranking_enable) - errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.errorMsg.fields.rerankModel`) }) - if (!errorMessages && payload.retrieval_mode === RETRIEVE_TYPE.oneWay && !payload.single_retrieval_config?.model?.provider) errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t('common.modelProvider.systemReasoningModel.key') }) + const { _datasets, multiple_retrieval_config, retrieval_mode } = payload + if (retrieval_mode === RETRIEVE_TYPE.multiWay) { + const checked = checkoutRerankModelConfigedInRetrievalSettings(_datasets || [], multiple_retrieval_config) + + if (!errorMessages && !checked) + errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.errorMsg.fields.rerankModel`) }) + } + return { isValid: !errorMessages, errorMessage: errorMessages, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/types.ts b/web/app/components/workflow/nodes/knowledge-retrieval/types.ts index da9373962b..1b85bfc0b5 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/types.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/types.ts @@ -1,6 +1,7 @@ import type { CommonNodeType, ModelConfig, ValueSelector } from '@/app/components/workflow/types' import type { RETRIEVE_TYPE } from '@/types/app' import type { + DataSet, RerankingModeEnum, } from '@/models/datasets' @@ -35,4 +36,5 @@ export type KnowledgeRetrievalNodeType = CommonNodeType & { retrieval_mode: RETRIEVE_TYPE multiple_retrieval_config?: MultipleRetrievalConfig single_retrieval_config?: SingleRetrievalConfig + _datasets?: DataSet[] } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts index 288a718aa2..e90fe2c2ff 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts @@ -67,6 +67,7 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { const { currentModel: currentRerankModel, + currentProvider: currentRerankProvider, } = useCurrentProviderAndModel( rerankModelList, rerankDefaultModel @@ -163,7 +164,10 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { draft.retrieval_mode = newMode if (newMode === RETRIEVE_TYPE.multiWay) { const multipleRetrievalConfig = draft.multiple_retrieval_config - draft.multiple_retrieval_config = getMultipleRetrievalConfig(multipleRetrievalConfig!, selectedDatasets, selectedDatasets, !!currentRerankModel) + draft.multiple_retrieval_config = getMultipleRetrievalConfig(multipleRetrievalConfig!, selectedDatasets, selectedDatasets, { + provider: currentRerankProvider?.provider, + model: currentRerankModel?.model, + }) } else { const hasSetModel = draft.single_retrieval_config?.model?.provider @@ -180,14 +184,17 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { } }) setInputs(newInputs) - }, [currentModel?.model, currentModel?.model_properties?.mode, currentProvider?.provider, inputs, setInputs, selectedDatasets, currentRerankModel]) + }, [currentModel?.model, currentModel?.model_properties?.mode, currentProvider?.provider, inputs, setInputs, selectedDatasets, currentRerankModel, currentRerankProvider]) const handleMultipleRetrievalConfigChange = useCallback((newConfig: MultipleRetrievalConfig) => { const newInputs = produce(inputs, (draft) => { - draft.multiple_retrieval_config = getMultipleRetrievalConfig(newConfig!, selectedDatasets, selectedDatasets, !!currentRerankModel) + draft.multiple_retrieval_config = getMultipleRetrievalConfig(newConfig!, selectedDatasets, selectedDatasets, { + provider: currentRerankProvider?.provider, + model: currentRerankModel?.model, + }) }) setInputs(newInputs) - }, [inputs, setInputs, selectedDatasets, currentRerankModel]) + }, [inputs, setInputs, selectedDatasets, currentRerankModel, currentRerankProvider]) // datasets useEffect(() => { @@ -200,6 +207,7 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { } const newInputs = produce(inputs, (draft) => { draft.dataset_ids = datasetIds + draft._datasets = selectedDatasets }) setInputs(newInputs) })() @@ -228,10 +236,14 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { } = getSelectedDatasetsMode(newDatasets) const newInputs = produce(inputs, (draft) => { draft.dataset_ids = newDatasets.map(d => d.id) + draft._datasets = newDatasets if (payload.retrieval_mode === RETRIEVE_TYPE.multiWay && newDatasets.length > 0) { const multipleRetrievalConfig = draft.multiple_retrieval_config - draft.multiple_retrieval_config = getMultipleRetrievalConfig(multipleRetrievalConfig!, newDatasets, selectedDatasets, !!currentRerankModel) + draft.multiple_retrieval_config = getMultipleRetrievalConfig(multipleRetrievalConfig!, newDatasets, selectedDatasets, { + provider: currentRerankProvider?.provider, + model: currentRerankModel?.model, + }) } }) setInputs(newInputs) @@ -243,7 +255,7 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { || allExternal ) setRerankModelOpen(true) - }, [inputs, setInputs, payload.retrieval_mode, selectedDatasets, currentRerankModel]) + }, [inputs, setInputs, payload.retrieval_mode, selectedDatasets, currentRerankModel, currentRerankProvider]) const filterVar = useCallback((varPayload: Var) => { return varPayload.type === VarType.string diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts b/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts index fd3d3ebab9..e9da9acccc 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts @@ -94,9 +94,10 @@ export const getMultipleRetrievalConfig = ( multipleRetrievalConfig: MultipleRetrievalConfig, selectedDatasets: DataSet[], originalDatasets: DataSet[], - isValidRerankModel?: boolean, + validRerankModel?: { provider?: string; model?: string }, ) => { const shouldSetWeightDefaultValue = xorBy(selectedDatasets, originalDatasets, 'id').length > 0 + const rerankModelIsValid = validRerankModel?.provider && validRerankModel?.model const { allHighQuality, @@ -128,18 +129,10 @@ export const getMultipleRetrievalConfig = ( reranking_enable: ((allInternal && allEconomic) || allExternal) ? reranking_enable : true, } - if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel || allExternal || mixtureInternalAndExternal) - result.reranking_mode = RerankingModeEnum.RerankingModel - - if (allHighQuality && !inconsistentEmbeddingModel && reranking_mode === undefined && allInternal) - result.reranking_mode = RerankingModeEnum.WeightedScore - - if (allHighQuality && !inconsistentEmbeddingModel && (reranking_mode === RerankingModeEnum.WeightedScore || reranking_mode === undefined) && allInternal && !weights) { - if (!isValidRerankModel) - result.reranking_mode = RerankingModeEnum.WeightedScore - else - result.reranking_mode = RerankingModeEnum.RerankingModel + if (!rerankModelIsValid) + result.reranking_model = undefined + const setDefaultWeights = () => { result.weights = { vector_setting: { vector_weight: allHighQualityVectorSearch @@ -160,31 +153,85 @@ export const getMultipleRetrievalConfig = ( } } - if (shouldSetWeightDefaultValue && allHighQuality && !inconsistentEmbeddingModel && (reranking_mode === RerankingModeEnum.WeightedScore || reranking_mode === undefined || !isValidRerankModel) && allInternal && weights) { - if (!isValidRerankModel) - result.reranking_mode = RerankingModeEnum.WeightedScore - else - result.reranking_mode = RerankingModeEnum.RerankingModel + if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel || allExternal || mixtureInternalAndExternal) { + result.reranking_mode = RerankingModeEnum.RerankingModel - result.weights = { - vector_setting: { - vector_weight: allHighQualityVectorSearch - ? DEFAULT_WEIGHTED_SCORE.allHighQualityVectorSearch.semantic - : allHighQualityFullTextSearch - ? DEFAULT_WEIGHTED_SCORE.allHighQualityFullTextSearch.semantic - : DEFAULT_WEIGHTED_SCORE.other.semantic, - embedding_provider_name: selectedDatasets[0].embedding_model_provider, - embedding_model_name: selectedDatasets[0].embedding_model, - }, - keyword_setting: { - keyword_weight: allHighQualityVectorSearch - ? DEFAULT_WEIGHTED_SCORE.allHighQualityVectorSearch.keyword - : allHighQualityFullTextSearch - ? DEFAULT_WEIGHTED_SCORE.allHighQualityFullTextSearch.keyword - : DEFAULT_WEIGHTED_SCORE.other.keyword, - }, + if (rerankModelIsValid) { + result.reranking_mode = RerankingModeEnum.RerankingModel + result.reranking_model = { + provider: validRerankModel?.provider || '', + model: validRerankModel?.model || '', + } + } + else { + result.reranking_model = undefined + } + } + + if (allHighQuality && !inconsistentEmbeddingModel && allInternal) { + if (!reranking_mode) { + if (validRerankModel?.provider && validRerankModel?.model) { + result.reranking_mode = RerankingModeEnum.RerankingModel + result.reranking_model = { + provider: validRerankModel.provider, + model: validRerankModel.model, + } + } + else { + result.reranking_mode = RerankingModeEnum.WeightedScore + setDefaultWeights() + } + } + + if (reranking_mode === RerankingModeEnum.WeightedScore && !weights) + setDefaultWeights() + + if (reranking_mode === RerankingModeEnum.WeightedScore && weights && shouldSetWeightDefaultValue) { + if (rerankModelIsValid) { + result.reranking_mode = RerankingModeEnum.RerankingModel + result.reranking_model = { + provider: validRerankModel.provider || '', + model: validRerankModel.model || '', + } + } + else { + setDefaultWeights() + } + } + + if (reranking_mode === RerankingModeEnum.RerankingModel && !rerankModelIsValid && shouldSetWeightDefaultValue) { + result.reranking_mode = RerankingModeEnum.WeightedScore + setDefaultWeights() } } return result } + +export const checkoutRerankModelConfigedInRetrievalSettings = ( + datasets: DataSet[], + multipleRetrievalConfig?: MultipleRetrievalConfig, +) => { + if (!multipleRetrievalConfig) + return true + + const { + allEconomic, + allExternal, + } = getSelectedDatasetsMode(datasets) + + const { + reranking_enable, + reranking_mode, + reranking_model, + } = multipleRetrievalConfig + + if (reranking_mode === RerankingModeEnum.RerankingModel && (!reranking_model?.provider || !reranking_model?.model)) { + if ((allEconomic || allExternal) && !reranking_enable) + return true + + return false + } + + return true +} From 40c5e6d67a2da47e71136e9c3a860222cda002f8 Mon Sep 17 00:00:00 2001 From: NFish Date: Tue, 12 Nov 2024 15:18:19 +0800 Subject: [PATCH 24/59] fix: Page may lock if user close the page when refresh access_token (#10550) --- web/service/refresh-token.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/web/service/refresh-token.ts b/web/service/refresh-token.ts index 8bd2215041..b193779629 100644 --- a/web/service/refresh-token.ts +++ b/web/service/refresh-token.ts @@ -1,11 +1,13 @@ import { apiPrefix } from '@/config' import { fetchWithRetry } from '@/utils' +const LOCAL_STORAGE_KEY = 'is_other_tab_refreshing' + let isRefreshing = false function waitUntilTokenRefreshed() { return new Promise((resolve, reject) => { function _check() { - const isRefreshingSign = localStorage.getItem('is_refreshing') + const isRefreshingSign = globalThis.localStorage.getItem(LOCAL_STORAGE_KEY) if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) { setTimeout(() => { _check() @@ -22,13 +24,14 @@ function waitUntilTokenRefreshed() { // only one request can send async function getNewAccessToken(): Promise { try { - const isRefreshingSign = localStorage.getItem('is_refreshing') + const isRefreshingSign = globalThis.localStorage.getItem(LOCAL_STORAGE_KEY) if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) { await waitUntilTokenRefreshed() } else { - globalThis.localStorage.setItem('is_refreshing', '1') isRefreshing = true + globalThis.localStorage.setItem(LOCAL_STORAGE_KEY, '1') + globalThis.addEventListener('beforeunload', releaseRefreshLock) const refresh_token = globalThis.localStorage.getItem('refresh_token') // Do not use baseFetch to refresh tokens. @@ -61,15 +64,21 @@ async function getNewAccessToken(): Promise { return Promise.reject(error) } finally { + releaseRefreshLock() + } +} + +function releaseRefreshLock() { + if (isRefreshing) { isRefreshing = false - globalThis.localStorage.removeItem('is_refreshing') + globalThis.localStorage.removeItem(LOCAL_STORAGE_KEY) + globalThis.removeEventListener('beforeunload', releaseRefreshLock) } } export async function refreshAccessTokenOrRelogin(timeout: number) { return Promise.race([new Promise((resolve, reject) => setTimeout(() => { - isRefreshing = false - globalThis.localStorage.removeItem('is_refreshing') + releaseRefreshLock() reject(new Error('request timeout')) }, timeout)), getNewAccessToken()]) } From b77628c45863e72ff8fe0ef5a24538a9c0e69574 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 12 Nov 2024 15:35:12 +0800 Subject: [PATCH 25/59] fix: text-generation webapp file form (#10578) --- .../share/text-generation/index.tsx | 2 ++ .../share/text-generation/run-once/index.tsx | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/web/app/components/share/text-generation/index.tsx b/web/app/components/share/text-generation/index.tsx index 0860560e7c..b853100b69 100644 --- a/web/app/components/share/text-generation/index.tsx +++ b/web/app/components/share/text-generation/index.tsx @@ -94,6 +94,7 @@ const TextGeneration: FC = ({ const [isCallBatchAPI, setIsCallBatchAPI] = useState(false) const isInBatchTab = currentTab === 'batch' const [inputs, setInputs] = useState>({}) + const inputsRef = useRef(inputs) const [appId, setAppId] = useState('') const [siteInfo, setSiteInfo] = useState(null) const [canReplaceLogo, setCanReplaceLogo] = useState(false) @@ -604,6 +605,7 @@ const TextGeneration: FC = ({ + inputsRef: React.MutableRefObject> onInputsChange: (inputs: Record) => void onSend: () => void visionConfig: VisionSettings @@ -27,6 +28,7 @@ export type IRunOnceProps = { const RunOnce: FC = ({ promptConfig, inputs, + inputsRef, onInputsChange, onSend, visionConfig, @@ -47,6 +49,11 @@ const RunOnce: FC = ({ onSend() } + const handleInputsChange = useCallback((newInputs: Record) => { + onInputsChange(newInputs) + inputsRef.current = newInputs + }, [onInputsChange, inputsRef]) + return (
@@ -60,7 +67,7 @@ const RunOnce: FC = ({