From b817036343a14f4f52717396981fb95adaf530a4 Mon Sep 17 00:00:00 2001 From: Novice <857526207@qq.com> Date: Tue, 11 Mar 2025 20:30:03 +0800 Subject: [PATCH 01/13] fix: nesting of conditional branches causing streaming output error (#14065) --- .../workflow/nodes/answer/base_stream_processor.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api/core/workflow/nodes/answer/base_stream_processor.py b/api/core/workflow/nodes/answer/base_stream_processor.py index 4759356ae1..f33ba6cd5d 100644 --- a/api/core/workflow/nodes/answer/base_stream_processor.py +++ b/api/core/workflow/nodes/answer/base_stream_processor.py @@ -57,11 +57,19 @@ class StreamProcessor(ABC): # The branch_identify parameter is added to ensure that # only nodes in the correct logical branch are included. + reachable_node_ids.append(edge.target_node_id) ids = self._fetch_node_ids_in_reachable_branch(edge.target_node_id, run_result.edge_source_handle) reachable_node_ids.extend(ids) else: + # if the condition edge in parallel, and the target node is not in parallel, we should not remove it + # Issues: #13626 + if ( + finished_node_id in self.graph.node_parallel_mapping + and edge.target_node_id not in self.graph.parallel_mapping + ): + continue unreachable_first_node_ids.append(edge.target_node_id) - + unreachable_first_node_ids = list(set(unreachable_first_node_ids) - set(reachable_node_ids)) for node_id in unreachable_first_node_ids: self._remove_node_ids_in_unreachable_branch(node_id, reachable_node_ids) From c960b364c929da8dd21011b49c79c8969fe9d4e6 Mon Sep 17 00:00:00 2001 From: yihong Date: Tue, 11 Mar 2025 20:44:09 +0800 Subject: [PATCH 02/13] chore: update opendal version (#14343) Signed-off-by: yihong0618 Signed-off-by: -LAN- Co-authored-by: -LAN- --- api/extensions/storage/opendal_storage.py | 11 ++--------- api/poetry.lock | 2 +- api/pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/api/extensions/storage/opendal_storage.py b/api/extensions/storage/opendal_storage.py index 98f646f4f9..ee8cfa9179 100644 --- a/api/extensions/storage/opendal_storage.py +++ b/api/extensions/storage/opendal_storage.py @@ -71,15 +71,8 @@ class OpenDALStorage(BaseStorage): logger.debug(f"file {filename} downloaded to {target_filepath}") def exists(self, filename: str) -> bool: - # FIXME this is a workaround for opendal python-binding do not have a exists method and no better - # error handler here when opendal python-binding has a exists method, we should use it - # more https://github.com/apache/opendal/blob/main/bindings/python/src/operator.rs - try: - res: bool = self.op.stat(path=filename).mode.is_file() - logger.debug(f"file {filename} checked") - return res - except Exception: - return False + res: bool = self.op.exists(path=filename) + return res def delete(self, filename: str): if self.exists(filename): diff --git a/api/poetry.lock b/api/poetry.lock index ffa5810982..1cb05b1789 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -10228,4 +10228,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "8e6eaed521da966cfcdb5400e9d3aedb7623b5ac6a8da428d9f984af22542cce" +content-hash = "5ed3febffb932561050bba56b04bd03ffec7d54a7d26b95ad42c55ef45e07fae" diff --git a/api/pyproject.toml b/api/pyproject.toml index 493bfa240b..eb7313449a 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -105,7 +105,7 @@ bce-python-sdk = "~0.9.23" cos-python-sdk-v5 = "1.9.30" esdk-obs-python = "3.24.6.1" google-cloud-storage = "2.16.0" -opendal = "~0.45.12" +opendal = "~0.45.16" oss2 = "2.18.5" supabase = "~2.8.1" tos = "~2.7.1" From 5d8b32a2497de43043261b6b4243a1a981c8c2b5 Mon Sep 17 00:00:00 2001 From: Kalo Chin <91766386+fdb02983rhy@users.noreply.github.com> Date: Tue, 11 Mar 2025 23:23:06 +0900 Subject: [PATCH 03/13] feat: add click-away and mounting logic to agent setting component (#15521) --- .../config/agent/agent-setting/index.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/web/app/components/app/configuration/config/agent/agent-setting/index.tsx b/web/app/components/app/configuration/config/agent/agent-setting/index.tsx index 9fae3417b9..50903b0b6e 100644 --- a/web/app/components/app/configuration/config/agent/agent-setting/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-setting/index.tsx @@ -1,8 +1,9 @@ 'use client' import type { FC } from 'react' -import React, { useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { RiCloseLine } from '@remixicon/react' +import { useClickAway } from 'ahooks' import ItemPanel from './item-panel' import Button from '@/app/components/base/button' import { CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' @@ -31,6 +32,18 @@ const AgentSetting: FC = ({ }) => { const { t } = useTranslation() const [tempPayload, setTempPayload] = useState(payload) + const ref = useRef(null) + const [mounted, setMounted] = useState(false) + + useClickAway(() => { + if (mounted) + onCancel() + }, ref) + + useEffect(() => { + setMounted(true) + }, []) + const handleSave = () => { onSave(tempPayload) } @@ -42,6 +55,7 @@ const AgentSetting: FC = ({ }} >
From eb9b256ee812b31d2534fda9965ce9c115c330d6 Mon Sep 17 00:00:00 2001 From: NFish Date: Wed, 12 Mar 2025 09:49:15 +0800 Subject: [PATCH 04/13] =?UTF-8?q?fix:=20remove=20size=20prop=20in=20PlanBa?= =?UTF-8?q?dge=20component=20because=20UpgradeBtn=20size=20=E2=80=A6=20(#1?= =?UTF-8?q?5544)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../header/account-dropdown/index.tsx | 40 +++++++++---------- .../workplace-selector/index.tsx | 2 +- .../components/header/plan-badge/index.tsx | 16 ++++---- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index bae936ede3..a8cd48ca91 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -106,17 +106,17 @@ export default function AppSelector({ isMobile }: IAppSelector) { )} href='/account' target='_self' rel='noopener noreferrer'> - -
{t('common.account.account')}
- + +
{t('common.account.account')}
+ } {({ active }) =>
setShowAccountSettingModal({ payload: 'members' })}> - -
{t('common.userProfile.settings')}
+ +
{t('common.userProfile.settings')}
}
@@ -130,9 +130,9 @@ export default function AppSelector({ isMobile }: IAppSelector) { locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/` } target='_blank' rel='noopener noreferrer'> - -
{t('common.userProfile.helpCenter')}
- + +
{t('common.userProfile.helpCenter')}
+ } @@ -146,9 +146,9 @@ export default function AppSelector({ isMobile }: IAppSelector) { )} href='https://roadmap.dify.ai' target='_blank' rel='noopener noreferrer'> - -
{t('common.userProfile.roadmap')}
- + +
{t('common.userProfile.roadmap')}
+ } {systemFeatures.license.status === LicenseStatus.NONE && @@ -156,12 +156,12 @@ export default function AppSelector({ isMobile }: IAppSelector) { className={classNames(itemClassName, 'group justify-between', active && 'bg-state-base-hover', )} - href='https://github.com/langgenius/dify/stargazers' + href='https://github.com/langgenius/dify' target='_blank' rel='noopener noreferrer'> - -
{t('common.userProfile.github')}
+ +
{t('common.userProfile.github')}
- +
} @@ -172,9 +172,9 @@ export default function AppSelector({ isMobile }: IAppSelector) { {({ active }) =>
setAboutVisible(true)}> - -
{t('common.userProfile.about')}
-
+ +
{t('common.userProfile.about')}
+
{langeniusVersionInfo.current_version}
@@ -190,8 +190,8 @@ export default function AppSelector({ isMobile }: IAppSelector) { active && 'bg-state-base-hover', )} > - -
{t('common.userProfile.logout')}
+ +
{t('common.userProfile.logout')}
}
diff --git a/web/app/components/header/account-dropdown/workplace-selector/index.tsx b/web/app/components/header/account-dropdown/workplace-selector/index.tsx index 5fbf59b9c2..fde48a292b 100644 --- a/web/app/components/header/account-dropdown/workplace-selector/index.tsx +++ b/web/app/components/header/account-dropdown/workplace-selector/index.tsx @@ -71,7 +71,7 @@ const WorkplaceSelector = () => {
handleSwitchWorkspace(workspace.id)}>
{workspace.name[0].toLocaleUpperCase()}
{workspace.name}
- +
)) } diff --git a/web/app/components/header/plan-badge/index.tsx b/web/app/components/header/plan-badge/index.tsx index bbf90bd3e9..07f798a9af 100644 --- a/web/app/components/header/plan-badge/index.tsx +++ b/web/app/components/header/plan-badge/index.tsx @@ -1,5 +1,4 @@ import { useProviderContext } from '@/context/provider-context' -import classNames from '@/utils/classnames' import type { FC } from 'react' import { useTranslation } from 'react-i18next' import { SparklesSoft } from '../../base/icons/src/public/common' @@ -8,13 +7,12 @@ import { Plan } from '../../billing/type' type PlanBadgeProps = { plan: Plan - size?: 's' | 'm' allowHover?: boolean sandboxAsUpgrade?: boolean onClick?: () => void } -const PlanBadge: FC = ({ plan, allowHover, size = 'm', sandboxAsUpgrade = false, onClick }) => { +const PlanBadge: FC = ({ plan, allowHover, sandboxAsUpgrade = false, onClick }) => { const { isFetchedPlan } = useProviderContext() const { t } = useTranslation() @@ -30,8 +28,8 @@ const PlanBadge: FC = ({ plan, allowHover, size = 'm', sandboxAs } if (plan === Plan.sandbox) { - return -
+ return +
{plan} @@ -39,8 +37,8 @@ const PlanBadge: FC = ({ plan, allowHover, size = 'm', sandboxAs } if (plan === Plan.professional) { - return -
+ return +
pro @@ -48,8 +46,8 @@ const PlanBadge: FC = ({ plan, allowHover, size = 'm', sandboxAs } if (plan === Plan.team) { - return -
+ return +
{plan} From b541792465c7b396a653028fd5ad66fe5a0a3891 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Wed, 12 Mar 2025 10:10:51 +0800 Subject: [PATCH 05/13] fix: workflow loop node break conditions (#15549) --- web/app/components/workflow/nodes/loop/default.ts | 3 --- web/app/components/workflow/nodes/loop/panel.tsx | 1 + web/app/components/workflow/nodes/loop/use-config.ts | 4 +++- web/i18n/en-US/workflow.ts | 1 + web/i18n/zh-Hans/workflow.ts | 1 + 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/web/app/components/workflow/nodes/loop/default.ts b/web/app/components/workflow/nodes/loop/default.ts index ded8afc517..67b1f6edde 100644 --- a/web/app/components/workflow/nodes/loop/default.ts +++ b/web/app/components/workflow/nodes/loop/default.ts @@ -28,9 +28,6 @@ const nodeDefault: NodeDefault = { checkValid(payload: LoopNodeType, t: any) { let errorMessages = '' - if (!errorMessages && (!payload.break_conditions || payload.break_conditions.length === 0)) - errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.loop.breakCondition') }) - payload.break_conditions!.forEach((condition) => { if (!errorMessages && (!condition.variable_selector || condition.variable_selector.length === 0)) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variable`) }) diff --git a/web/app/components/workflow/nodes/loop/panel.tsx b/web/app/components/workflow/nodes/loop/panel.tsx index e416e8d77e..e361749f6d 100644 --- a/web/app/components/workflow/nodes/loop/panel.tsx +++ b/web/app/components/workflow/nodes/loop/panel.tsx @@ -55,6 +55,7 @@ const Panel: FC> = ({
{t(`${i18nPrefix}.breakCondition`)}
} + tooltip={t(`${i18nPrefix}.breakConditionTip`)} > { const { nodesReadOnly: readOnly } = useNodesReadOnly() const { isNodeInLoop } = useIsNodeInLoop(id) const isChatMode = useIsChatMode() + const conversationVariables = useStore(s => s.conversationVariables) const { inputs, setInputs } = useNodeCrud(id, payload) @@ -35,7 +37,7 @@ const useConfig = (id: string, payload: LoopNodeType) => { const beforeNodes = getBeforeNodesInSameBranch(id) const loopChildrenNodes = getLoopNodeChildren(id) const canChooseVarNodes = [...beforeNodes, ...loopChildrenNodes] - const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode) + const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables) // single run const loopInputKey = `${id}.input_selector` diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 704364b20a..a6dd4deb73 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -673,6 +673,7 @@ const translation = { loop_other: '{{count}} Loops', currentLoop: 'Current Loop', breakCondition: 'Loop Termination Condition', + breakConditionTip: 'Only variables within loops with termination conditions and conversation variables can be referenced.', loopMaxCount: 'Maximum Loop Count', loopMaxCountError: 'Please enter a valid maximum loop count, ranging from 1 to {{maxCount}}', errorResponseMethod: 'Error Response Method', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 7cf6853f87..523a93b46d 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -674,6 +674,7 @@ const translation = { loop_other: '{{count}} 个循环', currentLoop: '当前循环', breakCondition: '循环终止条件', + breakConditionTip: '支持引用终止条件循环内的变量和会话变量。', loopMaxCount: '最大循环次数', loopMaxCountError: '请输入正确的 最大循环次数,范围为 1 到 {{maxCount}}', errorResponseMethod: '错误响应方法', From 037f200527bdb88af323ef816f8edf15bb591428 Mon Sep 17 00:00:00 2001 From: jiangbo721 <365065261@qq.com> Date: Wed, 12 Mar 2025 10:58:44 +0800 Subject: [PATCH 06/13] fix: invoke_error is not callable (#15555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 刘江波 --- api/core/model_runtime/model_providers/__base/ai_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/model_runtime/model_providers/__base/ai_model.py b/api/core/model_runtime/model_providers/__base/ai_model.py index cdd1bba6be..bd05590018 100644 --- a/api/core/model_runtime/model_providers/__base/ai_model.py +++ b/api/core/model_runtime/model_providers/__base/ai_model.py @@ -80,7 +80,7 @@ class AIModel(BaseModel): ) ) elif isinstance(invoke_error, InvokeError): - return invoke_error(description=f"[{self.provider_name}] {invoke_error.description}, {str(error)}") + return InvokeError(description=f"[{self.provider_name}] {invoke_error.description}, {str(error)}") else: return error From 0bc4da38fc85fcbab7b8dc3f5d72178d6d6ad0db Mon Sep 17 00:00:00 2001 From: Lam Date: Wed, 12 Mar 2025 11:25:54 +0800 Subject: [PATCH 07/13] feat: add debounced enter key submission to install form (#15445) (#15542) --- web/app/install/installForm.tsx | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/web/app/install/installForm.tsx b/web/app/install/installForm.tsx index abf377e389..df7aa0cbf4 100644 --- a/web/app/install/installForm.tsx +++ b/web/app/install/installForm.tsx @@ -1,6 +1,7 @@ 'use client' -import React, { useEffect } from 'react' +import React, { useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' +import { useDebounceFn } from 'ahooks' import Link from 'next/link' import { useRouter } from 'next/navigation' @@ -39,7 +40,7 @@ const InstallForm = () => { const { register, handleSubmit, - formState: { errors }, + formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(accountFormSchema), defaultValues: { @@ -59,9 +60,22 @@ const InstallForm = () => { } const handleSetting = async () => { + if (isSubmitting) return handleSubmit(onSubmit)() } + const { run: debouncedHandleKeyDown } = useDebounceFn( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault() + handleSetting() + } + }, + { wait: 200 }, + ) + + const handleKeyDown = useCallback(debouncedHandleKeyDown, [debouncedHandleKeyDown]) + useEffect(() => { fetchSetupStatus().then((res: SetupStatusResponse) => { if (res.step === 'finished') { @@ -90,7 +104,7 @@ const InstallForm = () => {
-
+
@@ -114,7 +128,7 @@ const InstallForm = () => {
{errors.name && {t(`${errors.name.message}`)}} @@ -129,7 +143,7 @@ const InstallForm = () => { {...register('password')} type={showPassword ? 'text' : 'password'} placeholder={t('login.passwordPlaceholder') || ''} - className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder-gray-400 caret-primary-600 sm:text-sm pr-10'} + className={'appearance-none block w-full rounded-lg pl-[14px] px-3 py-2 border border-gray-200 hover:border-gray-300 hover:shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 placeholder:text-gray-400 caret-primary-600 sm:text-sm pr-10'} />
From 258736f50546505845594828f59d94abf751ea5f Mon Sep 17 00:00:00 2001 From: crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Wed, 12 Mar 2025 12:09:39 +0800 Subject: [PATCH 08/13] chore: remove unused parameter (#15558) --- api/services/workspace_service.py | 1 - web/context/app-context.tsx | 1 - web/models/common.ts | 57 +++++++++++++++---------------- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/api/services/workspace_service.py b/api/services/workspace_service.py index 7637b31454..d91ce27854 100644 --- a/api/services/workspace_service.py +++ b/api/services/workspace_service.py @@ -18,7 +18,6 @@ class WorkspaceService: "plan": tenant.plan, "status": tenant.status, "created_at": tenant.created_at, - "in_trail": True, "trial_end_reason": None, "role": "normal", } diff --git a/web/context/app-context.tsx b/web/context/app-context.tsx index a6a9afaec9..da246d5ca4 100644 --- a/web/context/app-context.tsx +++ b/web/context/app-context.tsx @@ -49,7 +49,6 @@ const initialWorkspaceInfo: ICurrentWorkspace = { created_at: 0, role: 'normal', providers: [], - in_trail: true, } const AppContext = createContext({ diff --git a/web/models/common.ts b/web/models/common.ts index 4c25e92a85..4086220e2e 100644 --- a/web/models/common.ts +++ b/web/models/common.ts @@ -1,23 +1,23 @@ import type { I18nText } from '@/i18n/language' -export interface CommonResponse { +export type CommonResponse = { result: 'success' | 'fail' } -export interface OauthResponse { +export type OauthResponse = { redirect_url: string } -export interface SetupStatusResponse { +export type SetupStatusResponse = { step: 'finished' | 'not_started' setup_at?: Date } -export interface InitValidateStatusResponse { +export type InitValidateStatusResponse = { status: 'finished' | 'not_started' } -export interface UserProfileResponse { +export type UserProfileResponse = { id: string name: string email: string @@ -33,13 +33,13 @@ export interface UserProfileResponse { created_at?: string } -export interface UserProfileOriginResponse { +export type UserProfileOriginResponse = { json: () => Promise bodyUsed: boolean headers: any } -export interface LangGeniusVersionResponse { +export type LangGeniusVersionResponse = { current_version: string latest_version: string version: string @@ -49,7 +49,7 @@ export interface LangGeniusVersionResponse { current_env: string } -export interface TenantInfoResponse { +export type TenantInfoResponse = { name: string created_at: string providers: Array<{ @@ -80,14 +80,14 @@ export enum ProviderName { Tongyi = 'tongyi', ChatGLM = 'chatglm', } -export interface ProviderAzureToken { +export type ProviderAzureToken = { openai_api_base?: string openai_api_key?: string } -export interface ProviderAnthropicToken { +export type ProviderAnthropicToken = { anthropic_api_key?: string } -export interface ProviderTokenType { +export type ProviderTokenType = { [ProviderName.OPENAI]: string [ProviderName.AZURE_OPENAI]: ProviderAzureToken [ProviderName.ANTHROPIC]: ProviderAnthropicToken @@ -110,14 +110,14 @@ export type ProviderHosted = Provider & { quota_used: number } -export interface AccountIntegrate { +export type AccountIntegrate = { provider: 'google' | 'github' created_at: number is_bound: boolean link: string } -export interface IWorkspace { +export type IWorkspace = { id: string name: string plan: string @@ -129,7 +129,6 @@ export interface IWorkspace { export type ICurrentWorkspace = Omit & { role: 'owner' | 'admin' | 'editor' | 'dataset_operator' | 'normal' providers: Provider[] - in_trail: boolean trial_end_reason?: string custom_config?: { remove_webapp_brand?: boolean @@ -137,7 +136,7 @@ export type ICurrentWorkspace = Omit & { } } -export interface DataSourceNotionPage { +export type DataSourceNotionPage = { page_icon: null | { type: string | null url: string | null @@ -156,7 +155,7 @@ export type NotionPage = DataSourceNotionPage & { export type DataSourceNotionPageMap = Record -export interface DataSourceNotionWorkspace { +export type DataSourceNotionWorkspace = { workspace_name: string workspace_id: string workspace_icon: string | null @@ -166,7 +165,7 @@ export interface DataSourceNotionWorkspace { export type DataSourceNotionWorkspaceMap = Record -export interface DataSourceNotion { +export type DataSourceNotion = { id: string provider: string is_bound: boolean @@ -181,12 +180,12 @@ export enum DataSourceProvider { jinaReader = 'jinareader', } -export interface FirecrawlConfig { +export type FirecrawlConfig = { api_key: string base_url: string } -export interface DataSourceItem { +export type DataSourceItem = { id: string category: DataSourceCategory provider: DataSourceProvider @@ -195,15 +194,15 @@ export interface DataSourceItem { updated_at: number } -export interface DataSources { +export type DataSources = { sources: DataSourceItem[] } -export interface GithubRepo { +export type GithubRepo = { stargazers_count: number } -export interface PluginProvider { +export type PluginProvider = { tool_name: string is_enabled: boolean credentials: { @@ -211,7 +210,7 @@ export interface PluginProvider { } | null } -export interface FileUploadConfigResponse { +export type FileUploadConfigResponse = { batch_count_limit: number image_file_size_limit?: number | string // default is 10MB file_size_limit: number // default is 15MB @@ -234,14 +233,14 @@ export type InvitationResponse = CommonResponse & { invitation_results: InvitationResult[] } -export interface ApiBasedExtension { +export type ApiBasedExtension = { id?: string name?: string api_endpoint?: string api_key?: string } -export interface CodeBasedExtensionForm { +export type CodeBasedExtensionForm = { type: string label: I18nText variable: string @@ -252,17 +251,17 @@ export interface CodeBasedExtensionForm { max_length?: number } -export interface CodeBasedExtensionItem { +export type CodeBasedExtensionItem = { name: string label: any form_schema: CodeBasedExtensionForm[] } -export interface CodeBasedExtension { +export type CodeBasedExtension = { module: string data: CodeBasedExtensionItem[] } -export interface ExternalDataTool { +export type ExternalDataTool = { type?: string label?: string icon?: string @@ -274,7 +273,7 @@ export interface ExternalDataTool { } & Partial> } -export interface ModerateResponse { +export type ModerateResponse = { flagged: boolean text: string } From 1fab02c25a83628dce558138c2060ff00e2a3266 Mon Sep 17 00:00:00 2001 From: Mars <524574386@qq.com> Date: Wed, 12 Mar 2025 12:38:23 +0800 Subject: [PATCH 09/13] fix:message api doc (#15568) Co-authored-by: mars --- api/controllers/service_api/app/message.py | 16 +--------------- .../develop/template/template_chat.en.mdx | 4 ++-- .../develop/template/template_chat.ja.mdx | 4 ++-- .../develop/template/template_chat.zh.mdx | 4 ++-- 4 files changed, 7 insertions(+), 21 deletions(-) diff --git a/api/controllers/service_api/app/message.py b/api/controllers/service_api/app/message.py index 1869cf67c2..749099053d 100644 --- a/api/controllers/service_api/app/message.py +++ b/api/controllers/service_api/app/message.py @@ -10,7 +10,7 @@ from controllers.service_api.app.error import NotChatAppError from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token from core.app.entities.app_invoke_entities import InvokeFrom from fields.conversation_fields import message_file_fields -from fields.message_fields import feedback_fields, retriever_resource_fields +from fields.message_fields import agent_thought_fields, feedback_fields, retriever_resource_fields from fields.raws import FilesContainedField from libs.helper import TimestampField, uuid_value from models.model import App, AppMode, EndUser @@ -19,20 +19,6 @@ from services.message_service import MessageService class MessageListApi(Resource): - agent_thought_fields = { - "id": fields.String, - "chain_id": fields.String, - "message_id": fields.String, - "position": fields.Integer, - "thought": fields.String, - "tool": fields.String, - "tool_labels": fields.Raw, - "tool_input": fields.String, - "created_at": TimestampField, - "observation": fields.String, - "message_files": fields.List(fields.Nested(message_file_fields)), - } - message_fields = { "id": fields.String, "conversation_id": fields.String, diff --git a/web/app/components/develop/template/template_chat.en.mdx b/web/app/components/develop/template/template_chat.en.mdx index 7331dda54b..6388edf83d 100644 --- a/web/app/components/develop/template/template_chat.en.mdx +++ b/web/app/components/develop/template/template_chat.en.mdx @@ -641,7 +641,7 @@ Chat applications support session persistence, allowing previous chat history to "tool_input": "{\"dalle2\": {\"prompt\": \"cat\"}}", "created_at": 1705988186, "observation": "image has been created and sent to user already, you should tell user to check it now.", - "message_files": [ + "files": [ "976990d2-5294-47e6-8f14-7356ba9d2d76" ] }, @@ -655,7 +655,7 @@ Chat applications support session persistence, allowing previous chat history to "tool_input": "", "created_at": 1705988199, "observation": "", - "message_files": [] + "files": [] } ] } diff --git a/web/app/components/develop/template/template_chat.ja.mdx b/web/app/components/develop/template/template_chat.ja.mdx index 78f21476ab..0199951c5b 100644 --- a/web/app/components/develop/template/template_chat.ja.mdx +++ b/web/app/components/develop/template/template_chat.ja.mdx @@ -641,7 +641,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from "tool_input": "{\"dalle2\": {\"prompt\": \"cat\"}}", "created_at": 1705988186, "observation": "画像はすでに作成され、ユーザーに送信されました。今すぐユーザーに確認するように伝えてください。", - "message_files": [ + "files": [ "976990d2-5294-47e6-8f14-7356ba9d2d76" ] }, @@ -655,7 +655,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from "tool_input": "", "created_at": 1705988199, "observation": "", - "message_files": [] + "files": [] } ] } diff --git a/web/app/components/develop/template/template_chat.zh.mdx b/web/app/components/develop/template/template_chat.zh.mdx index e4a426462e..c3fcb849e1 100644 --- a/web/app/components/develop/template/template_chat.zh.mdx +++ b/web/app/components/develop/template/template_chat.zh.mdx @@ -657,7 +657,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' "tool_input": "{\"dalle2\": {\"prompt\": \"cat\"}}", "created_at": 1705988186, "observation": "image has been created and sent to user already, you should tell user to check it now.", - "message_files": [ + "files": [ "976990d2-5294-47e6-8f14-7356ba9d2d76" ] }, @@ -671,7 +671,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' "tool_input": "", "created_at": 1705988199, "observation": "", - "message_files": [] + "files": [] } ] } From 545e5cbcd6fdd6ad2c4fba05b2a87f2da4edb428 Mon Sep 17 00:00:00 2001 From: Joe <79627742+ZhouhaoJiang@users.noreply.github.com> Date: Wed, 12 Mar 2025 12:51:00 +0800 Subject: [PATCH 10/13] fix: dataset editor (#15218) --- .../console/datasets/datasets_document.py | 12 +++---- .../console/datasets/datasets_segments.py | 32 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/api/controllers/console/datasets/datasets_document.py b/api/controllers/console/datasets/datasets_document.py index 6e0e8f1903..38a80b4129 100644 --- a/api/controllers/console/datasets/datasets_document.py +++ b/api/controllers/console/datasets/datasets_document.py @@ -325,8 +325,8 @@ class DatasetInitApi(Resource): @cloud_edition_billing_resource_check("vector_space") @cloud_edition_billing_rate_limit_check("knowledge") def post(self): - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() parser = reqparse.RequestParser() @@ -704,8 +704,8 @@ class DocumentProcessingApi(DocumentResource): document_id = str(document_id) document = self.get_document(dataset_id, document_id) - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() if action == "pause": @@ -769,8 +769,8 @@ class DocumentMetadataApi(DocumentResource): doc_type = req_data.get("doc_type") doc_metadata = req_data.get("doc_metadata") - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() if doc_type is None or doc_metadata is None: diff --git a/api/controllers/console/datasets/datasets_segments.py b/api/controllers/console/datasets/datasets_segments.py index 4642ed3573..1b38d0776a 100644 --- a/api/controllers/console/datasets/datasets_segments.py +++ b/api/controllers/console/datasets/datasets_segments.py @@ -123,8 +123,8 @@ class DatasetDocumentSegmentListApi(Resource): raise NotFound("Document not found.") segment_ids = request.args.getlist("segment_id") - # The role of the current user in the ta table must be admin or owner - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() try: DatasetService.check_dataset_permission(dataset, current_user) @@ -151,8 +151,8 @@ class DatasetDocumentSegmentApi(Resource): raise NotFound("Document not found.") # check user's model setting DatasetService.check_dataset_model_setting(dataset) - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() try: @@ -206,7 +206,7 @@ class DatasetDocumentSegmentAddApi(Resource): document = DocumentService.get_document(dataset_id, document_id) if not document: raise NotFound("Document not found.") - if not current_user.is_editor: + if not current_user.is_dataset_editor: raise Forbidden() # check embedding model setting if dataset.indexing_technique == "high_quality": @@ -281,8 +281,8 @@ class DatasetDocumentSegmentUpdateApi(Resource): ).first() if not segment: raise NotFound("Segment not found.") - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() try: DatasetService.check_dataset_permission(dataset, current_user) @@ -325,8 +325,8 @@ class DatasetDocumentSegmentUpdateApi(Resource): ).first() if not segment: raise NotFound("Segment not found.") - # The role of the current user in the ta table must be admin or owner - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() try: DatasetService.check_dataset_permission(dataset, current_user) @@ -428,7 +428,7 @@ class ChildChunkAddApi(Resource): ).first() if not segment: raise NotFound("Segment not found.") - if not current_user.is_editor: + if not current_user.is_dataset_editor: raise Forbidden() # check embedding model setting if dataset.indexing_technique == "high_quality": @@ -528,8 +528,8 @@ class ChildChunkAddApi(Resource): ).first() if not segment: raise NotFound("Segment not found.") - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() try: DatasetService.check_dataset_permission(dataset, current_user) @@ -579,8 +579,8 @@ class ChildChunkUpdateApi(Resource): ).first() if not child_chunk: raise NotFound("Child chunk not found.") - # The role of the current user in the ta table must be admin or owner - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() try: DatasetService.check_dataset_permission(dataset, current_user) @@ -624,8 +624,8 @@ class ChildChunkUpdateApi(Resource): ).first() if not child_chunk: raise NotFound("Child chunk not found.") - # The role of the current user in the ta table must be admin or owner - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor + if not current_user.is_dataset_editor: raise Forbidden() try: DatasetService.check_dataset_permission(dataset, current_user) From 0415cc209d3471517d7a459c82e1ca3e090593a2 Mon Sep 17 00:00:00 2001 From: jiangbo721 <365065261@qq.com> Date: Wed, 12 Mar 2025 12:56:30 +0800 Subject: [PATCH 11/13] chore: use TenantAccountRole instead of TenantAccountJoinRole (#15514) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 刘江波 --- api/models/__init__.py | 2 -- api/models/account.py | 7 ------- api/services/account_service.py | 13 ++++++------- api/services/workspace_service.py | 6 ++---- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/api/models/__init__.py b/api/models/__init__.py index 1237c55a30..2066481a61 100644 --- a/api/models/__init__.py +++ b/api/models/__init__.py @@ -5,7 +5,6 @@ from .account import ( InvitationCode, Tenant, TenantAccountJoin, - TenantAccountJoinRole, TenantAccountRole, TenantStatus, ) @@ -156,7 +155,6 @@ __all__ = [ "TagBinding", "Tenant", "TenantAccountJoin", - "TenantAccountJoinRole", "TenantAccountRole", "TenantDefaultModel", "TenantPreferredModelProvider", diff --git a/api/models/account.py b/api/models/account.py index bac1ec1c2e..a0b8957fe1 100644 --- a/api/models/account.py +++ b/api/models/account.py @@ -220,13 +220,6 @@ class Tenant(db.Model): # type: ignore[name-defined] self.custom_config = json.dumps(value) -class TenantAccountJoinRole(enum.Enum): - OWNER = "owner" - ADMIN = "admin" - NORMAL = "normal" - DATASET_OPERATOR = "dataset_operator" - - class TenantAccountJoin(db.Model): # type: ignore[name-defined] __tablename__ = "tenant_account_joins" __table_args__ = ( diff --git a/api/services/account_service.py b/api/services/account_service.py index 2923750298..42d1fba97f 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -28,7 +28,6 @@ from models.account import ( AccountStatus, Tenant, TenantAccountJoin, - TenantAccountJoinRole, TenantAccountRole, TenantStatus, ) @@ -625,8 +624,8 @@ class TenantService: @staticmethod def create_tenant_member(tenant: Tenant, account: Account, role: str = "normal") -> TenantAccountJoin: """Create tenant member""" - if role == TenantAccountJoinRole.OWNER.value: - if TenantService.has_roles(tenant, [TenantAccountJoinRole.OWNER]): + if role == TenantAccountRole.OWNER.value: + if TenantService.has_roles(tenant, [TenantAccountRole.OWNER]): logging.error(f"Tenant {tenant.id} has already an owner.") raise Exception("Tenant already has an owner.") @@ -734,10 +733,10 @@ class TenantService: return updated_accounts @staticmethod - def has_roles(tenant: Tenant, roles: list[TenantAccountJoinRole]) -> bool: + def has_roles(tenant: Tenant, roles: list[TenantAccountRole]) -> bool: """Check if user has any of the given roles for a tenant""" - if not all(isinstance(role, TenantAccountJoinRole) for role in roles): - raise ValueError("all roles must be TenantAccountJoinRole") + if not all(isinstance(role, TenantAccountRole) for role in roles): + raise ValueError("all roles must be TenantAccountRole") return ( db.session.query(TenantAccountJoin) @@ -749,7 +748,7 @@ class TenantService: ) @staticmethod - def get_user_role(account: Account, tenant: Tenant) -> Optional[TenantAccountJoinRole]: + def get_user_role(account: Account, tenant: Tenant) -> Optional[TenantAccountRole]: """Get the role of the current account for a given tenant""" join = ( db.session.query(TenantAccountJoin) diff --git a/api/services/workspace_service.py b/api/services/workspace_service.py index d91ce27854..e012fd4296 100644 --- a/api/services/workspace_service.py +++ b/api/services/workspace_service.py @@ -2,7 +2,7 @@ from flask_login import current_user # type: ignore from configs import dify_config from extensions.ext_database import db -from models.account import Tenant, TenantAccountJoin, TenantAccountJoinRole +from models.account import Tenant, TenantAccountJoin, TenantAccountRole from services.account_service import TenantService from services.feature_service import FeatureService @@ -33,9 +33,7 @@ class WorkspaceService: can_replace_logo = FeatureService.get_features(tenant_info["id"]).can_replace_logo - if can_replace_logo and TenantService.has_roles( - tenant, [TenantAccountJoinRole.OWNER, TenantAccountJoinRole.ADMIN] - ): + if can_replace_logo and TenantService.has_roles(tenant, [TenantAccountRole.OWNER, TenantAccountRole.ADMIN]): base_url = dify_config.FILES_URL replace_webapp_logo = ( f"{base_url}/files/workspaces/{tenant.id}/webapp-logo" From 1d5ea80a2b4feda798263328982cf4fc66f34a06 Mon Sep 17 00:00:00 2001 From: Rafael Carvalho Date: Wed, 12 Mar 2025 01:57:05 -0300 Subject: [PATCH 12/13] feat: env MAX_TOOLS_NUM (#15431) Co-authored-by: crazywoola <427733928@qq.com> --- docker/.env.example | 3 +++ docker/docker-compose-template.yaml | 1 + docker/docker-compose.yaml | 2 ++ web/.env.example | 3 +++ web/app/layout.tsx | 1 + web/config/index.ts | 9 ++++++++- 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docker/.env.example b/docker/.env.example index 29073fa1b0..a3788ecada 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -723,6 +723,9 @@ SSRF_PROXY_HTTPS_URL=http://ssrf_proxy:3128 # Maximum loop count in the workflow LOOP_NODE_MAX_COUNT=100 +# The maximum number of tools that can be used in the agent. +MAX_TOOLS_NUM=10 + # ------------------------------ # Environment Variables for web Service # ------------------------------ diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index 59544600d0..2879f2194f 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -68,6 +68,7 @@ services: INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-} PM2_INSTANCES: ${PM2_INSTANCES:-2} LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} + MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10} # The postgres database. db: diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 6c95ddd1f2..1d7f0ac3d8 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -311,6 +311,7 @@ x-shared-env: &shared-api-worker-env SSRF_PROXY_HTTP_URL: ${SSRF_PROXY_HTTP_URL:-http://ssrf_proxy:3128} SSRF_PROXY_HTTPS_URL: ${SSRF_PROXY_HTTPS_URL:-http://ssrf_proxy:3128} LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} + MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10} TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000} PGUSER: ${PGUSER:-${DB_USERNAME}} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-${DB_PASSWORD}} @@ -486,6 +487,7 @@ services: INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-} PM2_INSTANCES: ${PM2_INSTANCES:-2} LOOP_NODE_MAX_COUNT: ${LOOP_NODE_MAX_COUNT:-100} + MAX_TOOLS_NUM: ${MAX_TOOLS_NUM:-10} # The postgres database. db: diff --git a/web/.env.example b/web/.env.example index 6738bfce07..fa1477ba79 100644 --- a/web/.env.example +++ b/web/.env.example @@ -40,3 +40,6 @@ NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000 # Maximum loop count in the workflow NEXT_PUBLIC_LOOP_NODE_MAX_COUNT=100 + +# Maximum number of tools in the agent/workflow +NEXT_PUBLIC_MAX_TOOLS_NUM=10 \ No newline at end of file diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 72077567b3..8a5af7acfc 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -47,6 +47,7 @@ const LocaleLayout = ({ data-public-maintenance-notice={process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE} data-public-site-about={process.env.NEXT_PUBLIC_SITE_ABOUT} data-public-text-generation-timeout-ms={process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS} + data-public-max-tools-num={process.env.NEXT_PUBLIC_MAX_TOOLS_NUM} data-public-top-k-max-value={process.env.NEXT_PUBLIC_TOP_K_MAX_VALUE} data-public-indexing-max-segmentation-tokens-length={process.env.NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH} data-public-loop-node-max-count={process.env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT} diff --git a/web/config/index.ts b/web/config/index.ts index 94eb0db948..3426222796 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -162,7 +162,14 @@ export const ANNOTATION_DEFAULT = { score_threshold: 0.9, } -export const MAX_TOOLS_NUM = 10 +export let maxToolsNum = 10 + +if (process.env.NEXT_PUBLIC_MAX_TOOLS_NUM && process.env.NEXT_PUBLIC_MAX_TOOLS_NUM !== '') + maxToolsNum = Number.parseInt(process.env.NEXT_PUBLIC_MAX_TOOLS_NUM) +else if (globalThis.document?.body?.getAttribute('data-public-max-tools-num') && globalThis.document.body.getAttribute('data-public-max-tools-num') !== '') + maxToolsNum = Number.parseInt(globalThis.document.body.getAttribute('data-public-max-tools-num') as string) + +export const MAX_TOOLS_NUM = maxToolsNum export const DEFAULT_AGENT_SETTING = { enabled: false, From 3c945978920906c93d6a2e7409b99461dfed45bd Mon Sep 17 00:00:00 2001 From: NFish Date: Wed, 12 Mar 2025 16:58:53 +0800 Subject: [PATCH 13/13] fix: update default github star count value --- web/app/components/header/github-star/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/header/github-star/index.tsx b/web/app/components/header/github-star/index.tsx index 328fa8ec7c..b087b9e41a 100644 --- a/web/app/components/header/github-star/index.tsx +++ b/web/app/components/header/github-star/index.tsx @@ -17,7 +17,7 @@ const GithubStar: FC<{ className: string }> = (props) => { queryKey: ['github-star'], queryFn: getStar, enabled: process.env.NODE_ENV !== 'development', - initialData: { stargazers_count: 6000 }, + initialData: { stargazers_count: 81204 }, }) if (isFetching) return null