diff --git a/README_FR.md b/README_FR.md index 3890c2a996..afbf18b069 100644 --- a/README_FR.md +++ b/README_FR.md @@ -55,7 +55,7 @@ Dify est une plateforme de développement d'applications LLM open source. Son interface intuitive combine un flux de travail d'IA, un pipeline RAG, des capacités d'agent, une gestion de modèles, des fonctionnalités d'observabilité, et plus encore, vous permettant de passer rapidement du prototype à la production. Voici une liste des fonctionnalités principales:

-**1. Flux de travail**: +**1. Flux de travail** : Construisez et testez des flux de travail d'IA puissants sur un canevas visuel, en utilisant toutes les fonctionnalités suivantes et plus encore. @@ -63,27 +63,25 @@ Dify est une plateforme de développement d'applications LLM open source. Son in -**2. Prise en charge complète des modèles**: +**2. Prise en charge complète des modèles** : Intégration transparente avec des centaines de LLM propriétaires / open source provenant de dizaines de fournisseurs d'inférence et de solutions auto-hébergées, couvrant GPT, Mistral, Llama3, et tous les modèles compatibles avec l'API OpenAI. Une liste complète des fournisseurs de modèles pris en charge se trouve [ici](https://docs.dify.ai/getting-started/readme/model-providers). ![providers-v5](https://github.com/langgenius/dify/assets/13230914/5a17bdbe-097a-4100-8363-40255b70f6e3) -**3. IDE de prompt**: +**3. IDE de prompt** : Interface intuitive pour créer des prompts, comparer les performances des modèles et ajouter des fonctionnalités supplémentaires telles que la synthèse vocale à une application basée sur des chats. -**4. Pipeline RAG**: +**4. Pipeline RAG** : Des capacités RAG étendues qui couvrent tout, de l'ingestion de documents à la récupération, avec un support prêt à l'emploi pour l'extraction de texte à partir de PDF, PPT et autres formats de document courants. -**5. Capac - -ités d'agent**: +**5. Capacités d'agent** : Vous pouvez définir des agents basés sur l'appel de fonction LLM ou ReAct, et ajouter des outils pré-construits ou personnalisés pour l'agent. Dify fournit plus de 50 outils intégrés pour les agents d'IA, tels que la recherche Google, DALL·E, Stable Diffusion et WolframAlpha. -**6. LLMOps**: +**6. LLMOps** : Surveillez et analysez les journaux d'application et les performances au fil du temps. Vous pouvez continuellement améliorer les prompts, les ensembles de données et les modèles en fonction des données de production et des annotations. -**7. Backend-as-a-Service**: +**7. Backend-as-a-Service** : Toutes les offres de Dify sont accompagnées d'API correspondantes, vous permettant d'intégrer facilement Dify dans votre propre logique métier. diff --git a/api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py b/api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py index 031a7b8309..372496a8fa 100644 --- a/api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py +++ b/api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py @@ -1,6 +1,3 @@ -from collections.abc import Mapping, Sequence -from typing import Any - from core.workflow.entities.node_entities import NodeRunResult from core.workflow.nodes.base import BaseNode from core.workflow.nodes.enums import NodeType @@ -36,16 +33,3 @@ class VariableAggregatorNode(BaseNode[VariableAssignerNodeData]): break return NodeRunResult(status=WorkflowNodeExecutionStatus.SUCCEEDED, outputs=outputs, inputs=inputs) - - @classmethod - def _extract_variable_selector_to_variable_mapping( - cls, *, graph_config: Mapping[str, Any], node_id: str, node_data: VariableAssignerNodeData - ) -> Mapping[str, Sequence[str]]: - """ - Extract variable selector to variable mapping - :param graph_config: graph config - :param node_id: node id - :param node_data: node data - :return: - """ - return {} diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index 34a28d908e..463e9cf515 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -59,8 +59,8 @@ const Apps = () => { const [activeTab, setActiveTab] = useTabSearchParams({ defaultTab: 'all', }) - const { query: { tagIDs = [], keywords = '' }, setQuery } = useAppsQueryState() - const [isCreatedByMe, setIsCreatedByMe] = useState(false) + const { query: { tagIDs = [], keywords = '', isCreatedByMe: queryIsCreatedByMe = false }, setQuery } = useAppsQueryState() + const [isCreatedByMe, setIsCreatedByMe] = useState(queryIsCreatedByMe) const [tagFilterValue, setTagFilterValue] = useState(tagIDs) const [searchKeywords, setSearchKeywords] = useState(keywords) const setKeywords = useCallback((keywords: string) => { @@ -126,6 +126,12 @@ const Apps = () => { handleTagsUpdate() } + const handleCreatedByMeChange = useCallback(() => { + const newValue = !isCreatedByMe + setIsCreatedByMe(newValue) + setQuery(prev => ({ ...prev, isCreatedByMe: newValue })) + }, [isCreatedByMe, setQuery]) + return ( <>
@@ -139,7 +145,7 @@ const Apps = () => { className='mr-2' label={t('app.showMyCreatedAppsOnly')} isChecked={isCreatedByMe} - onChange={() => setIsCreatedByMe(!isCreatedByMe)} + onChange={handleCreatedByMeChange} /> 0) current.set('tagIDs', tagIDs.join(';')) @@ -26,6 +28,11 @@ function updateSearchParams(query: AppsQuery, current: URLSearchParams) { current.set('keywords', keywords) else current.delete('keywords') + + if (isCreatedByMe) + current.set('isCreatedByMe', 'true') + else + current.delete('isCreatedByMe') } function useAppsQueryState() { diff --git a/web/app/components/datasets/create/file-uploader/index.tsx b/web/app/components/datasets/create/file-uploader/index.tsx index e42a24cfef..5a64c319cd 100644 --- a/web/app/components/datasets/create/file-uploader/index.tsx +++ b/web/app/components/datasets/create/file-uploader/index.tsx @@ -243,7 +243,7 @@ const FileUploader = ({ }, [handleDrop]) return ( -
+
{!hideUpload && ( +
- {t('datasetCreation.stepOne.dataSourceType.file')} + + {t('datasetCreation.stepOne.dataSourceType.file')} +
- {t('datasetCreation.stepOne.dataSourceType.notion')} + + {t('datasetCreation.stepOne.dataSourceType.notion')} +
changeType(DataSourceType.WEB)} > - {t('datasetCreation.stepOne.dataSourceType.web')} + + {t('datasetCreation.stepOne.dataSourceType.web')} +
) diff --git a/web/app/components/header/nav/index.tsx b/web/app/components/header/nav/index.tsx index de98593ddd..b7ee7b6973 100644 --- a/web/app/components/header/nav/index.tsx +++ b/web/app/components/header/nav/index.tsx @@ -1,8 +1,8 @@ 'use client' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import Link from 'next/link' -import { useSelectedLayoutSegment } from 'next/navigation' +import { usePathname, useSearchParams, useSelectedLayoutSegment } from 'next/navigation' import type { INavSelectorProps } from './nav-selector' import NavSelector from './nav-selector' import classNames from '@/utils/classnames' @@ -35,6 +35,14 @@ const Nav = ({ const [hovered, setHovered] = useState(false) const segment = useSelectedLayoutSegment() const isActivated = Array.isArray(activeSegment) ? activeSegment.includes(segment!) : segment === activeSegment + const pathname = usePathname() + const searchParams = useSearchParams() + const [linkLastSearchParams, setLinkLastSearchParams] = useState('') + + useEffect(() => { + if (pathname === link) + setLinkLastSearchParams(searchParams.toString()) + }, [pathname, searchParams]) return (
- +
setAppDetail()} className={classNames(` diff --git a/web/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list.tsx b/web/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list.tsx index bb6c1baedb..ac26971359 100644 --- a/web/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list.tsx +++ b/web/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list.tsx @@ -23,17 +23,15 @@ const useRefreshPluginList = () => { // installed list invalidateInstalledPluginList() - if (!manifest) return - // tool page, tool select - if (PluginType.tool.includes(manifest.category) || refreshAllType) { + if ((manifest && PluginType.tool.includes(manifest.category)) || refreshAllType) { invalidateAllToolProviders() invalidateAllBuiltInTools() // TODO: update suggested tools. It's a function in hook useMarketplacePlugins,handleUpdatePlugins } // model select - if (PluginType.model.includes(manifest.category) || refreshAllType) { + if ((manifest && PluginType.model.includes(manifest.category)) || refreshAllType) { refreshModelProviders() refetchLLMModelList() refetchEmbeddingModelList() @@ -41,7 +39,7 @@ const useRefreshPluginList = () => { } // agent select - if (PluginType.agent.includes(manifest.category) || refreshAllType) + if ((manifest && PluginType.agent.includes(manifest.category)) || refreshAllType) invalidateStrategyProviders() }, } diff --git a/web/i18n/zh-Hans/app-overview.ts b/web/i18n/zh-Hans/app-overview.ts index 6274a64f13..5232fc0c63 100644 --- a/web/i18n/zh-Hans/app-overview.ts +++ b/web/i18n/zh-Hans/app-overview.ts @@ -148,15 +148,15 @@ const translation = { }, avgSessionInteractions: { title: '平均会话互动数', - explanation: '反应每个会话用户的持续沟通次数,如果用户与 AI 问答了 10 轮,即为 10。该指标反映了用户粘性。仅在对话型应用提供。', + explanation: '反映每个会话用户的持续沟通次数,如果用户与 AI 问答了 10 轮,即为 10。该指标反映了用户粘性。仅在对话型应用提供。', }, avgUserInteractions: { title: '平均用户调用次数', - explanation: '反应每天用户的使用次数。该指标反映了用户粘性。', + explanation: '反映每天用户的使用次数。该指标反映了用户粘性。', }, userSatisfactionRate: { title: '用户满意度', - explanation: '每 1000 条消息的点赞数。反应了用户对回答十分满意的比例。', + explanation: '每 1000 条消息的点赞数。反映了用户对回答十分满意的比例。', }, avgResponseTime: { title: '平均响应时间', diff --git a/web/i18n/zh-Hans/billing.ts b/web/i18n/zh-Hans/billing.ts index bc20839abc..84bcac6590 100644 --- a/web/i18n/zh-Hans/billing.ts +++ b/web/i18n/zh-Hans/billing.ts @@ -52,7 +52,7 @@ const translation = { communityForums: '社区论坛', emailSupport: '电子邮件支持', priorityEmail: '优先电子邮件和聊天支持', - logoChange: 'Logo更改', + logoChange: 'Logo 更改', SSOAuthentication: 'SSO 认证', personalizedSupport: '个性化支持', dedicatedAPISupport: '专用 API 支持', diff --git a/web/i18n/zh-Hans/run-log.ts b/web/i18n/zh-Hans/run-log.ts index dc93e9aeb0..0cf49b03fd 100644 --- a/web/i18n/zh-Hans/run-log.ts +++ b/web/i18n/zh-Hans/run-log.ts @@ -19,7 +19,7 @@ const translation = { steps: '运行步数', }, resultEmpty: { - title: '本次运行仅输出JSON格式,', + title: '本次运行仅输出 JSON 格式,', tipLeft: '请转到', link: '详细信息面板', tipRight: '查看它。', diff --git a/web/i18n/zh-Hant/app-overview.ts b/web/i18n/zh-Hant/app-overview.ts index 956fa9a1c9..e7870d90c7 100644 --- a/web/i18n/zh-Hant/app-overview.ts +++ b/web/i18n/zh-Hant/app-overview.ts @@ -148,15 +148,15 @@ const translation = { }, avgSessionInteractions: { title: '平均會話互動數', - explanation: '反應每個會話使用者的持續溝通次數,如果使用者與 AI 問答了 10 輪,即為 10。該指標反映了使用者粘性。僅在對話型應用提供。', + explanation: '反映每個會話使用者的持續溝通次數,如果使用者與 AI 問答了 10 輪,即為 10。該指標反映了使用者粘性。僅在對話型應用提供。', }, avgUserInteractions: { title: '平均使用者呼叫次數', - explanation: '反應每天使用者的使用次數。該指標反映了使用者粘性。', + explanation: '反映每天使用者的使用次數。該指標反映了使用者粘性。', }, userSatisfactionRate: { title: '使用者滿意度', - explanation: '每 1000 條訊息的點贊數。反應了使用者對回答十分滿意的比例。', + explanation: '每 1000 條訊息的點贊數。反映了使用者對回答十分滿意的比例。', }, avgResponseTime: { title: '平均響應時間', diff --git a/web/i18n/zh-Hant/billing.ts b/web/i18n/zh-Hant/billing.ts index f318b6fa66..208102c5be 100644 --- a/web/i18n/zh-Hant/billing.ts +++ b/web/i18n/zh-Hant/billing.ts @@ -51,7 +51,7 @@ const translation = { communityForums: '社群論壇', emailSupport: '電子郵件支援', priorityEmail: '優先電子郵件和聊天支援', - logoChange: 'Logo更改', + logoChange: 'Logo 更改', SSOAuthentication: 'SSO 認證', personalizedSupport: '個性化支援', dedicatedAPISupport: '專用 API 支援', diff --git a/web/i18n/zh-Hant/run-log.ts b/web/i18n/zh-Hant/run-log.ts index a22ebf8a53..c3bfb54731 100644 --- a/web/i18n/zh-Hant/run-log.ts +++ b/web/i18n/zh-Hant/run-log.ts @@ -19,7 +19,7 @@ const translation = { steps: '執行步數', }, resultEmpty: { - title: '本運行僅輸出JSON格式,', + title: '本運行僅輸出 JSON 格式,', tipLeft: '請到', link: '詳細資訊面板', tipRight: '查看它。', diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index e05ae37c88..63e7a053ab 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useCallback, useEffect } from 'react' import type { ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' @@ -39,6 +39,7 @@ import { useInvalidateAllBuiltInTools } from './use-tools' import usePermission from '@/app/components/plugins/plugin-page/use-permission' import { uninstallPlugin } from '@/service/plugins' import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list' +import { cloneDeep } from 'lodash-es' const NAME_SPACE = 'plugins' @@ -383,6 +384,7 @@ export const usePluginTaskList = (category?: PluginType) => { const { data, isFetched, + isRefetching, refetch, ...rest } = useQuery({ @@ -392,16 +394,24 @@ export const usePluginTaskList = (category?: PluginType) => { refetchInterval: (lastQuery) => { const lastData = lastQuery.state.data const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed) + return taskDone ? false : 5000 + }, + }) + + useEffect(() => { + // After first fetch, refresh plugin list each time all tasks are done + if (!isRefetching) { + const lastData = cloneDeep(data) + const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed) const taskAllFailed = lastData?.tasks.every(task => task.status === TaskStatus.failed) if (taskDone) { if (lastData?.tasks.length && !taskAllFailed) refreshPluginList(category ? { category } as any : undefined, !category) - return false } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isRefetching]) - return 5000 - }, - }) const handleRefetch = useCallback(() => { refetch() }, [refetch])