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:
</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.
@ -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.

@ -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 {}

@ -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<string[]>(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 (
<>
<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'
label={t('app.showMyCreatedAppsOnly')}
isChecked={isCreatedByMe}
onChange={() => setIsCreatedByMe(!isCreatedByMe)}
onChange={handleCreatedByMeChange}
/>
<TagFilter type='app' value={tagFilterValue} onChange={handleTagsChange} />
<Input

@ -4,18 +4,20 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
type AppsQuery = {
tagIDs?: string[]
keywords?: string
isCreatedByMe?: boolean
}
// Parse the query parameters from the URL search string.
function parseParams(params: ReadonlyURLSearchParams): AppsQuery {
const tagIDs = params.get('tagIDs')?.split(';')
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.
function updateSearchParams(query: AppsQuery, current: URLSearchParams) {
const { tagIDs, keywords } = query || {}
const { tagIDs, keywords, isCreatedByMe } = query || {}
if (tagIDs && tagIDs.length > 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() {

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

@ -14,7 +14,7 @@
}
.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;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
font-weight: 500;
@ -64,7 +64,7 @@
}
.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-image: url(../assets/file.svg);
background-size: 16px;

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

@ -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 (
<div className={`
@ -42,7 +50,7 @@ const Nav = ({
${isActivated && 'bg-components-main-nav-nav-button-bg-active shadow-md font-semibold'}
${!curNav && !isActivated && 'hover:bg-components-main-nav-nav-button-bg-hover'}
`}>
<Link href={link}>
<Link href={link + (linkLastSearchParams && `?${linkLastSearchParams}`)}>
<div
onClick={() => setAppDetail()}
className={classNames(`

@ -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()
},
}

@ -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: '平均响应时间',

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

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

@ -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: '平均響應時間',

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

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

@ -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])

Loading…
Cancel
Save