From b8e305f18327ecdee40fd9b7b1341effcbe7c13e Mon Sep 17 00:00:00 2001 From: Good Wood Date: Tue, 13 May 2025 19:46:26 +0800 Subject: [PATCH 01/16] fix: fix can't config Nth item in list Node (#19618) --- .../workflow/nodes/list-operator/panel.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/web/app/components/workflow/nodes/list-operator/panel.tsx b/web/app/components/workflow/nodes/list-operator/panel.tsx index a09839f04f..d93a79397d 100644 --- a/web/app/components/workflow/nodes/list-operator/panel.tsx +++ b/web/app/components/workflow/nodes/list-operator/panel.tsx @@ -97,16 +97,14 @@ const Panel: FC> = ({ {inputs.extract_by?.enabled ? (
- {hasSubVariable && ( -
- -
- )} +
+ +
) : null} From 297d35364e4c6de1fa2ba8725c99aab454d1ca92 Mon Sep 17 00:00:00 2001 From: yangzheli <43645580+yangzheli@users.noreply.github.com> Date: Tue, 13 May 2025 21:31:01 +0800 Subject: [PATCH 02/16] fix(web): optimize action buttons style in the question component (#19626) --- .../components/base/chat/chat/question.tsx | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/web/app/components/base/chat/chat/question.tsx b/web/app/components/base/chat/chat/question.tsx index 3f7f2e837f..8fc49d1978 100644 --- a/web/app/components/base/chat/chat/question.tsx +++ b/web/app/components/base/chat/chat/question.tsx @@ -5,6 +5,8 @@ import type { import { memo, useCallback, + useEffect, + useRef, useState, } from 'react' import type { ChatItem } from '../types' @@ -52,6 +54,8 @@ const Question: FC = ({ const [isEditing, setIsEditing] = useState(false) const [editedContent, setEditedContent] = useState(content) + const [contentWidth, setContentWidth] = useState(0) + const contentRef = useRef(null) const handleEdit = useCallback(() => { setIsEditing(true) @@ -75,14 +79,31 @@ const Question: FC = ({ item.nextSibling && switchSibling?.(item.nextSibling) }, [switchSibling, item.prevSibling, item.nextSibling]) + const getContentWidth = () => { + if (contentRef.current) + setContentWidth(contentRef.current?.clientWidth) + } + + useEffect(() => { + if (!contentRef.current) + return + const resizeObserver = new ResizeObserver(() => { + getContentWidth() + }) + resizeObserver.observe(contentRef.current) + return () => { + resizeObserver.disconnect() + } + }, []) + return ( -
-
+
+
-
+
{ copy(content) Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) @@ -95,6 +116,7 @@ const Question: FC = ({
From 3548c133e35ed4d27cb64a342f1206b8c8a23780 Mon Sep 17 00:00:00 2001 From: KVOJJJin Date: Wed, 14 May 2025 09:06:14 +0800 Subject: [PATCH 03/16] Feat: add theme switcher (#18093) --- web/app/(commonLayout)/datasets/Doc.tsx | 2 +- .../app/create-from-dsl-modal/index.tsx | 2 +- .../app/create-from-dsl-modal/uploader.tsx | 22 ++--- .../assets/vender/line/editor/collapse.svg | 9 ++ .../src/vender/line/editor/Collapse.json | 62 ++++++++++++ .../icons/src/vender/line/editor/Collapse.tsx | 20 ++++ .../icons/src/vender/line/editor/index.ts | 1 + web/app/components/base/theme-selector.tsx | 97 +++++++++++++++++++ web/app/components/base/theme-switcher.tsx | 58 +++++++++++ .../detail/completed/display-toggle.tsx | 2 +- web/app/components/develop/doc.tsx | 2 +- .../develop/secret-key/input-copy.tsx | 21 +--- web/app/components/explore/category.tsx | 6 +- .../header/account-dropdown/index.tsx | 15 ++- .../tool-selector/reasoning-config-form.tsx | 2 +- .../share/text-generation/menu-dropdown.tsx | 9 ++ .../components/workflow/nodes/end/node.tsx | 2 +- .../nodes/tool/components/input-var-list.tsx | 2 +- web/app/layout.tsx | 3 +- web/app/signin/_header.tsx | 29 +++--- web/i18n/en-US/common.ts | 6 ++ web/i18n/ja-JP/common.ts | 6 ++ web/i18n/zh-Hans/common.ts | 6 ++ 23 files changed, 330 insertions(+), 54 deletions(-) create mode 100644 web/app/components/base/icons/assets/vender/line/editor/collapse.svg create mode 100644 web/app/components/base/icons/src/vender/line/editor/Collapse.json create mode 100644 web/app/components/base/icons/src/vender/line/editor/Collapse.tsx create mode 100644 web/app/components/base/theme-selector.tsx create mode 100644 web/app/components/base/theme-switcher.tsx diff --git a/web/app/(commonLayout)/datasets/Doc.tsx b/web/app/(commonLayout)/datasets/Doc.tsx index 20264ce8ad..efdfe157f2 100644 --- a/web/app/(commonLayout)/datasets/Doc.tsx +++ b/web/app/(commonLayout)/datasets/Doc.tsx @@ -121,7 +121,7 @@ const Doc = ({ apiBaseUrl }: DocProps) => { )}
-
+
{Template}
diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx index c1df10ed64..9739ac47ea 100644 --- a/web/app/components/app/create-from-dsl-modal/index.tsx +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -262,7 +262,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS { currentTab === CreateFromDSLModalTab.FROM_URL && (
-
DSL URL
+
DSL URL
= ({ />
{!file && ( -
+
- -
+ +
{t('datasetCreation.stepOne.uploader.button')} - {t('datasetDocuments.list.batchModal.browse')} + {t('datasetDocuments.list.batchModal.browse')}
{dragging &&
}
)} {file && ( -
+
@@ -126,12 +126,10 @@ const Uploader: FC = ({ {formatFileSize(file.size)}
-
- -
-
+
+ -
+
)} diff --git a/web/app/components/base/icons/assets/vender/line/editor/collapse.svg b/web/app/components/base/icons/assets/vender/line/editor/collapse.svg new file mode 100644 index 0000000000..b54e046085 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/editor/collapse.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/app/components/base/icons/src/vender/line/editor/Collapse.json b/web/app/components/base/icons/src/vender/line/editor/Collapse.json new file mode 100644 index 0000000000..5e3cf08ce0 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/editor/Collapse.json @@ -0,0 +1,62 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Icon L" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Vector" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M2.66602 11.3333H0.666016L3.33268 8.66667L5.99935 11.3333H3.99935L3.99935 14H2.66602L2.66602 11.3333Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M2.66602 4.66667L2.66602 2L3.99935 2L3.99935 4.66667L5.99935 4.66667L3.33268 7.33333L0.666016 4.66667L2.66602 4.66667Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M7.33268 2.66667H13.9993V4H7.33268V2.66667ZM7.33268 12H13.9993V13.3333H7.33268V12ZM5.99935 7.33333H13.9993V8.66667H5.99935V7.33333Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Collapse" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/editor/Collapse.tsx b/web/app/components/base/icons/src/vender/line/editor/Collapse.tsx new file mode 100644 index 0000000000..6f43dde272 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/editor/Collapse.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Collapse.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'Collapse' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/editor/index.ts b/web/app/components/base/icons/src/vender/line/editor/index.ts index f571be03c6..b31c42e390 100644 --- a/web/app/components/base/icons/src/vender/line/editor/index.ts +++ b/web/app/components/base/icons/src/vender/line/editor/index.ts @@ -1,5 +1,6 @@ export { default as AlignLeft } from './AlignLeft' export { default as BezierCurve03 } from './BezierCurve03' +export { default as Collapse } from './Collapse' export { default as Colors } from './Colors' export { default as ImageIndentLeft } from './ImageIndentLeft' export { default as LeftIndent02 } from './LeftIndent02' diff --git a/web/app/components/base/theme-selector.tsx b/web/app/components/base/theme-selector.tsx new file mode 100644 index 0000000000..8dfe1d2602 --- /dev/null +++ b/web/app/components/base/theme-selector.tsx @@ -0,0 +1,97 @@ +'use client' + +import { useState } from 'react' +import { + RiCheckLine, + RiComputerLine, + RiMoonLine, + RiSunLine, +} from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useTheme } from 'next-themes' +import ActionButton from '@/app/components/base/action-button' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' + +export type Theme = 'light' | 'dark' | 'system' + +export default function ThemeSelector() { + const { t } = useTranslation() + const { theme, setTheme } = useTheme() + const [open, setOpen] = useState(false) + + const handleThemeChange = (newTheme: Theme) => { + setTheme(newTheme) + setOpen(false) + } + + const getCurrentIcon = () => { + switch (theme) { + case 'light': return + case 'dark': return + default: return + } + } + + return ( + + setOpen(!open)} + > + + {getCurrentIcon()} + + + +
+ + + +
+
+
+ ) +} diff --git a/web/app/components/base/theme-switcher.tsx b/web/app/components/base/theme-switcher.tsx new file mode 100644 index 0000000000..902d064a66 --- /dev/null +++ b/web/app/components/base/theme-switcher.tsx @@ -0,0 +1,58 @@ +'use client' +import { + RiComputerLine, + RiMoonLine, + RiSunLine, +} from '@remixicon/react' +import { useTheme } from 'next-themes' +import cn from '@/utils/classnames' + +export type Theme = 'light' | 'dark' | 'system' + +export default function ThemeSwitcher() { + const { theme, setTheme } = useTheme() + + const handleThemeChange = (newTheme: Theme) => { + setTheme(newTheme) + } + + return ( +
+
handleThemeChange('system')} + > +
+ +
+
+
+
handleThemeChange('light')} + > +
+ +
+
+
+
handleThemeChange('dark')} + > +
+ +
+
+
+ ) +} diff --git a/web/app/components/datasets/documents/detail/completed/display-toggle.tsx b/web/app/components/datasets/documents/detail/completed/display-toggle.tsx index b5da72c603..24e0be15a1 100644 --- a/web/app/components/datasets/documents/detail/completed/display-toggle.tsx +++ b/web/app/components/datasets/documents/detail/completed/display-toggle.tsx @@ -2,7 +2,7 @@ import React, { type FC } from 'react' import { useTranslation } from 'react-i18next' import { RiLineHeight } from '@remixicon/react' import Tooltip from '@/app/components/base/tooltip' -import { Collapse } from '@/app/components/base/icons/src/public/knowledge' +import { Collapse } from '@/app/components/base/icons/src/vender/line/editor' type DisplayToggleProps = { isCollapsed: boolean diff --git a/web/app/components/develop/doc.tsx b/web/app/components/develop/doc.tsx index 12a273ea9c..c61cc09863 100644 --- a/web/app/components/develop/doc.tsx +++ b/web/app/components/develop/doc.tsx @@ -121,7 +121,7 @@ const Doc = ({ appDetail }: IDocProps) => { )}
-
+
{(appDetail?.mode === 'chat' || appDetail?.mode === 'agent-chat') && ( (() => { switch (locale) { diff --git a/web/app/components/develop/secret-key/input-copy.tsx b/web/app/components/develop/secret-key/input-copy.tsx index bb6b4653b5..982c63f620 100644 --- a/web/app/components/develop/secret-key/input-copy.tsx +++ b/web/app/components/develop/secret-key/input-copy.tsx @@ -2,20 +2,18 @@ import React, { useEffect, useState } from 'react' import copy from 'copy-to-clipboard' import { t } from 'i18next' -import s from './style.module.css' import Tooltip from '@/app/components/base/tooltip' +import CopyFeedback from '@/app/components/base/copy-feedback' type IInputCopyProps = { value?: string className?: string - readOnly?: boolean children?: React.ReactNode } const InputCopy = ({ value = '', className, - readOnly = true, children, }: IInputCopyProps) => { const [isCopied, setIsCopied] = useState(false) @@ -45,23 +43,12 @@ const InputCopy = ({ popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`} position='bottom' > - {value} + {value}
-
- -
-
{ - copy(value) - setIsCopied(true) - }}> -
-
-
+
+
) diff --git a/web/app/components/explore/category.tsx b/web/app/components/explore/category.tsx index 4456131600..51daaa95dd 100644 --- a/web/app/components/explore/category.tsx +++ b/web/app/components/explore/category.tsx @@ -31,8 +31,8 @@ const Category: FC = ({ const isAllCategories = !list.includes(value as AppCategory) || value === allCategoriesEn const itemClassName = (isSelected: boolean) => cn( - 'flex h-[32px] cursor-pointer items-center rounded-lg border-[0.5px] border-transparent px-3 py-[7px] font-medium leading-[18px] text-gray-700 hover:bg-gray-200', - isSelected && 'border-gray-200 bg-white text-primary-600 shadow-xs hover:bg-white', + 'flex h-[32px] cursor-pointer items-center rounded-lg border-[0.5px] border-transparent px-3 py-[7px] font-medium leading-[18px] text-text-tertiary hover:bg-components-main-nav-nav-button-bg-active', + isSelected && 'border-components-main-nav-nav-button-border bg-components-main-nav-nav-button-bg-active text-components-main-nav-nav-button-text-active shadow-xs', ) return ( @@ -50,7 +50,7 @@ const Category: FC = ({ className={itemClassName(name === value)} onClick={() => onChange(name)} > - {categoryI18n[name] ? t(`explore.category.${name}`) : name} + {(categoryI18n as any)[name] ? t(`explore.category.${name}`) : name}
))}
diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index df0dd37260..4a08a4c03d 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -14,6 +14,7 @@ import { RiMap2Line, RiSettings3Line, RiStarLine, + RiTShirt2Line, } from '@remixicon/react' import Link from 'next/link' import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' @@ -25,6 +26,7 @@ import Compliance from './compliance' import PremiumBadge from '@/app/components/base/premium-badge' import { useGetDocLanguage } from '@/context/i18n' import Avatar from '@/app/components/base/avatar' +import ThemeSwitcher from '@/app/components/base/theme-switcher' import { logout } from '@/service/common' import AppContext, { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' @@ -82,8 +84,8 @@ export default function AppSelector() { @@ -186,6 +188,15 @@ export default function AppSelector() { ) }
+ +
+
+ +
{t('common.theme.theme')}
+ +
+
+
handleLogout()}>
= ({ <> {isString && ( = ({
+
+
+
{t('common.theme.theme')}
+ +
+
+
{data?.privacy_policy && ( diff --git a/web/app/components/workflow/nodes/end/node.tsx b/web/app/components/workflow/nodes/end/node.tsx index e0c5604391..6906e0f77c 100644 --- a/web/app/components/workflow/nodes/end/node.tsx +++ b/web/app/components/workflow/nodes/end/node.tsx @@ -52,7 +52,7 @@ const Node: FC> = ({ isChatMode, }) return ( -
+
{!isEnv && !isChatVar && ( <> diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx index dc25184f5a..1a609c58f5 100644 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx @@ -179,7 +179,7 @@ const InputVarList: FC = ({
{isString && ( diff --git a/web/app/signin/_header.tsx b/web/app/signin/_header.tsx index c7b1e67092..d2e3236d1d 100644 --- a/web/app/signin/_header.tsx +++ b/web/app/signin/_header.tsx @@ -2,6 +2,8 @@ import React from 'react' import { useContext } from 'use-context-selector' import Select from '@/app/components/base/select/locale' +import ThemeSelector from '@/app/components/base/theme-selector' +import Divider from '@/app/components/base/divider' import { languages } from '@/i18n/language' import type { Locale } from '@/i18n' import I18n from '@/context/i18n' @@ -10,17 +12,22 @@ import LogoSite from '@/app/components/base/logo/logo-site' const Header = () => { const { locale, setLocaleOnClient } = useContext(I18n) - return
- - item.supported)} + onChange={(value) => { + setLocaleOnClient(value as Locale) + }} + /> + + +
+
+ ) } export default Header diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 8756095075..b194f6ed15 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -1,4 +1,10 @@ const translation = { + theme: { + theme: 'Theme', + light: 'light', + dark: 'dark', + auto: 'system', + }, api: { success: 'Success', actionSuccess: 'Action succeeded', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index f2d7e0b4f0..a962eefa2b 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -1,4 +1,10 @@ const translation = { + theme: { + theme: 'テーマ', + light: '明るい', + dark: '暗い', + auto: 'システム', + }, api: { success: '成功', actionSuccess: 'アクションが成功しました', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 22211a92aa..9ed961feab 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -1,4 +1,10 @@ const translation = { + theme: { + theme: '主题', + light: '浅色', + dark: '深色', + auto: '自动', + }, api: { success: '成功', actionSuccess: '操作成功', From 9e795374bcef350101f0c3e0f7087723a099ab05 Mon Sep 17 00:00:00 2001 From: Bowen Liang Date: Wed, 14 May 2025 09:54:47 +0800 Subject: [PATCH 04/16] chore: add dev scripts to start api and worker service with uv run (#19633) --- dev/start-api | 10 ++++++++++ dev/start-worker | 11 +++++++++++ 2 files changed, 21 insertions(+) create mode 100755 dev/start-api create mode 100755 dev/start-worker diff --git a/dev/start-api b/dev/start-api new file mode 100755 index 0000000000..0b50ad0d0a --- /dev/null +++ b/dev/start-api @@ -0,0 +1,10 @@ +#!/bin/bash + +set -x + +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + + +uv --directory api run \ + flask run --host 0.0.0.0 --port=5001 --debug diff --git a/dev/start-worker b/dev/start-worker new file mode 100755 index 0000000000..7007b265e0 --- /dev/null +++ b/dev/start-worker @@ -0,0 +1,11 @@ +#!/bin/bash + +set -x + +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +cd "$SCRIPT_DIR/.." + + +uv --directory api run \ + celery -A app.celery worker \ + -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion From 3c953cb0efd4465f092ad43ec95eae99becea022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AF=8C=E5=AE=9D?= <74889154+tinet-dongfb@users.noreply.github.com> Date: Wed, 14 May 2025 10:17:15 +0800 Subject: [PATCH 05/16] fix:#18447:When variables in the workflow are deleted or modified, it is impossible to visually identify subsequent node errors (#18554) Co-authored-by: crazywoola <427733928@qq.com> --- web/app/components/workflow/constants.ts | 16 ++++ .../components/workflow/header/checklist.tsx | 10 +++ .../workflow/hooks/use-checklist.ts | 58 +++++++++++++- .../nodes/_base/components/variable/utils.ts | 2 +- .../workflow/nodes/answer/default.ts | 16 +++- .../workflow/nodes/assigner/default.ts | 20 ++++- .../components/workflow/nodes/code/default.ts | 16 +++- .../nodes/document-extractor/default.ts | 16 +++- .../components/workflow/nodes/http/default.ts | 54 ++++++++++++- .../workflow/nodes/if-else/default.ts | 37 +++++++++ .../workflow/nodes/iteration/default.ts | 16 +++- .../nodes/knowledge-retrieval/default.ts | 16 +++- .../workflow/nodes/list-operator/default.ts | 15 +++- .../components/workflow/nodes/llm/default.ts | 35 ++++++++- .../nodes/parameter-extractor/default.ts | 27 ++++++- .../nodes/question-classifier/default.ts | 27 ++++++- .../nodes/template-transform/default.ts | 16 +++- .../components/workflow/nodes/tool/default.ts | 32 +++++++- .../nodes/variable-assigner/default.ts | 14 ++++ web/app/components/workflow/types.ts | 1 + .../workflow/utils/workflow-init.spec.ts | 12 +++ web/app/components/workflow/utils/workflow.ts | 76 ++++++++++++++++++- web/config/index.ts | 2 + web/i18n/en-US/workflow.ts | 3 + web/i18n/ja-JP/workflow.ts | 3 + web/i18n/zh-Hans/workflow.ts | 3 + 26 files changed, 522 insertions(+), 21 deletions(-) diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index cdfd963cfa..1b54add0ce 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -31,6 +31,7 @@ type NodesExtraData = { getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[] getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[] checkValid: any + checkVarValid?: any } export const NODES_EXTRA_DATA: Record = { [BlockEnum.Start]: { @@ -59,6 +60,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: AnswerDefault.getAvailablePrevNodes, getAvailableNextNodes: AnswerDefault.getAvailableNextNodes, checkValid: AnswerDefault.checkValid, + checkVarValid: AnswerDefault.checkVarValid, }, [BlockEnum.LLM]: { author: 'Dify', @@ -68,6 +70,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: LLMDefault.getAvailablePrevNodes, getAvailableNextNodes: LLMDefault.getAvailableNextNodes, checkValid: LLMDefault.checkValid, + checkVarValid: LLMDefault.checkVarValid, }, [BlockEnum.KnowledgeRetrieval]: { author: 'Dify', @@ -77,6 +80,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: KnowledgeRetrievalDefault.getAvailablePrevNodes, getAvailableNextNodes: KnowledgeRetrievalDefault.getAvailableNextNodes, checkValid: KnowledgeRetrievalDefault.checkValid, + checkVarValid: KnowledgeRetrievalDefault.checkVarValid, }, [BlockEnum.IfElse]: { author: 'Dify', @@ -86,6 +90,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: IfElseDefault.getAvailablePrevNodes, getAvailableNextNodes: IfElseDefault.getAvailableNextNodes, checkValid: IfElseDefault.checkValid, + checkVarValid: IfElseDefault.checkVarValid, }, [BlockEnum.Iteration]: { author: 'Dify', @@ -95,6 +100,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: IterationDefault.getAvailablePrevNodes, getAvailableNextNodes: IterationDefault.getAvailableNextNodes, checkValid: IterationDefault.checkValid, + checkVarValid: IterationDefault.checkVarValid, }, [BlockEnum.IterationStart]: { author: 'Dify', @@ -140,6 +146,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: CodeDefault.getAvailablePrevNodes, getAvailableNextNodes: CodeDefault.getAvailableNextNodes, checkValid: CodeDefault.checkValid, + checkVarValid: CodeDefault.checkVarValid, }, [BlockEnum.TemplateTransform]: { author: 'Dify', @@ -149,6 +156,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: TemplateTransformDefault.getAvailablePrevNodes, getAvailableNextNodes: TemplateTransformDefault.getAvailableNextNodes, checkValid: TemplateTransformDefault.checkValid, + checkVarValid: TemplateTransformDefault.checkVarValid, }, [BlockEnum.QuestionClassifier]: { author: 'Dify', @@ -158,6 +166,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: QuestionClassifierDefault.getAvailablePrevNodes, getAvailableNextNodes: QuestionClassifierDefault.getAvailableNextNodes, checkValid: QuestionClassifierDefault.checkValid, + checkVarValid: QuestionClassifierDefault.checkVarValid, }, [BlockEnum.HttpRequest]: { author: 'Dify', @@ -167,6 +176,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: HttpRequestDefault.getAvailablePrevNodes, getAvailableNextNodes: HttpRequestDefault.getAvailableNextNodes, checkValid: HttpRequestDefault.checkValid, + checkVarValid: HttpRequestDefault.checkVarValid, }, [BlockEnum.VariableAssigner]: { author: 'Dify', @@ -185,6 +195,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: AssignerDefault.getAvailablePrevNodes, getAvailableNextNodes: AssignerDefault.getAvailableNextNodes, checkValid: AssignerDefault.checkValid, + checkVarValid: AssignerDefault.checkVarValid, }, [BlockEnum.VariableAggregator]: { author: 'Dify', @@ -194,6 +205,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: VariableAssignerDefault.getAvailablePrevNodes, getAvailableNextNodes: VariableAssignerDefault.getAvailableNextNodes, checkValid: VariableAssignerDefault.checkValid, + checkVarValid: VariableAssignerDefault.checkVarValid, }, [BlockEnum.ParameterExtractor]: { author: 'Dify', @@ -203,6 +215,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: ParameterExtractorDefault.getAvailablePrevNodes, getAvailableNextNodes: ParameterExtractorDefault.getAvailableNextNodes, checkValid: ParameterExtractorDefault.checkValid, + checkVarValid: ParameterExtractorDefault.checkVarValid, }, [BlockEnum.Tool]: { author: 'Dify', @@ -212,6 +225,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: ToolDefault.getAvailablePrevNodes, getAvailableNextNodes: ToolDefault.getAvailableNextNodes, checkValid: ToolDefault.checkValid, + checkVarValid: ToolDefault.checkVarValid, }, [BlockEnum.DocExtractor]: { author: 'Dify', @@ -221,6 +235,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: DocExtractorDefault.getAvailablePrevNodes, getAvailableNextNodes: DocExtractorDefault.getAvailableNextNodes, checkValid: DocExtractorDefault.checkValid, + checkVarValid: DocExtractorDefault.checkVarValid, }, [BlockEnum.ListFilter]: { author: 'Dify', @@ -230,6 +245,7 @@ export const NODES_EXTRA_DATA: Record = { getAvailablePrevNodes: ListFilterDefault.getAvailablePrevNodes, getAvailableNextNodes: ListFilterDefault.getAvailableNextNodes, checkValid: ListFilterDefault.checkValid, + checkVarValid: ListFilterDefault.checkVarValid, }, [BlockEnum.Agent]: { author: 'Dify', diff --git a/web/app/components/workflow/header/checklist.tsx b/web/app/components/workflow/header/checklist.tsx index 9da16c59c6..374726cbeb 100644 --- a/web/app/components/workflow/header/checklist.tsx +++ b/web/app/components/workflow/header/checklist.tsx @@ -140,6 +140,16 @@ const WorkflowChecklist = ({
) } + { + node.varErrorMessage?.map((errorMessage: string) => ( +
+
+ + {errorMessage} +
+
+ )) + }
)) diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index c1b0189b8b..4b0fcca192 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -15,6 +15,7 @@ import { useStore } from '../store' import { getToolCheckParams, getValidTreeNodes, + transformStartNodeVariables, } from '../utils' import { CUSTOM_NODE, @@ -45,6 +46,9 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { const { data: strategyProviders } = useStrategyProviders() const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail) + const chatVarList = useStore(s => s.conversationVariables) + const environmentVariables = useStore(s => s.environmentVariables) + const getCheckData = useCallback((data: CommonNodeType<{}>) => { let checkData = data if (data.type === BlockEnum.KnowledgeRetrieval) { @@ -64,7 +68,10 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { const needWarningNodes = useMemo(() => { const list = [] - const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges) + const { validNodes } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges, true) + + const allVariablesMap = transformStartNodeVariables(chatVarList, environmentVariables) + const errMessageMap = new Map() for (let i = 0; i < nodes.length; i++) { const node = nodes[i] @@ -110,8 +117,32 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { toolIcon, unConnected: !validNodes.find(n => n.id === node.id), errorMessage, + varErrorMessage: [], }) } + errMessageMap.set(node.id, list[list.length - 1]) + if (nodesExtraData[node.data.type as BlockEnum].checkVarValid) { + const { errorMessage: varErrorMessages } = nodesExtraData[node.data.type as BlockEnum].checkVarValid(node.data, { ...allVariablesMap, ...node._parentOutputVarMap }, t) + + if (varErrorMessages?.length) { + const errMessage = errMessageMap.get(node.id) + if (errMessage) { + errMessage.varErrorMessage = varErrorMessages + } + else { + list.push({ + id: node.id, + type: node.data.type, + title: node.data.title, + toolIcon, + unConnected: !validNodes.find(n => n.id === node.id), + errorMessage: '', + varErrorMessage: varErrorMessages, + }) + errMessageMap.set(node.id, list[list.length - 1]) + } + } + } } } @@ -133,8 +164,13 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { }) } + for (let i = 0; i < validNodes.length; i++) { + const node = validNodes[i] + delete node._parentOutputVarMap + } + return list - }, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData]) + }, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData, chatVarList, environmentVariables]) return needWarningNodes } @@ -153,6 +189,9 @@ export const useChecklistBeforePublish = () => { const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail) const updateTime = useRef(0) + const chatVarList = useStore(s => s.conversationVariables) + const environmentVariables = useStore(s => s.environmentVariables) + const getCheckData = useCallback((data: CommonNodeType<{}>, datasets: DataSet[]) => { let checkData = data if (data.type === BlockEnum.KnowledgeRetrieval) { @@ -183,12 +222,15 @@ export const useChecklistBeforePublish = () => { const { validNodes, maxDepth, - } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges) + } = getValidTreeNodes(nodes.filter(node => node.type === CUSTOM_NODE), edges, true) if (maxDepth > MAX_TREE_DEPTH) { notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEPTH }) }) return false } + + const allVariablesMap = transformStartNodeVariables(chatVarList, environmentVariables) + // Before publish, we need to fetch datasets detail, in case of the settings of datasets have been changed const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval) const allDatasetIds = knowledgeRetrievalNodes.reduce((acc, node) => { @@ -239,6 +281,14 @@ export const useChecklistBeforePublish = () => { notify({ type: 'error', message: `[${node.data.title}] ${t('workflow.common.needConnectTip')}` }) return false } + + if (nodesExtraData[node.data.type as BlockEnum].checkVarValid) { + const { errorMessage: varErrorMessage } = nodesExtraData[node.data.type as BlockEnum].checkVarValid(node.data, { ...allVariablesMap, ...node._parentOutputVarMap }, t) + if (varErrorMessage?.length) { + notify({ type: 'error', message: `[${node.data.title}] ${varErrorMessage[0]}` }) + return false + } + } } if (isChatMode && !nodes.find(node => node.data.type === BlockEnum.Answer)) { @@ -252,7 +302,7 @@ export const useChecklistBeforePublish = () => { } return true - }, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData]) + }, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, chatVarList, environmentVariables]) return { handleCheckBeforePublish, diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index f804cf6902..751d438d68 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -164,7 +164,7 @@ const findExceptVarInObject = (obj: any, filterVar: (payload: Var, selector: Val return res } -const formatItem = ( +export const formatItem = ( item: any, isChatMode: boolean, filterVar: (payload: Var, selector: ValueSelector) => boolean, diff --git a/web/app/components/workflow/nodes/answer/default.ts b/web/app/components/workflow/nodes/answer/default.ts index 4ff6e49d7e..5dff505007 100644 --- a/web/app/components/workflow/nodes/answer/default.ts +++ b/web/app/components/workflow/nodes/answer/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByText } from '../../utils/workflow' import type { AnswerNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -31,6 +32,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: AnswerNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const answer_warnings = getNotExistVariablesByText(payload.answer || '', varMap) + if (answer_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.answer.answer')} ${t('workflow.common.referenceVar')}${answer_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...answer_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/assigner/default.ts b/web/app/components/workflow/nodes/assigner/default.ts index 6341305576..32dac73b36 100644 --- a/web/app/components/workflow/nodes/assigner/default.ts +++ b/web/app/components/workflow/nodes/assigner/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import { type AssignerNodeType, WriteMode } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' const i18nPrefix = 'workflow.errorMsg' @@ -47,6 +48,23 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + + checkVarValid(payload: AssignerNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + const variables_warnings = getNotExistVariablesByArray(payload.items.map(item => item.variable_selector ?? []) ?? [], varMap) + if (variables_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.assigner.assignedVariable')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`) + + const value_warnings = getNotExistVariablesByArray(payload.items.map(item => item.value ?? []) ?? [], varMap) + if (value_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.assigner.setVariable')} ${t('workflow.common.referenceVar')}${value_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...variables_warnings, ...value_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/code/default.ts b/web/app/components/workflow/nodes/code/default.ts index 5f90c18716..320cac59b4 100644 --- a/web/app/components/workflow/nodes/code/default.ts +++ b/web/app/components/workflow/nodes/code/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import { CodeLanguage, type CodeNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -37,7 +38,20 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: CodeNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + const variables_selector = payload.variables.map(v => v.value_selector) + const variables_selector_warnings = getNotExistVariablesByArray(variables_selector, varMap) + if (variables_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.code.inputVars')} ${t('workflow.common.referenceVar')}${variables_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: variables_selector_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/document-extractor/default.ts b/web/app/components/workflow/nodes/document-extractor/default.ts index e141844d19..57eb58b168 100644 --- a/web/app/components/workflow/nodes/document-extractor/default.ts +++ b/web/app/components/workflow/nodes/document-extractor/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { DocExtractorNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' const i18nPrefix = 'workflow.errorMsg' @@ -31,6 +32,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: DocExtractorNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + + const variables_warnings = getNotExistVariablesByArray([payload.variable_selector], varMap) + if (variables_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.docExtractor.inputVar')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: variables_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/http/default.ts b/web/app/components/workflow/nodes/http/default.ts index 1bd584eeb9..1c027a3fec 100644 --- a/web/app/components/workflow/nodes/http/default.ts +++ b/web/app/components/workflow/nodes/http/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import { AuthorizationType, BodyType, Method } from './types' import type { BodyPayload, HttpNodeType } from './types' import { @@ -50,8 +51,8 @@ const nodeDefault: NodeDefault = { errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.api') }) if (!errorMessages - && payload.body.type === BodyType.binary - && ((!(payload.body.data as BodyPayload)[0]?.file) || (payload.body.data as BodyPayload)[0]?.file?.length === 0) + && payload.body.type === BodyType.binary + && ((!(payload.body.data as BodyPayload)[0]?.file) || (payload.body.data as BodyPayload)[0]?.file?.length === 0) ) errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.binaryFileVariable') }) @@ -60,6 +61,53 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: HttpNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + const url_warnings = getNotExistVariablesByText(payload.url, varMap) + if (url_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.http.api')} ${t('workflow.common.referenceVar')}${url_warnings.join('、')}${t('workflow.common.noExist')}`) + + const headers_warnings = getNotExistVariablesByText(payload.headers, varMap) + if (headers_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.http.headers')} ${t('workflow.common.referenceVar')}${headers_warnings.join('、')}${t('workflow.common.noExist')}`) + + const params_warnings = getNotExistVariablesByText(payload.params, varMap) + if (params_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.http.params')} ${t('workflow.common.referenceVar')}${params_warnings.join('、')}${t('workflow.common.noExist')}`) + + const body_warnings: string[] = [] + + if ([BodyType.binary].includes(payload.body.type)) { + const body_data = payload.body.data as BodyPayload + body_data.forEach((item) => { + const key_warnings = getNotExistVariablesByText(item.key || '', varMap) + if (key_warnings.length) + body_warnings.push(...key_warnings) + const warnings = getNotExistVariablesByArray([item.file || []], varMap) + if (warnings.length) + body_warnings.push(...warnings) + }) + } + else { + const body_data = payload.body.data as BodyPayload + body_data.forEach((item) => { + const key_warnings = getNotExistVariablesByText(item.key || '', varMap) + if (key_warnings.length) + body_warnings.push(...key_warnings) + const value_warnings = getNotExistVariablesByText(item.value || '', varMap) + if (value_warnings.length) + body_warnings.push(...value_warnings) + }) + } + if (body_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.http.body')} ${t('workflow.common.referenceVar')}${body_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...url_warnings, ...headers_warnings, ...params_warnings, ...body_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/if-else/default.ts b/web/app/components/workflow/nodes/if-else/default.ts index 1be80592e5..53c139b4c0 100644 --- a/web/app/components/workflow/nodes/if-else/default.ts +++ b/web/app/components/workflow/nodes/if-else/default.ts @@ -1,4 +1,6 @@ +import type { Var } from '../../types' import { BlockEnum, type NodeDefault } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import { type IfElseNodeType, LogicalOperator } from './types' import { isEmptyRelatedOperator } from './utils' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -75,6 +77,41 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: IfElseNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const condition_variable_selector_warnings: string[] = [] + const condition_value_warnings: string[] = [] + payload.cases.forEach((caseItem) => { + caseItem.conditions.forEach((condition) => { + if (!condition.variable_selector) + return + const selector_warnings = getNotExistVariablesByArray([condition.variable_selector], varMap) + if (selector_warnings.length) + condition_variable_selector_warnings.push(...selector_warnings) + const value_warnings = Array.isArray(condition.value) ? getNotExistVariablesByArray([condition.value], varMap) : getNotExistVariablesByText(condition.value, varMap) + if (value_warnings.length) + condition_value_warnings.push(...value_warnings) + condition.sub_variable_condition?.conditions.forEach((subCondition) => { + const sub_variable_value_warnings = Array.isArray(subCondition.value) ? getNotExistVariablesByArray([subCondition.value], varMap) : getNotExistVariablesByText(subCondition.value, varMap) + if (sub_variable_value_warnings.length) + condition_value_warnings.push(...sub_variable_value_warnings) + }) + }) + }) + + if (condition_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.ifElse.condition')} ${t('workflow.common.referenceVar')}${condition_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + if (condition_value_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.ifElse.enterValue')} ${t('workflow.common.referenceVar')}${condition_value_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: condition_variable_selector_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/iteration/default.ts b/web/app/components/workflow/nodes/iteration/default.ts index 0ef8382abe..392c64137e 100644 --- a/web/app/components/workflow/nodes/iteration/default.ts +++ b/web/app/components/workflow/nodes/iteration/default.ts @@ -1,5 +1,6 @@ import { BlockEnum, ErrorHandleMode } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { IterationNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, @@ -58,6 +59,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: IterationNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + + const iterator_selector_warnings = getNotExistVariablesByArray([payload.iterator_selector], varMap) + if (iterator_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.iteration.input')} ${t('workflow.common.referenceVar')}${iterator_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: iterator_selector_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts index 09da8dd789..955e30d90f 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { KnowledgeRetrievalNodeType } from './types' import { checkoutRerankModelConfigedInRetrievalSettings } from './utils' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -52,6 +53,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: KnowledgeRetrievalNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const query_variable_selector_warnings = getNotExistVariablesByArray([payload.query_variable_selector], varMap) + if (query_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.knowledgeRetrieval.queryVariable')} ${t('workflow.common.referenceVar')}${query_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...query_variable_selector_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/list-operator/default.ts b/web/app/components/workflow/nodes/list-operator/default.ts index 0256cb8673..c2d15edc58 100644 --- a/web/app/components/workflow/nodes/list-operator/default.ts +++ b/web/app/components/workflow/nodes/list-operator/default.ts @@ -1,5 +1,6 @@ import { BlockEnum, VarType } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import { comparisonOperatorNotRequireValue } from '../if-else/utils' import { type ListFilterNodeType, OrderBy } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -60,6 +61,18 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: ListFilterNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const variable_warnings = getNotExistVariablesByArray([payload.variable], varMap) + if (variable_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.listFilter.inputVar')} ${t('workflow.common.referenceVar')}${variable_warnings.join('、')}${t('workflow.common.noExist')}`) + return { + isValid: true, + warning_vars: variable_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/llm/default.ts b/web/app/components/workflow/nodes/llm/default.ts index 92377f74b8..5625c85480 100644 --- a/web/app/components/workflow/nodes/llm/default.ts +++ b/web/app/components/workflow/nodes/llm/default.ts @@ -1,5 +1,7 @@ -import { BlockEnum, EditionType } from '../../types' +import type { Var } from '../../types' +import { BlockEnum, EditionType, VarType } from '../../types' import { type NodeDefault, type PromptItem, PromptRole } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import type { LLMNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -86,6 +88,37 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: LLMNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + const prompt_templates_warnings: string[] = [] + if (payload.context?.enabled && payload.context?.variable_selector?.length) { + const context_variable_selector_warnings = getNotExistVariablesByArray([payload.context.variable_selector], varMap) + if (context_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.context')} ${t('workflow.common.referenceVar')}${context_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + } + const prompt_templates = Array.isArray(payload.prompt_template) ? payload.prompt_template : [payload.prompt_template] as PromptItem[] + prompt_templates.forEach((v) => { + prompt_templates_warnings.push(...getNotExistVariablesByText(v.text, { context: { variable: 'context', type: VarType.string }, ...varMap })) + }) + if (prompt_templates_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.prompt')} ${t('workflow.common.referenceVar')}${prompt_templates_warnings.join('、')}${t('workflow.common.noExist')}`) + + const memory_query_prompt_template_warnings = getNotExistVariablesByText(payload.memory?.query_prompt_template || '', varMap) + if (memory_query_prompt_template_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.common.memories.title')}USER ${t('workflow.common.referenceVar')}${memory_query_prompt_template_warnings.join('、')}${t('workflow.common.noExist')}`) + + if (payload.vision?.enabled && payload.vision?.configs?.variable_selector?.length) { + const vision_variable_selector_warnings = getNotExistVariablesByArray([payload.vision.configs.variable_selector], varMap) + if (vision_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.vision')} ${t('workflow.common.referenceVar')}${vision_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + } + + return { + isValid: true, + warning_vars: [...prompt_templates_warnings, ...memory_query_prompt_template_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/parameter-extractor/default.ts b/web/app/components/workflow/nodes/parameter-extractor/default.ts index 0e3b707d30..5ab5384502 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/default.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import { type ParameterExtractorNodeType, ReasoningModeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' const i18nPrefix = 'workflow' @@ -64,6 +65,30 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: ParameterExtractorNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + + const variables_warnings = getNotExistVariablesByArray([payload.query], varMap) + if (variables_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.parameterExtractor.inputVar')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`) + + let vision_variable_warnings: string[] = [] + if (payload.vision?.configs?.variable_selector?.length) { + vision_variable_warnings = getNotExistVariablesByArray([payload.vision.configs.variable_selector], varMap) + if (vision_variable_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.vision')} ${t('workflow.common.referenceVar')}${vision_variable_warnings.join('、')}${t('workflow.common.noExist')}`) + } + + const instruction_warnings = getNotExistVariablesByText(payload.instruction, varMap) + if (instruction_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.parameterExtractor.instruction')} ${t('workflow.common.referenceVar')}${instruction_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...variables_warnings, ...vision_variable_warnings, ...instruction_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/question-classifier/default.ts b/web/app/components/workflow/nodes/question-classifier/default.ts index 2729c53f29..70410a5f18 100644 --- a/web/app/components/workflow/nodes/question-classifier/default.ts +++ b/web/app/components/workflow/nodes/question-classifier/default.ts @@ -1,5 +1,6 @@ -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' import { BlockEnum } from '../../types' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' import type { QuestionClassifierNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -71,6 +72,30 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: QuestionClassifierNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const query_variable_selector_warnings = getNotExistVariablesByArray([payload.query_variable_selector], varMap) + if (query_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.questionClassifiers.inputVars')} ${t('workflow.common.referenceVar')}${query_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + let vision_variable_selector_warnings: string[] = [] + if (payload.vision?.configs?.variable_selector?.length) { + vision_variable_selector_warnings = getNotExistVariablesByArray([payload.vision?.configs?.variable_selector], varMap) + if (vision_variable_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.llm.vision')} ${t('workflow.common.referenceVar')}${vision_variable_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + } + + const instruction_warnings: string[] = getNotExistVariablesByText(payload.instruction, varMap) + if (instruction_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.questionClassifiers.advancedSetting')}-${t('workflow.nodes.questionClassifiers.instruction')} ${t('workflow.common.referenceVar')}${instruction_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: [...query_variable_selector_warnings, ...vision_variable_selector_warnings, ...instruction_warnings], + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/template-transform/default.ts b/web/app/components/workflow/nodes/template-transform/default.ts index c698680342..25e5d7f961 100644 --- a/web/app/components/workflow/nodes/template-transform/default.ts +++ b/web/app/components/workflow/nodes/template-transform/default.ts @@ -1,5 +1,6 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { TemplateTransformNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' const i18nPrefix = 'workflow.errorMsg' @@ -33,6 +34,19 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: TemplateTransformNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + + const variables_selector = payload.variables.map(v => v.value_selector) + const variables_selector_warnings = getNotExistVariablesByArray(variables_selector, varMap) + if (variables_selector_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.templateTransform.inputVars')} ${t('workflow.common.referenceVar')}${variables_selector_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/tool/default.ts b/web/app/components/workflow/nodes/tool/default.ts index f245929684..782c575c8e 100644 --- a/web/app/components/workflow/nodes/tool/default.ts +++ b/web/app/components/workflow/nodes/tool/default.ts @@ -1,8 +1,9 @@ import { BlockEnum } from '../../types' -import type { NodeDefault } from '../../types' +import type { NodeDefault, Var } from '../../types' import type { ToolNodeType } from './types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { getNotExistVariablesByArray, getNotExistVariablesByText } from '../../utils/workflow' const i18nPrefix = 'workflow.errorMsg' @@ -63,6 +64,35 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: ToolNodeType, varMap: Record, t: any) { + const errorMessageArr = [] + const tool_parametersMap = payload.tool_parameters + const tool_parameters_array = Object.values(tool_parametersMap) + const tool_parameters_warnings: string[] = [] + tool_parameters_array?.forEach((item) => { + if (!item.value) + return + if (Array.isArray(item.value)) { + const warnings = getNotExistVariablesByArray([item.value], varMap) + if (warnings.length) + tool_parameters_warnings.push(...warnings) + return + } + if (typeof item.value === 'string') { + const warnings = getNotExistVariablesByText(item.value, varMap) + if (warnings.length) + tool_parameters_warnings.push(...warnings) + } + }) + if (tool_parameters_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.tool.inputVars')} ${t('workflow.common.referenceVar')}${tool_parameters_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: tool_parameters_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/nodes/variable-assigner/default.ts b/web/app/components/workflow/nodes/variable-assigner/default.ts index 60c7c27969..8482406602 100644 --- a/web/app/components/workflow/nodes/variable-assigner/default.ts +++ b/web/app/components/workflow/nodes/variable-assigner/default.ts @@ -1,5 +1,7 @@ +import type { Var } from '../../types' import { type NodeDefault, VarType } from '../../types' import { BlockEnum } from '../../types' +import { getNotExistVariablesByArray } from '../../utils/workflow' import type { VariableAssignerNodeType } from './types' import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' @@ -54,6 +56,18 @@ const nodeDefault: NodeDefault = { errorMessage: errorMessages, } }, + checkVarValid(payload: VariableAssignerNodeType, varMap: Record, t: any) { + const errorMessageArr: string[] = [] + const variables_warnings = getNotExistVariablesByArray(payload.variables ?? [], varMap) + if (variables_warnings.length) + errorMessageArr.push(`${t('workflow.nodes.variableAssigner.title')} ${t('workflow.common.referenceVar')}${variables_warnings.join('、')}${t('workflow.common.noExist')}`) + + return { + isValid: true, + warning_vars: variables_warnings, + errorMessage: errorMessageArr, + } + }, } export default nodeDefault diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 884bdfbd10..1e6186924e 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -297,6 +297,7 @@ export type NodeDefault = { getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[] getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[] checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean; errorMessage?: string } + checkVarValid?: (payload: T, varMap: Record, t: any,) => { isValid: boolean; errorMessage?: string[] } } export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void diff --git a/web/app/components/workflow/utils/workflow-init.spec.ts b/web/app/components/workflow/utils/workflow-init.spec.ts index 8b7bdfaa92..d96e54a76a 100644 --- a/web/app/components/workflow/utils/workflow-init.spec.ts +++ b/web/app/components/workflow/utils/workflow-init.spec.ts @@ -5,6 +5,18 @@ import type { } from '@/app/components/workflow/types' import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' +jest.mock('ky', () => ({ + __esModule: true, + default: { + create: jest.fn(), + }, +})) + +jest.mock('lodash-es/groupBy', () => ({ + __esModule: true, + default: jest.fn(), +})) + describe('preprocessNodesAndEdges', () => { it('process nodes without iteration node or loop node should return origin nodes and edges.', () => { const nodes = [ diff --git a/web/app/components/workflow/utils/workflow.ts b/web/app/components/workflow/utils/workflow.ts index 88c31f09b5..739908646a 100644 --- a/web/app/components/workflow/utils/workflow.ts +++ b/web/app/components/workflow/utils/workflow.ts @@ -10,14 +10,20 @@ import { uniqBy, } from 'lodash-es' import type { + ConversationVariable, Edge, + EnvironmentVariable, Node, + Var, } from '../types' import { BlockEnum, } from '../types' import type { IterationNodeType } from '../nodes/iteration/types' import type { LoopNodeType } from '../nodes/loop/types' +import { VAR_REGEX_TEXT } from '@/config' +import { formatItem } from '../nodes/_base/components/variable/utils' +import type { StructuredOutput } from '../nodes/llm/types' export const canRunBySingle = (nodeType: BlockEnum) => { return nodeType === BlockEnum.LLM @@ -86,7 +92,17 @@ export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSo return nodesConnectedSourceOrTargetHandleIdsMap } -export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => { +function getParentOutputVarMap(item: Var, path: string, varMap: Record) { + if (!item.children || (Array.isArray(item.children) && !item.children.length) || ((item.children as StructuredOutput).schema)) + return + (item.children as Var[]).forEach((child) => { + const newPath = `${path}.${child.variable}` + varMap[newPath] = child + getParentOutputVarMap(child, newPath, varMap) + }) +} + +export const getValidTreeNodes = (nodes: Node[], edges: Edge[], isCollectVar?: boolean) => { const startNode = nodes.find(node => node.data.type === BlockEnum.Start) if (!startNode) { @@ -109,6 +125,19 @@ export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => { outgoers.forEach((outgoer) => { list.push(outgoer) + if (isCollectVar) { + const nodeObj = formatItem(root, false, () => true) + const varMap = {} as Record + nodeObj.vars.forEach((item) => { + if (item.variable.startsWith('sys.')) + return + const newPath = `${nodeObj.nodeId}.${item.variable}` + varMap[newPath] = item + getParentOutputVarMap(item, newPath, varMap) + }) + outgoer._parentOutputVarMap = { ...(root._parentOutputVarMap ?? {}), ...varMap } + } + if (outgoer.data.type === BlockEnum.Iteration) list.push(...nodes.filter(node => node.parentId === outgoer.id)) if (outgoer.data.type === BlockEnum.Loop) @@ -327,3 +356,48 @@ export const getParallelInfo = (nodes: Node[], edges: Edge[], parentNodeId?: str export const hasErrorHandleNode = (nodeType?: BlockEnum) => { return nodeType === BlockEnum.LLM || nodeType === BlockEnum.Tool || nodeType === BlockEnum.HttpRequest || nodeType === BlockEnum.Code } + +export const transformStartNodeVariables = (chatVarList: ConversationVariable[], environmentVariables: EnvironmentVariable[]) => { + const variablesMap: Record = {} + chatVarList.forEach((variable) => { + variablesMap[`conversation.${variable.name}`] = variable + }) + environmentVariables.forEach((variable) => { + variablesMap[`env.${variable.name}`] = variable + }) + return variablesMap +} + +export const getNotExistVariablesByText = (text: string, varMap: Record) => { + const var_warnings: string[] = [] + text?.replace(VAR_REGEX_TEXT, (str, id_name) => { + if (id_name.startsWith('sys.')) + return str + if (varMap[id_name]) + return str + const arr = id_name.split('.') + arr.shift() + var_warnings.push(arr.join('.')) + return str + }) + return var_warnings +} + +export const getNotExistVariablesByArray = (array: string[][], varMap: Record) => { + if (!array.length) + return [] + const var_warnings: string[] = [] + array.forEach((item) => { + if (!item.length) + return + if (['sys'].includes(item[0])) + return + const var_warning = varMap[item.join('.')] + if (var_warning) + return + const arr = [...item] + arr.shift() + var_warnings.push(arr.join('.')) + }) + return var_warnings +} diff --git a/web/config/index.ts b/web/config/index.ts index 4d308807c5..4ddf30c2e4 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -282,6 +282,8 @@ Thought: {{agent_scratchpad}} export const VAR_REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_]\w{0,29}){1,10}#)\}\}/gi +export const VAR_REGEX_TEXT = /\{\{#([a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*)#\}\}/gi + export const resetReg = () => VAR_REGEX.lastIndex = 0 export let textGenerationTimeoutMs = 60000 diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index f7121a7a8a..009cfd4326 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -113,6 +113,8 @@ const translation = { addFailureBranch: 'Add Fail Branch', loadMore: 'Load More', noHistory: 'No History', + referenceVar: 'Reference Variable', + noExist: 'No such variable', }, env: { envPanelTitle: 'Environment Variables', @@ -596,6 +598,7 @@ const translation = { selectVariable: 'Select variable...', addSubVariable: 'Sub Variable', select: 'Select', + condition: 'Condition', }, variableAssigner: { title: 'Assign variables', diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index 5fc9a81875..46a20efa26 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -113,6 +113,8 @@ const translation = { addFailureBranch: '失敗ブランチを追加', loadMore: 'さらに読み込む', noHistory: '履歴がありません', + referenceVar: '参照変数', + noExist: '存在しません', }, env: { envPanelTitle: '環境変数', @@ -596,6 +598,7 @@ const translation = { }, select: '選ぶ', addSubVariable: 'サブ変数', + condition: '条件', }, variableAssigner: { title: '変数を代入する', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index ab77e0ef8d..74b948837e 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -113,6 +113,8 @@ const translation = { openInExplore: '在“探索”中打开', loadMore: '加载更多', noHistory: '没有历史版本', + referenceVar: '引用变量', + noExist: '不存在', }, env: { envPanelTitle: '环境变量', @@ -597,6 +599,7 @@ const translation = { selectVariable: '选择变量', addSubVariable: '添加子变量', select: '选择', + condition: '条件', }, variableAssigner: { title: '变量赋值', From ff20b560748722dfb1aaa8d3105af1f747783b0e Mon Sep 17 00:00:00 2001 From: lexmin0412 Date: Wed, 14 May 2025 10:43:36 +0800 Subject: [PATCH 06/16] Add `/site` API (#19631) --- api/controllers/common/fields.py | 19 ++++++ api/controllers/service_api/__init__.py | 2 +- api/controllers/service_api/app/site.py | 30 ++++++++++ .../develop/template/template.en.mdx | 59 ++++++++++++++++++ .../develop/template/template.ja.mdx | 59 ++++++++++++++++++ .../develop/template/template.zh.mdx | 59 +++++++++++++++++- .../template/template_advanced_chat.en.mdx | 58 ++++++++++++++++++ .../template/template_advanced_chat.ja.mdx | 60 +++++++++++++++++++ .../template/template_advanced_chat.zh.mdx | 58 +++++++++++++++++- .../develop/template/template_chat.en.mdx | 58 ++++++++++++++++++ .../develop/template/template_chat.ja.mdx | 60 +++++++++++++++++++ .../develop/template/template_chat.zh.mdx | 60 +++++++++++++++++++ .../develop/template/template_workflow.en.mdx | 53 ++++++++++++++++ .../develop/template/template_workflow.ja.mdx | 54 +++++++++++++++++ .../develop/template/template_workflow.zh.mdx | 54 +++++++++++++++++ 15 files changed, 740 insertions(+), 3 deletions(-) create mode 100644 api/controllers/service_api/app/site.py diff --git a/api/controllers/common/fields.py b/api/controllers/common/fields.py index 79869916ed..3466eea1f6 100644 --- a/api/controllers/common/fields.py +++ b/api/controllers/common/fields.py @@ -1,5 +1,7 @@ from flask_restful import fields +from libs.helper import AppIconUrlField + parameters__system_parameters = { "image_file_size_limit": fields.Integer, "video_file_size_limit": fields.Integer, @@ -22,3 +24,20 @@ parameters_fields = { "file_upload": fields.Raw, "system_parameters": fields.Nested(parameters__system_parameters), } + +site_fields = { + "title": fields.String, + "chat_color_theme": fields.String, + "chat_color_theme_inverted": fields.Boolean, + "icon_type": fields.String, + "icon": fields.String, + "icon_background": fields.String, + "icon_url": AppIconUrlField, + "description": fields.String, + "copyright": fields.String, + "privacy_policy": fields.String, + "custom_disclaimer": fields.String, + "default_language": fields.String, + "show_workflow_steps": fields.Boolean, + "use_icon_as_answer_icon": fields.Boolean, +} diff --git a/api/controllers/service_api/__init__.py b/api/controllers/service_api/__init__.py index d97074e8b9..d964e27819 100644 --- a/api/controllers/service_api/__init__.py +++ b/api/controllers/service_api/__init__.py @@ -6,6 +6,6 @@ bp = Blueprint("service_api", __name__, url_prefix="/v1") api = ExternalApi(bp) from . import index -from .app import annotation, app, audio, completion, conversation, file, message, workflow +from .app import annotation, app, audio, completion, conversation, file, message, site, workflow from .dataset import dataset, document, hit_testing, metadata, segment, upload_file from .workspace import models diff --git a/api/controllers/service_api/app/site.py b/api/controllers/service_api/app/site.py new file mode 100644 index 0000000000..e752dfee30 --- /dev/null +++ b/api/controllers/service_api/app/site.py @@ -0,0 +1,30 @@ +from flask_restful import Resource, marshal_with +from werkzeug.exceptions import Forbidden + +from controllers.common import fields +from controllers.service_api import api +from controllers.service_api.wraps import validate_app_token +from extensions.ext_database import db +from models.account import TenantStatus +from models.model import App, Site + + +class AppSiteApi(Resource): + """Resource for app sites.""" + + @validate_app_token + @marshal_with(fields.site_fields) + def get(self, app_model: App): + """Retrieve app site info.""" + site = db.session.query(Site).filter(Site.app_id == app_model.id).first() + + if not site: + raise Forbidden() + + if app_model.tenant.status == TenantStatus.ARCHIVE: + raise Forbidden() + + return site + + +api.add_resource(AppSiteApi, "/site") diff --git a/web/app/components/develop/template/template.en.mdx b/web/app/components/develop/template/template.en.mdx index 43dc226107..8212e800f7 100755 --- a/web/app/components/develop/template/template.en.mdx +++ b/web/app/components/develop/template/template.en.mdx @@ -647,3 +647,62 @@ The text generation application offers non-session support and is ideal for tran +--- + + + + + Used to get the WebApp settings of the application. + ### Response + - `title` (string) WebApp name + - `chat_color_theme` (string) Chat color theme, in hex format + - `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted + - `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture + - `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL. + - `icon_background` (string) Background color in hex format + - `icon_url` (string) Icon URL + - `description` (string) Description + - `copyright` (string) Copyright information + - `privacy_policy` (string) Privacy policy link + - `custom_disclaimer` (string) Custom disclaimer + - `default_language` (string) Default language + - `show_workflow_steps` (bool) Whether to show workflow details + - `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template.ja.mdx b/web/app/components/develop/template/template.ja.mdx index 2c676e392f..5061792caf 100755 --- a/web/app/components/develop/template/template.ja.mdx +++ b/web/app/components/develop/template/template.ja.mdx @@ -645,3 +645,62 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from +--- + + + + + アプリのWebApp設定を取得するために使用します。 + ### レスポンス + - `title` (string) WebApp名 + - `chat_color_theme` (string) チャットの色テーマ、16進数形式 + - `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか + - `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像 + - `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL + - `icon_background` (string) 16進数形式の背景色 + - `icon_url` (string) アイコンのURL + - `description` (string) 説明 + - `copyright` (string) 著作権情報 + - `privacy_policy` (string) プライバシーポリシーのリンク + - `custom_disclaimer` (string) カスタム免責事項 + - `default_language` (string) デフォルト言語 + - `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか + - `use_icon_as_answer_icon` (bool) WebAppのアイコンをチャット内の🤖に置き換えるかどうか + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template.zh.mdx b/web/app/components/develop/template/template.zh.mdx index da9aa43204..df83684c06 100755 --- a/web/app/components/develop/template/template.zh.mdx +++ b/web/app/components/develop/template/template.zh.mdx @@ -507,7 +507,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - --- --- + + + + 用于获取应用的 WebApp 设置 + ### Response + - `title` (string) WebApp 名称 + - `chat_color_theme` (string) 聊天颜色主题, hex 格式 + - `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转 + - `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片 + - `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL + - `icon_background` (string) hex 格式的背景色 + - `icon_url` (string) 图标 URL + - `description` (string) 描述 + - `copyright` (string) 版权信息 + - `privacy_policy` (string) 隐私政策链接 + - `custom_disclaimer` (string) 自定义免责声明 + - `default_language` (string) 默认语言 + - `show_workflow_steps` (bool) 是否显示工作流详情 + - `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + + + +___ + --- + + + + Used to get the WebApp settings of the application. + ### Response + - `title` (string) WebApp name + - `chat_color_theme` (string) Chat color theme, in hex format + - `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted + - `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture + - `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL + - `icon_background` (string) Background color in hex format + - `icon_url` (string) Icon URL + - `description` (string) Description + - `copyright` (string) Copyright information + - `privacy_policy` (string) Privacy policy link + - `custom_disclaimer` (string) Custom disclaimer + - `default_language` (string) Default language + - `show_workflow_steps` (bool) Whether to show workflow details + - `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + + + +___ + + +--- + + + + + アプリのWebApp設定を取得するために使用します。 + ### 応答 + - `title` (string) WebApp名 + - `chat_color_theme` (string) チャットの色テーマ、16進数形式 + - `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか + - `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像 + - `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL + - `icon_background` (string) 16進数形式の背景色 + - `icon_url` (string) アイコンのURL + - `description` (string) 説明 + - `copyright` (string) 著作権情報 + - `privacy_policy` (string) プライバシーポリシーのリンク + - `custom_disclaimer` (string) カスタム免責事項 + - `default_language` (string) デフォルト言語 + - `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか + - `use_icon_as_answer_icon` (bool) WebAppのアイコンをチャット内の🤖に置き換えるかどうか + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template_advanced_chat.zh.mdx b/web/app/components/develop/template/template_advanced_chat.zh.mdx index f8d5934974..9077f5492f 100755 --- a/web/app/components/develop/template/template_advanced_chat.zh.mdx +++ b/web/app/components/develop/template/template_advanced_chat.zh.mdx @@ -1329,7 +1329,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --- ---- + + + + 用于获取应用的 WebApp 设置 + ### Response + - `title` (string) WebApp 名称 + - `chat_color_theme` (string) 聊天颜色主题, hex 格式 + - `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转 + - `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片 + - `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL + - `icon_background` (string) hex 格式的背景色 + - `icon_url` (string) 图标 URL + - `description` (string) 描述 + - `copyright` (string) 版权信息 + - `privacy_policy` (string) 隐私政策链接 + - `custom_disclaimer` (string) 自定义免责声明 + - `default_language` (string) 默认语言 + - `show_workflow_steps` (bool) 是否显示工作流详情 + - `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + + + +___ --- + + + + Used to get the WebApp settings of the application. + ### Response + - `title` (string) WebApp name + - `chat_color_theme` (string) Chat color theme, in hex format + - `chat_color_theme_inverted` (bool) Whether the chat color theme is inverted + - `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture + - `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL + - `icon_background` (string) Background color in hex format + - `icon_url` (string) Icon URL + - `description` (string) Description + - `copyright` (string) Copyright information + - `privacy_policy` (string) Privacy policy link + - `custom_disclaimer` (string) Custom disclaimer + - `default_language` (string) Default language + - `show_workflow_steps` (bool) Whether to show workflow details + - `use_icon_as_answer_icon` (bool) Whether to replace 🤖 in chat with the WebApp icon + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + + + +___ + + +--- + + + + + アプリのWebApp設定を取得するために使用します。 + ### 応答 + - `title` (string) WebApp名 + - `chat_color_theme` (string) チャットの色テーマ、16進数形式 + - `chat_color_theme_inverted` (bool) チャットの色テーマを反転するかどうか + - `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像 + - `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL + - `icon_background` (string) 16進数形式の背景色 + - `icon_url` (string) アイコンのURL + - `description` (string) 説明 + - `copyright` (string) 著作権情報 + - `privacy_policy` (string) プライバシーポリシーのリンク + - `custom_disclaimer` (string) カスタム免責事項 + - `default_language` (string) デフォルト言語 + - `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか + - `use_icon_as_answer_icon` (bool) WebAppのアイコンをチャット内の🤖に置き換えるかどうか + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template_chat.zh.mdx b/web/app/components/develop/template/template_chat.zh.mdx index 99c9876546..866170027f 100644 --- a/web/app/components/develop/template/template_chat.zh.mdx +++ b/web/app/components/develop/template/template_chat.zh.mdx @@ -1332,3 +1332,63 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' + +--- + + + + + 用于获取应用的 WebApp 设置 + ### Response + - `title` (string) WebApp 名称 + - `chat_color_theme` (string) 聊天颜色主题, hex 格式 + - `chat_color_theme_inverted` (bool) 聊天颜色主题是否反转 + - `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片 + - `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL + - `icon_background` (string) hex 格式的背景色 + - `icon_url` (string) 图标 URL + - `description` (string) 描述 + - `copyright` (string) 版权信息 + - `privacy_policy` (string) 隐私政策链接 + - `custom_disclaimer` (string) 自定义免责声明 + - `default_language` (string) 默认语言 + - `show_workflow_steps` (bool) 是否显示工作流详情 + - `use_icon_as_answer_icon` (bool) 是否使用 WebApp 图标替换聊天中的 🤖 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "chat_color_theme": "#ff4a4a", + "chat_color_theme_inverted": false, + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + "use_icon_as_answer_icon": false, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template_workflow.en.mdx b/web/app/components/develop/template/template_workflow.en.mdx index 556b306a64..2afcedc88c 100644 --- a/web/app/components/develop/template/template_workflow.en.mdx +++ b/web/app/components/develop/template/template_workflow.en.mdx @@ -737,3 +737,56 @@ Workflow applications offers non-session support and is ideal for translation, a +--- + + + + + Used to get the WebApp settings of the application. + ### Response + - `title` (string) WebApp name + - `icon_type` (string) Icon type, `emoji` - emoji, `image` - picture + - `icon` (string) Icon. If it's `emoji` type, it's an emoji symbol; if it's `image` type, it's an image URL. + - `icon_background` (string) Background color in hex format + - `icon_url` (string) Icon URL + - `description` (string) Description + - `copyright` (string) Copyright information + - `privacy_policy` (string) Privacy policy link + - `custom_disclaimer` (string) Custom disclaimer + - `default_language` (string) Default language + - `show_workflow_steps` (bool) Whether to show workflow details + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + } + ``` + + + +___ diff --git a/web/app/components/develop/template/template_workflow.ja.mdx b/web/app/components/develop/template/template_workflow.ja.mdx index 3f33be58b9..b6afea9bc4 100644 --- a/web/app/components/develop/template/template_workflow.ja.mdx +++ b/web/app/components/develop/template/template_workflow.ja.mdx @@ -740,3 +740,57 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from +——— + + + + + アプリのWebApp設定を取得するために使用します。 + ### 応答 + - `title` (string) WebApp名 + - `icon_type` (string) アイコンタイプ、`emoji`-絵文字、`image`-画像 + - `icon` (string) アイコン。`emoji`タイプの場合は絵文字、`image`タイプの場合は画像URL + - `icon_background` (string) 16進数形式の背景色 + - `icon_url` (string) アイコンのURL + - `description` (string) 説明 + - `copyright` (string) 著作権情報 + - `privacy_policy` (string) プライバシーポリシーのリンク + - `custom_disclaimer` (string) カスタム免責事項 + - `default_language` (string) デフォルト言語 + - `show_workflow_steps` (bool) ワークフローの詳細を表示するかどうか + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + } + ``` + + + +___ + diff --git a/web/app/components/develop/template/template_workflow.zh.mdx b/web/app/components/develop/template/template_workflow.zh.mdx index b032407ee9..e78b309e05 100644 --- a/web/app/components/develop/template/template_workflow.zh.mdx +++ b/web/app/components/develop/template/template_workflow.zh.mdx @@ -727,3 +727,57 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 + +--- + + + + + 用于获取应用的 WebApp 设置 + ### Response + - `title` (string) WebApp 名称 + - `icon_type` (string) 图标类型, `emoji`-表情, `image`-图片 + - `icon` (string) 图标, 如果是 `emoji` 类型, 则是 emoji 表情符号, 如果是 `image` 类型, 则是图片 URL + - `icon_background` (string) hex 格式的背景色 + - `icon_url` (string) 图标 URL + - `description` (string) 描述 + - `copyright` (string) 版权信息 + - `privacy_policy` (string) 隐私政策链接 + - `custom_disclaimer` (string) 自定义免责声明 + - `default_language` (string) 默认语言 + - `show_workflow_steps` (bool) 是否显示工作流详情 + + + + ```bash {{ title: 'cURL' }} + curl -X GET '${props.appDetail.api_base_url}/site' \ + -H 'Authorization: Bearer {api_key}' + ``` + + + + + ```json {{ title: 'Response' }} + { + "title": "My App", + "icon_type": "emoji", + "icon": "😄", + "icon_background": "#FFEAD5", + "icon_url": null, + "description": "This is my app.", + "copyright": "all rights reserved", + "privacy_policy": "", + "custom_disclaimer": "All generated by AI", + "default_language": "en-US", + "show_workflow_steps": false, + } + ``` + + + +___ From 9dce0e40b525e5bd76fb8fb7c0ebff3c750807ba Mon Sep 17 00:00:00 2001 From: rouxiaomin <1530140574@qq.com> Date: Wed, 14 May 2025 12:17:35 +0800 Subject: [PATCH 07/16] fix:fix log formatting field not found in record: 'req_id' (#19575) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 刘敏 --- api/extensions/ext_logging.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/api/extensions/ext_logging.py b/api/extensions/ext_logging.py index aa55862b7c..79d49aba5e 100644 --- a/api/extensions/ext_logging.py +++ b/api/extensions/ext_logging.py @@ -39,6 +39,10 @@ def init_app(app: DifyApp): handlers=log_handlers, force=True, ) + + # Apply RequestIdFormatter to all handlers + apply_request_id_formatter() + # Disable propagation for noisy loggers to avoid duplicate logs logging.getLogger("sqlalchemy.engine").propagate = False log_tz = dify_config.LOG_TZ @@ -74,3 +78,16 @@ class RequestIdFilter(logging.Filter): def filter(self, record): record.req_id = get_request_id() if flask.has_request_context() else "" return True + + +class RequestIdFormatter(logging.Formatter): + def format(self, record): + if not hasattr(record, "req_id"): + record.req_id = "" + return super().format(record) + + +def apply_request_id_formatter(): + for handler in logging.root.handlers: + if handler.formatter: + handler.formatter = RequestIdFormatter(dify_config.LOG_FORMAT, dify_config.LOG_DATEFORMAT) From 2c5f5b0c674c0cc2de501de6969aaffc5bc790a2 Mon Sep 17 00:00:00 2001 From: KVOJJJin Date: Wed, 14 May 2025 13:46:03 +0800 Subject: [PATCH 08/16] Feat: add search params with theme in links of marketplace (#19648) --- .../model-provider-page/install-from-marketplace.tsx | 4 +++- .../components/plugins/marketplace/list/card-wrapper.tsx | 4 +++- .../plugins/plugin-detail-panel/detail-header.tsx | 6 ++++-- web/app/components/plugins/plugin-item/index.tsx | 4 +++- web/app/components/plugins/provider-card.tsx | 4 +++- web/app/components/tools/marketplace/index.tsx | 5 +++-- .../workflow/block-selector/market-place-plugin/action.tsx | 4 +++- 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx b/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx index 38f35d6fb4..818007a26f 100644 --- a/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx +++ b/web/app/components/header/account-setting/model-provider-page/install-from-marketplace.tsx @@ -1,4 +1,5 @@ import { useCallback, useState } from 'react' +import { useTheme } from 'next-themes' import { useTranslation } from 'react-i18next' import Link from 'next/link' import { @@ -29,6 +30,7 @@ const InstallFromMarketplace = ({ searchText, }: InstallFromMarketplaceProps) => { const { t } = useTranslation() + const { theme } = useTheme() const [collapse, setCollapse] = useState(false) const locale = getLocaleOnClient() const { @@ -53,7 +55,7 @@ const InstallFromMarketplace = ({
{t('common.modelProvider.discoverMore')} - + {t('plugin.marketplace.difyMarketplace')} diff --git a/web/app/components/plugins/marketplace/list/card-wrapper.tsx b/web/app/components/plugins/marketplace/list/card-wrapper.tsx index 75c9aae3d5..27511559d9 100644 --- a/web/app/components/plugins/marketplace/list/card-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/card-wrapper.tsx @@ -1,4 +1,5 @@ 'use client' +import { useTheme } from 'next-themes' import { RiArrowRightUpLine } from '@remixicon/react' import { getPluginLinkInMarketplace } from '../utils' import Card from '@/app/components/plugins/card' @@ -22,6 +23,7 @@ const CardWrapper = ({ locale, }: CardWrapperProps) => { const { t } = useMixedTranslation(locale) + const { theme } = useTheme() const [isShowInstallFromMarketplace, { setTrue: showInstallFromMarketplace, setFalse: hideInstallFromMarketplace, @@ -54,7 +56,7 @@ const CardWrapper = ({ > {t('plugin.detailPanel.operation.install')} - +