Merge branch 'langgenius:main' into tracing-weave

pull/14262/head
Bharat Ramanathan 1 year ago committed by GitHub
commit 58dfa551c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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: 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:
</br> </br> </br> </br>
**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. 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). 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) ![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. 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. 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 **5. Capacités d'agent** :
ité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. 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. 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. Toutes les offres de Dify sont accompagnées d'API correspondantes, vous permettant d'intégrer facilement Dify dans votre propre logique métier.

@ -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.entities.node_entities import NodeRunResult
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
@ -36,16 +33,3 @@ class VariableAggregatorNode(BaseNode[VariableAssignerNodeData]):
break break
return NodeRunResult(status=WorkflowNodeExecutionStatus.SUCCEEDED, outputs=outputs, inputs=inputs) 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 {}

@ -59,8 +59,8 @@ const Apps = () => {
const [activeTab, setActiveTab] = useTabSearchParams({ const [activeTab, setActiveTab] = useTabSearchParams({
defaultTab: 'all', defaultTab: 'all',
}) })
const { query: { tagIDs = [], keywords = '' }, setQuery } = useAppsQueryState() const { query: { tagIDs = [], keywords = '', isCreatedByMe: queryIsCreatedByMe = false }, setQuery } = useAppsQueryState()
const [isCreatedByMe, setIsCreatedByMe] = useState(false) const [isCreatedByMe, setIsCreatedByMe] = useState(queryIsCreatedByMe)
const [tagFilterValue, setTagFilterValue] = useState<string[]>(tagIDs) const [tagFilterValue, setTagFilterValue] = useState<string[]>(tagIDs)
const [searchKeywords, setSearchKeywords] = useState(keywords) const [searchKeywords, setSearchKeywords] = useState(keywords)
const setKeywords = useCallback((keywords: string) => { const setKeywords = useCallback((keywords: string) => {
@ -126,6 +126,12 @@ const Apps = () => {
handleTagsUpdate() handleTagsUpdate()
} }
const handleCreatedByMeChange = useCallback(() => {
const newValue = !isCreatedByMe
setIsCreatedByMe(newValue)
setQuery(prev => ({ ...prev, isCreatedByMe: newValue }))
}, [isCreatedByMe, setQuery])
return ( return (
<> <>
<div className='sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'> <div className='sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-background-body z-10 flex-wrap gap-y-2'>
@ -139,7 +145,7 @@ const Apps = () => {
className='mr-2' className='mr-2'
label={t('app.showMyCreatedAppsOnly')} label={t('app.showMyCreatedAppsOnly')}
isChecked={isCreatedByMe} isChecked={isCreatedByMe}
onChange={() => setIsCreatedByMe(!isCreatedByMe)} onChange={handleCreatedByMeChange}
/> />
<TagFilter type='app' value={tagFilterValue} onChange={handleTagsChange} /> <TagFilter type='app' value={tagFilterValue} onChange={handleTagsChange} />
<Input <Input

@ -4,18 +4,20 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
type AppsQuery = { type AppsQuery = {
tagIDs?: string[] tagIDs?: string[]
keywords?: string keywords?: string
isCreatedByMe?: boolean
} }
// Parse the query parameters from the URL search string. // Parse the query parameters from the URL search string.
function parseParams(params: ReadonlyURLSearchParams): AppsQuery { function parseParams(params: ReadonlyURLSearchParams): AppsQuery {
const tagIDs = params.get('tagIDs')?.split(';') const tagIDs = params.get('tagIDs')?.split(';')
const keywords = params.get('keywords') || undefined const keywords = params.get('keywords') || undefined
return { tagIDs, keywords } const isCreatedByMe = params.get('isCreatedByMe') === 'true'
return { tagIDs, keywords, isCreatedByMe }
} }
// Update the URL search string with the given query parameters. // Update the URL search string with the given query parameters.
function updateSearchParams(query: AppsQuery, current: URLSearchParams) { function updateSearchParams(query: AppsQuery, current: URLSearchParams) {
const { tagIDs, keywords } = query || {} const { tagIDs, keywords, isCreatedByMe } = query || {}
if (tagIDs && tagIDs.length > 0) if (tagIDs && tagIDs.length > 0)
current.set('tagIDs', tagIDs.join(';')) current.set('tagIDs', tagIDs.join(';'))
@ -26,6 +28,11 @@ function updateSearchParams(query: AppsQuery, current: URLSearchParams) {
current.set('keywords', keywords) current.set('keywords', keywords)
else else
current.delete('keywords') current.delete('keywords')
if (isCreatedByMe)
current.set('isCreatedByMe', 'true')
else
current.delete('isCreatedByMe')
} }
function useAppsQueryState() { function useAppsQueryState() {

@ -243,7 +243,7 @@ const FileUploader = ({
}, [handleDrop]) }, [handleDrop])
return ( return (
<div className="mb-5 w-[640px]"> <div className="mb-5 max-w-[640px]">
{!hideUpload && ( {!hideUpload && (
<input <input
ref={fileUploader} ref={fileUploader}

@ -14,7 +14,7 @@
} }
.dataSourceItem { .dataSourceItem {
@apply box-border relative grow shrink-0 flex items-center p-3 h-14 bg-white rounded-xl cursor-pointer; @apply w-full box-border relative flex items-center p-3 h-14 bg-white rounded-xl cursor-pointer;
border: 0.5px solid #EAECF0; border: 0.5px solid #EAECF0;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
font-weight: 500; font-weight: 500;
@ -64,7 +64,7 @@
} }
.datasetIcon { .datasetIcon {
@apply flex mr-2 w-8 h-8 rounded-lg bg-center bg-no-repeat; @apply flex shrink-0 mr-2 w-8 h-8 rounded-lg bg-center bg-no-repeat;
background-color: #F5FAFF; background-color: #F5FAFF;
background-image: url(../assets/file.svg); background-image: url(../assets/file.svg);
background-size: 16px; background-size: 16px;

@ -137,7 +137,7 @@ const StepOne = ({
} }
{ {
shouldShowDataSourceTypeList && ( shouldShowDataSourceTypeList && (
<div className='flex items-center mb-8 flex-wrap gap-4'> <div className='grid grid-cols-3 mb-8 gap-4'>
<div <div
className={cn( className={cn(
s.dataSourceItem, s.dataSourceItem,
@ -153,7 +153,12 @@ const StepOne = ({
}} }}
> >
<span className={cn(s.datasetIcon)} /> <span className={cn(s.datasetIcon)} />
<span
title={t('datasetCreation.stepOne.dataSourceType.file')}
className='truncate'
>
{t('datasetCreation.stepOne.dataSourceType.file')} {t('datasetCreation.stepOne.dataSourceType.file')}
</span>
</div> </div>
<div <div
className={cn( className={cn(
@ -170,7 +175,12 @@ const StepOne = ({
}} }}
> >
<span className={cn(s.datasetIcon, s.notion)} /> <span className={cn(s.datasetIcon, s.notion)} />
<span
title={t('datasetCreation.stepOne.dataSourceType.notion')}
className='truncate'
>
{t('datasetCreation.stepOne.dataSourceType.notion')} {t('datasetCreation.stepOne.dataSourceType.notion')}
</span>
</div> </div>
<div <div
className={cn( className={cn(
@ -181,7 +191,12 @@ const StepOne = ({
onClick={() => changeType(DataSourceType.WEB)} onClick={() => changeType(DataSourceType.WEB)}
> >
<span className={cn(s.datasetIcon, s.web)} /> <span className={cn(s.datasetIcon, s.web)} />
<span
title={t('datasetCreation.stepOne.dataSourceType.web')}
className='truncate'
>
{t('datasetCreation.stepOne.dataSourceType.web')} {t('datasetCreation.stepOne.dataSourceType.web')}
</span>
</div> </div>
</div> </div>
) )

@ -1,8 +1,8 @@
'use client' 'use client'
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation' import { usePathname, useSearchParams, useSelectedLayoutSegment } from 'next/navigation'
import type { INavSelectorProps } from './nav-selector' import type { INavSelectorProps } from './nav-selector'
import NavSelector from './nav-selector' import NavSelector from './nav-selector'
import classNames from '@/utils/classnames' import classNames from '@/utils/classnames'
@ -35,6 +35,14 @@ const Nav = ({
const [hovered, setHovered] = useState(false) const [hovered, setHovered] = useState(false)
const segment = useSelectedLayoutSegment() const segment = useSelectedLayoutSegment()
const isActivated = Array.isArray(activeSegment) ? activeSegment.includes(segment!) : segment === activeSegment 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 ( return (
<div className={` <div className={`
@ -42,7 +50,7 @@ const Nav = ({
${isActivated && 'bg-components-main-nav-nav-button-bg-active shadow-md font-semibold'} ${isActivated && 'bg-components-main-nav-nav-button-bg-active shadow-md font-semibold'}
${!curNav && !isActivated && 'hover:bg-components-main-nav-nav-button-bg-hover'} ${!curNav && !isActivated && 'hover:bg-components-main-nav-nav-button-bg-hover'}
`}> `}>
<Link href={link}> <Link href={link + (linkLastSearchParams && `?${linkLastSearchParams}`)}>
<div <div
onClick={() => setAppDetail()} onClick={() => setAppDetail()}
className={classNames(` className={classNames(`

@ -23,17 +23,15 @@ const useRefreshPluginList = () => {
// installed list // installed list
invalidateInstalledPluginList() invalidateInstalledPluginList()
if (!manifest) return
// tool page, tool select // tool page, tool select
if (PluginType.tool.includes(manifest.category) || refreshAllType) { if ((manifest && PluginType.tool.includes(manifest.category)) || refreshAllType) {
invalidateAllToolProviders() invalidateAllToolProviders()
invalidateAllBuiltInTools() invalidateAllBuiltInTools()
// TODO: update suggested tools. It's a function in hook useMarketplacePlugins,handleUpdatePlugins // TODO: update suggested tools. It's a function in hook useMarketplacePlugins,handleUpdatePlugins
} }
// model select // model select
if (PluginType.model.includes(manifest.category) || refreshAllType) { if ((manifest && PluginType.model.includes(manifest.category)) || refreshAllType) {
refreshModelProviders() refreshModelProviders()
refetchLLMModelList() refetchLLMModelList()
refetchEmbeddingModelList() refetchEmbeddingModelList()
@ -41,7 +39,7 @@ const useRefreshPluginList = () => {
} }
// agent select // agent select
if (PluginType.agent.includes(manifest.category) || refreshAllType) if ((manifest && PluginType.agent.includes(manifest.category)) || refreshAllType)
invalidateStrategyProviders() invalidateStrategyProviders()
}, },
} }

@ -148,15 +148,15 @@ const translation = {
}, },
avgSessionInteractions: { avgSessionInteractions: {
title: '平均会话互动数', title: '平均会话互动数',
explanation: '反每个会话用户的持续沟通次数,如果用户与 AI 问答了 10 轮,即为 10。该指标反映了用户粘性。仅在对话型应用提供。', explanation: '反每个会话用户的持续沟通次数,如果用户与 AI 问答了 10 轮,即为 10。该指标反映了用户粘性。仅在对话型应用提供。',
}, },
avgUserInteractions: { avgUserInteractions: {
title: '平均用户调用次数', title: '平均用户调用次数',
explanation: '反每天用户的使用次数。该指标反映了用户粘性。', explanation: '反每天用户的使用次数。该指标反映了用户粘性。',
}, },
userSatisfactionRate: { userSatisfactionRate: {
title: '用户满意度', title: '用户满意度',
explanation: '每 1000 条消息的点赞数。反了用户对回答十分满意的比例。', explanation: '每 1000 条消息的点赞数。反了用户对回答十分满意的比例。',
}, },
avgResponseTime: { avgResponseTime: {
title: '平均响应时间', title: '平均响应时间',

@ -52,7 +52,7 @@ const translation = {
communityForums: '社区论坛', communityForums: '社区论坛',
emailSupport: '电子邮件支持', emailSupport: '电子邮件支持',
priorityEmail: '优先电子邮件和聊天支持', priorityEmail: '优先电子邮件和聊天支持',
logoChange: 'Logo更改', logoChange: 'Logo 更改',
SSOAuthentication: 'SSO 认证', SSOAuthentication: 'SSO 认证',
personalizedSupport: '个性化支持', personalizedSupport: '个性化支持',
dedicatedAPISupport: '专用 API 支持', dedicatedAPISupport: '专用 API 支持',

@ -19,7 +19,7 @@ const translation = {
steps: '运行步数', steps: '运行步数',
}, },
resultEmpty: { resultEmpty: {
title: '本次运行仅输出JSON格式', title: '本次运行仅输出 JSON 格式,',
tipLeft: '请转到', tipLeft: '请转到',
link: '详细信息面板', link: '详细信息面板',
tipRight: '查看它。', tipRight: '查看它。',

@ -148,15 +148,15 @@ const translation = {
}, },
avgSessionInteractions: { avgSessionInteractions: {
title: '平均會話互動數', title: '平均會話互動數',
explanation: '反每個會話使用者的持續溝通次數,如果使用者與 AI 問答了 10 輪,即為 10。該指標反映了使用者粘性。僅在對話型應用提供。', explanation: '反每個會話使用者的持續溝通次數,如果使用者與 AI 問答了 10 輪,即為 10。該指標反映了使用者粘性。僅在對話型應用提供。',
}, },
avgUserInteractions: { avgUserInteractions: {
title: '平均使用者呼叫次數', title: '平均使用者呼叫次數',
explanation: '反每天使用者的使用次數。該指標反映了使用者粘性。', explanation: '反每天使用者的使用次數。該指標反映了使用者粘性。',
}, },
userSatisfactionRate: { userSatisfactionRate: {
title: '使用者滿意度', title: '使用者滿意度',
explanation: '每 1000 條訊息的點贊數。反了使用者對回答十分滿意的比例。', explanation: '每 1000 條訊息的點贊數。反了使用者對回答十分滿意的比例。',
}, },
avgResponseTime: { avgResponseTime: {
title: '平均響應時間', title: '平均響應時間',

@ -51,7 +51,7 @@ const translation = {
communityForums: '社群論壇', communityForums: '社群論壇',
emailSupport: '電子郵件支援', emailSupport: '電子郵件支援',
priorityEmail: '優先電子郵件和聊天支援', priorityEmail: '優先電子郵件和聊天支援',
logoChange: 'Logo更改', logoChange: 'Logo 更改',
SSOAuthentication: 'SSO 認證', SSOAuthentication: 'SSO 認證',
personalizedSupport: '個性化支援', personalizedSupport: '個性化支援',
dedicatedAPISupport: '專用 API 支援', dedicatedAPISupport: '專用 API 支援',

@ -19,7 +19,7 @@ const translation = {
steps: '執行步數', steps: '執行步數',
}, },
resultEmpty: { resultEmpty: {
title: '本運行僅輸出JSON格式', title: '本運行僅輸出 JSON 格式,',
tipLeft: '請到', tipLeft: '請到',
link: '詳細資訊面板', link: '詳細資訊面板',
tipRight: '查看它。', tipRight: '查看它。',

@ -1,4 +1,4 @@
import { useCallback } from 'react' import { useCallback, useEffect } from 'react'
import type { import type {
ModelProvider, ModelProvider,
} from '@/app/components/header/account-setting/model-provider-page/declarations' } 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 usePermission from '@/app/components/plugins/plugin-page/use-permission'
import { uninstallPlugin } from '@/service/plugins' import { uninstallPlugin } from '@/service/plugins'
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list' import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
import { cloneDeep } from 'lodash-es'
const NAME_SPACE = 'plugins' const NAME_SPACE = 'plugins'
@ -383,6 +384,7 @@ export const usePluginTaskList = (category?: PluginType) => {
const { const {
data, data,
isFetched, isFetched,
isRefetching,
refetch, refetch,
...rest ...rest
} = useQuery({ } = useQuery({
@ -392,16 +394,24 @@ export const usePluginTaskList = (category?: PluginType) => {
refetchInterval: (lastQuery) => { refetchInterval: (lastQuery) => {
const lastData = lastQuery.state.data const lastData = lastQuery.state.data
const taskDone = lastData?.tasks.every(task => task.status === TaskStatus.success || task.status === TaskStatus.failed) 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) const taskAllFailed = lastData?.tasks.every(task => task.status === TaskStatus.failed)
if (taskDone) { if (taskDone) {
if (lastData?.tasks.length && !taskAllFailed) if (lastData?.tasks.length && !taskAllFailed)
refreshPluginList(category ? { category } as any : undefined, !category) refreshPluginList(category ? { category } as any : undefined, !category)
return false
} }
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isRefetching])
return 5000
},
})
const handleRefetch = useCallback(() => { const handleRefetch = useCallback(() => {
refetch() refetch()
}, [refetch]) }, [refetch])

Loading…
Cancel
Save