From fce126b206a7b08b6d47f1c06462e54f257c03f4 Mon Sep 17 00:00:00 2001 From: chenguowei <457219884@qq.com> Date: Mon, 28 Jul 2025 15:37:13 +0800 Subject: [PATCH 01/20] fix(api): fix incorrect path handling in Langfuse integration (#22766) Co-authored-by: QuantumGhost --- api/core/ops/entities/config_entity.py | 2 +- api/core/ops/utils.py | 8 +++++++- api/tests/unit_tests/core/ops/test_config_entity.py | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/api/core/ops/entities/config_entity.py b/api/core/ops/entities/config_entity.py index 89ff0cfded..626782cee5 100644 --- a/api/core/ops/entities/config_entity.py +++ b/api/core/ops/entities/config_entity.py @@ -102,7 +102,7 @@ class LangfuseConfig(BaseTracingConfig): @field_validator("host") @classmethod def host_validator(cls, v, info: ValidationInfo): - return cls.validate_endpoint_url(v, "https://api.langfuse.com") + return validate_url_with_path(v, "https://api.langfuse.com") class LangSmithConfig(BaseTracingConfig): diff --git a/api/core/ops/utils.py b/api/core/ops/utils.py index 573e8cac88..2c0afb1600 100644 --- a/api/core/ops/utils.py +++ b/api/core/ops/utils.py @@ -67,7 +67,13 @@ def generate_dotted_order( def validate_url(url: str, default_url: str, allowed_schemes: tuple = ("https", "http")) -> str: """ - Validate and normalize URL with proper error handling + Validate and normalize URL with proper error handling. + + NOTE: This function does not retain the `path` component of the provided URL. + In most cases, it is recommended to use `validate_url_with_path` instead. + + This function is deprecated and retained only for compatibility purposes. + New implementations should use `validate_url_with_path`. Args: url: The URL to validate diff --git a/api/tests/unit_tests/core/ops/test_config_entity.py b/api/tests/unit_tests/core/ops/test_config_entity.py index 81cb04548d..4bcc6cb605 100644 --- a/api/tests/unit_tests/core/ops/test_config_entity.py +++ b/api/tests/unit_tests/core/ops/test_config_entity.py @@ -117,6 +117,13 @@ class TestLangfuseConfig: assert config.secret_key == "secret_key" assert config.host == "https://custom.langfuse.com" + def test_valid_config_with_path(self): + host = host = "https://custom.langfuse.com/api/v1" + config = LangfuseConfig(public_key="public_key", secret_key="secret_key", host=host) + assert config.public_key == "public_key" + assert config.secret_key == "secret_key" + assert config.host == host + def test_default_values(self): """Test default values are set correctly""" config = LangfuseConfig(public_key="public", secret_key="secret") From 15757110cff780a72637b52037676f598e0a4072 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Mon, 28 Jul 2025 10:37:23 +0300 Subject: [PATCH 02/20] feat: default value option for select input fields (#21192) Co-authored-by: crazywoola <427733928@qq.com> Co-authored-by: GuanMu --- .../config-var/config-modal/index.tsx | 28 +++++++++++++-- .../base/chat/chat-with-history/hooks.tsx | 2 +- .../chat-with-history/inputs-form/content.tsx | 2 +- .../base/chat/embedded-chatbot/hooks.tsx | 2 +- .../embedded-chatbot/inputs-form/content.tsx | 2 +- .../components/before-run-form/form-item.tsx | 2 +- .../panel/debug-and-preview/chat-wrapper.tsx | 35 +++++++++++++++++-- web/i18n/de-DE/app-debug.ts | 3 ++ web/i18n/en-US/app-debug.ts | 3 ++ web/i18n/es-ES/app-debug.ts | 3 ++ web/i18n/fr-FR/app-debug.ts | 3 ++ web/i18n/hi-IN/app-debug.ts | 3 ++ web/i18n/it-IT/app-debug.ts | 3 ++ web/i18n/ja-JP/app-debug.ts | 3 ++ web/i18n/ko-KR/app-debug.ts | 3 ++ web/i18n/pl-PL/app-debug.ts | 3 ++ web/i18n/pt-BR/app-debug.ts | 3 ++ web/i18n/ro-RO/app-debug.ts | 3 ++ web/i18n/ru-RU/app-debug.ts | 3 ++ web/i18n/tr-TR/app-debug.ts | 3 ++ web/i18n/uk-UA/app-debug.ts | 3 ++ web/i18n/vi-VN/app-debug.ts | 3 ++ web/i18n/zh-Hans/app-debug.ts | 3 ++ web/i18n/zh-Hant/app-debug.ts | 3 ++ 24 files changed, 113 insertions(+), 11 deletions(-) diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx index 8fcc0f4c08..72c66cf76a 100644 --- a/web/app/components/app/configuration/config-var/config-modal/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -20,6 +20,7 @@ import FileUploadSetting from '@/app/components/workflow/nodes/_base/components/ import Checkbox from '@/app/components/base/checkbox' import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants' import { DEFAULT_VALUE_MAX_LEN } from '@/config' +import { SimpleSelect } from '@/app/components/base/select' const TEXT_MAX_LENGTH = 256 @@ -234,9 +235,30 @@ const ConfigModal: FC = ({ )} {type === InputVarType.select && ( - - - + <> + + + + {options && options.length > 0 && ( + + opt.trim() !== '').map(option => ({ + value: option, + name: option, + })), + ]} + defaultValue={tempPayload.default || ''} + onSelect={item => handlePayloadChange('default')(item.value === '' ? undefined : item.value)} + placeholder={t('appDebug.variableConfig.selectDefaultValue')} + allowSearch={false} + /> + + )} + )} {[InputVarType.singleFile, InputVarType.multiFiles].includes(type) && ( 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 76eb89164e..382ded3201 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -211,7 +211,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const isInputInOptions = item.select.options.includes(initInputs[item.select.variable]) return { ...item.select, - default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.default, + default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.select.default, type: 'select', } } diff --git a/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx b/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx index 73a1f07b69..3304d50a50 100644 --- a/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx +++ b/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx @@ -73,7 +73,7 @@ const InputsFormContent = ({ showTip }: Props) => { {form.type === InputVarType.select && ( ({ value: option, name: option }))} onSelect={item => handleFormChange(form.variable, item.value as string)} placeholder={form.label} diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 8ae86bda84..4e86ad50e4 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -199,7 +199,7 @@ export const useEmbeddedChatbot = () => { const isInputInOptions = item.select.options.includes(initInputs[item.select.variable]) return { ...item.select, - default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.default, + default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.select.default, type: 'select', } } diff --git a/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx b/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx index c5f39718f1..29fa5394ef 100644 --- a/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx +++ b/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx @@ -73,7 +73,7 @@ const InputsFormContent = ({ showTip }: Props) => { {form.type === InputVarType.select && ( ({ value: option, name: option }))} onSelect={item => handleFormChange(form.variable, item.value as string)} placeholder={form.label} diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx index 269f5e0a96..430359b845 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx @@ -158,7 +158,7 @@ const FormItem: FC = ({ type === InputVarType.select && ( handleValueChange(e.target.value)} placeholder={placeholder?.[language] || placeholder?.en_US} /> diff --git a/web/app/components/workflow/nodes/tool/node.tsx b/web/app/components/workflow/nodes/tool/node.tsx index e15ddcaaaa..8cc3ec580d 100644 --- a/web/app/components/workflow/nodes/tool/node.tsx +++ b/web/app/components/workflow/nodes/tool/node.tsx @@ -22,13 +22,13 @@ const Node: FC> = ({ {key} {typeof tool_configurations[key].value === 'string' && ( -
+
{paramSchemas?.find(i => i.name === key)?.type === FormTypeEnum.secretInput ? '********' : tool_configurations[key].value}
)} {typeof tool_configurations[key].value === 'number' && ( -
- {tool_configurations[key].value} +
+ {Number.isNaN(tool_configurations[key].value) ? '' : tool_configurations[key].value}
)} {typeof tool_configurations[key] !== 'string' && tool_configurations[key]?.type === FormTypeEnum.modelSelector && ( From 63b6026e6e414d3c1970944457dd77fb4b105336 Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:59:43 +0800 Subject: [PATCH 18/20] minor fix: fix error messages (#23081) --- api/core/workflow/nodes/tool/entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/workflow/nodes/tool/entities.py b/api/core/workflow/nodes/tool/entities.py index 4f47fb1efc..c1cfbb1edc 100644 --- a/api/core/workflow/nodes/tool/entities.py +++ b/api/core/workflow/nodes/tool/entities.py @@ -55,7 +55,7 @@ class ToolNodeData(BaseNodeData, ToolEntity): if not isinstance(val, str): raise ValueError("value must be a list of strings") elif typ == "constant" and not isinstance(value, str | int | float | bool | dict): - raise ValueError("value must be a string, int, float, or bool") + raise ValueError("value must be a string, int, float, bool or dict") return typ tool_parameters: dict[str, ToolInput] From 47cc95184132b756a94e58c2e0c85fb01044eced Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:17:50 +0800 Subject: [PATCH 19/20] Fix Empty Collection WHERE Filter Issue (#23086) --- api/services/app_service.py | 5 +++-- api/services/conversation_service.py | 6 ++++-- api/services/dataset_service.py | 26 +++++++++++++++++++++----- api/services/message_service.py | 3 ++- api/services/tag_service.py | 6 ++++++ 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/api/services/app_service.py b/api/services/app_service.py index 3557f13337..0f22666d5a 100644 --- a/api/services/app_service.py +++ b/api/services/app_service.py @@ -53,9 +53,10 @@ class AppService: if args.get("name"): name = args["name"][:30] filters.append(App.name.ilike(f"%{name}%")) - if args.get("tag_ids"): + # Check if tag_ids is not empty to avoid WHERE false condition + if args.get("tag_ids") and len(args["tag_ids"]) > 0: target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, args["tag_ids"]) - if target_ids: + if target_ids and len(target_ids) > 0: filters.append(App.id.in_(target_ids)) else: return None diff --git a/api/services/conversation_service.py b/api/services/conversation_service.py index 525c87fe4a..206c832a20 100644 --- a/api/services/conversation_service.py +++ b/api/services/conversation_service.py @@ -46,9 +46,11 @@ class ConversationService: Conversation.from_account_id == (user.id if isinstance(user, Account) else None), or_(Conversation.invoke_from.is_(None), Conversation.invoke_from == invoke_from.value), ) - if include_ids is not None: + # Check if include_ids is not None and not empty to avoid WHERE false condition + if include_ids is not None and len(include_ids) > 0: stmt = stmt.where(Conversation.id.in_(include_ids)) - if exclude_ids is not None: + # Check if exclude_ids is not None and not empty to avoid WHERE false condition + if exclude_ids is not None and len(exclude_ids) > 0: stmt = stmt.where(~Conversation.id.in_(exclude_ids)) # define sort fields and directions diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 209d153b0c..1280399990 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -91,14 +91,16 @@ class DatasetService: if user.current_role == TenantAccountRole.DATASET_OPERATOR: # only show datasets that the user has permission to access - if permitted_dataset_ids: + # Check if permitted_dataset_ids is not empty to avoid WHERE false condition + if permitted_dataset_ids and len(permitted_dataset_ids) > 0: query = query.where(Dataset.id.in_(permitted_dataset_ids)) else: return [], 0 else: if user.current_role != TenantAccountRole.OWNER or not include_all: # show all datasets that the user has permission to access - if permitted_dataset_ids: + # Check if permitted_dataset_ids is not empty to avoid WHERE false condition + if permitted_dataset_ids and len(permitted_dataset_ids) > 0: query = query.where( db.or_( Dataset.permission == DatasetPermissionEnum.ALL_TEAM, @@ -127,9 +129,10 @@ class DatasetService: if search: query = query.where(Dataset.name.ilike(f"%{search}%")) - if tag_ids: + # Check if tag_ids is not empty to avoid WHERE false condition + if tag_ids and len(tag_ids) > 0: target_ids = TagService.get_target_ids_by_tag_ids("knowledge", tenant_id, tag_ids) - if target_ids: + if target_ids and len(target_ids) > 0: query = query.where(Dataset.id.in_(target_ids)) else: return [], 0 @@ -158,6 +161,9 @@ class DatasetService: @staticmethod def get_datasets_by_ids(ids, tenant_id): + # Check if ids is not empty to avoid WHERE false condition + if not ids or len(ids) == 0: + return [], 0 stmt = select(Dataset).where(Dataset.id.in_(ids), Dataset.tenant_id == tenant_id) datasets = db.paginate(select=stmt, page=1, per_page=len(ids), max_per_page=len(ids), error_out=False) @@ -951,6 +957,9 @@ class DocumentService: @staticmethod def delete_documents(dataset: Dataset, document_ids: list[str]): + # Check if document_ids is not empty to avoid WHERE false condition + if not document_ids or len(document_ids) == 0: + return documents = db.session.query(Document).where(Document.id.in_(document_ids)).all() file_ids = [ document.data_source_info_dict["upload_file_id"] @@ -2320,6 +2329,9 @@ class SegmentService: @classmethod def delete_segments(cls, segment_ids: list, document: Document, dataset: Dataset): + # Check if segment_ids is not empty to avoid WHERE false condition + if not segment_ids or len(segment_ids) == 0: + return index_node_ids = ( db.session.query(DocumentSegment) .with_entities(DocumentSegment.index_node_id) @@ -2339,6 +2351,9 @@ class SegmentService: @classmethod def update_segments_status(cls, segment_ids: list, action: str, dataset: Dataset, document: Document): + # Check if segment_ids is not empty to avoid WHERE false condition + if not segment_ids or len(segment_ids) == 0: + return if action == "enable": segments = ( db.session.query(DocumentSegment) @@ -2600,7 +2615,8 @@ class SegmentService: DocumentSegment.document_id == document_id, DocumentSegment.tenant_id == tenant_id ) - if status_list: + # Check if status_list is not empty to avoid WHERE false condition + if status_list and len(status_list) > 0: query = query.where(DocumentSegment.status.in_(status_list)) if keyword: diff --git a/api/services/message_service.py b/api/services/message_service.py index 283b7b9b4b..a19d6ee157 100644 --- a/api/services/message_service.py +++ b/api/services/message_service.py @@ -111,7 +111,8 @@ class MessageService: base_query = base_query.where(Message.conversation_id == conversation.id) - if include_ids is not None: + # Check if include_ids is not None and not empty to avoid WHERE false condition + if include_ids is not None and len(include_ids) > 0: base_query = base_query.where(Message.id.in_(include_ids)) if last_id: diff --git a/api/services/tag_service.py b/api/services/tag_service.py index 75fa52a75c..2e5e96214b 100644 --- a/api/services/tag_service.py +++ b/api/services/tag_service.py @@ -26,6 +26,9 @@ class TagService: @staticmethod def get_target_ids_by_tag_ids(tag_type: str, current_tenant_id: str, tag_ids: list) -> list: + # Check if tag_ids is not empty to avoid WHERE false condition + if not tag_ids or len(tag_ids) == 0: + return [] tags = ( db.session.query(Tag) .where(Tag.id.in_(tag_ids), Tag.tenant_id == current_tenant_id, Tag.type == tag_type) @@ -34,6 +37,9 @@ class TagService: if not tags: return [] tag_ids = [tag.id for tag in tags] + # Check if tag_ids is not empty to avoid WHERE false condition + if not tag_ids or len(tag_ids) == 0: + return [] tag_bindings = ( db.session.query(TagBinding.target_id) .where(TagBinding.tag_id.in_(tag_ids), TagBinding.tenant_id == current_tenant_id) From 77216488675db30d21e43466cca95dfe82bd9c0c Mon Sep 17 00:00:00 2001 From: GuanMu Date: Tue, 29 Jul 2025 11:24:59 +0800 Subject: [PATCH 20/20] Fix variable config (#23070) --- .../config-var/config-modal/index.tsx | 1 + web/app/components/base/select/index.tsx | 4 +--- .../share/text-generation/run-once/index.tsx | 4 +++- .../workflow/panel/inputs-panel.tsx | 24 ++++++++++++++++++- web/utils/model-config.ts | 3 ++- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx index 72c66cf76a..27072f5208 100644 --- a/web/app/components/app/configuration/config-var/config-modal/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -244,6 +244,7 @@ const ConfigModal: FC = ({ opt.trim() !== '').map(option => ({ diff --git a/web/app/components/base/select/index.tsx b/web/app/components/base/select/index.tsx index 77d229672f..d9285c1061 100644 --- a/web/app/components/base/select/index.tsx +++ b/web/app/components/base/select/index.tsx @@ -77,7 +77,6 @@ const Select: FC = ({ defaultSelect = existed setSelectedItem(defaultSelect) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue]) const filteredItems: Item[] @@ -201,7 +200,6 @@ const SimpleSelect: FC = ({ defaultSelect = existed setSelectedItem(defaultSelect) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue]) const listboxRef = useRef(null) @@ -344,7 +342,7 @@ const PortalSelect: FC = ({ > diff --git a/web/app/components/share/text-generation/run-once/index.tsx b/web/app/components/share/text-generation/run-once/index.tsx index 546b21d2b0..cfafe73bf2 100644 --- a/web/app/components/share/text-generation/run-once/index.tsx +++ b/web/app/components/share/text-generation/run-once/index.tsx @@ -66,7 +66,9 @@ const RunOnce: FC = ({ useEffect(() => { const newInputs: Record = {} promptConfig.prompt_variables.forEach((item) => { - if (item.type === 'string' || item.type === 'paragraph') + if (item.type === 'select') + newInputs[item.key] = item.default + else if (item.type === 'string' || item.type === 'paragraph') newInputs[item.key] = '' else newInputs[item.key] = undefined diff --git a/web/app/components/workflow/panel/inputs-panel.tsx b/web/app/components/workflow/panel/inputs-panel.tsx index 8be8d810f0..64ac6d8686 100644 --- a/web/app/components/workflow/panel/inputs-panel.tsx +++ b/web/app/components/workflow/panel/inputs-panel.tsx @@ -1,6 +1,7 @@ import { memo, useCallback, + useEffect, useMemo, } from 'react' import { useTranslation } from 'react-i18next' @@ -32,9 +33,12 @@ type Props = { const InputsPanel = ({ onRun }: Props) => { const { t } = useTranslation() const workflowStore = useWorkflowStore() + const { inputs, setInputs } = useStore(s => ({ + inputs: s.inputs, + setInputs: s.setInputs, + })) const fileSettings = useFeatures(s => s.features.file) const nodes = useNodes() - const inputs = useStore(s => s.inputs) const files = useStore(s => s.files) const workflowRunningData = useStore(s => s.workflowRunningData) const { @@ -44,6 +48,24 @@ const InputsPanel = ({ onRun }: Props) => { const startVariables = startNode?.data.variables const { checkInputsForm } = useCheckInputsForms() + const initialInputs = useMemo(() => { + const initInputs: Record = {} + if (startVariables) { + startVariables.forEach((variable) => { + if (variable.default) + initInputs[variable.variable] = variable.default + }) + } + return initInputs + }, [startVariables]) + + useEffect(() => { + setInputs({ + ...initialInputs, + ...inputs, + }) + }, [initialInputs]) + const variables = useMemo(() => { const data = startVariables || [] if (fileSettings?.image?.enabled) { diff --git a/web/utils/model-config.ts b/web/utils/model-config.ts index 330d8f9b52..3a500f22bc 100644 --- a/web/utils/model-config.ts +++ b/web/utils/model-config.ts @@ -62,6 +62,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | options: content.options, is_context_var, hide: content.hide, + default: content.default, }) } else if (type === 'file') { @@ -148,7 +149,7 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[ variable: item.key, required: item.required !== false, // default true options: item.options, - default: '', + default: item.default ?? '', hide: item.hide, }, } as any)