Compare commits

..

2 Commits

Author SHA1 Message Date
Joel
5abfe7e46f chore: to fix build error again 2025-02-19 13:56:13 +08:00
Joel
2bf4a20875 fix: build web error 2025-02-19 13:40:20 +08:00
55 changed files with 218 additions and 254 deletions

View File

@@ -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,25 +63,27 @@ 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. Capacités d'agent** :
**5. Capac
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.
**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.

View File

@@ -164,7 +164,7 @@ DifyはオープンソースのLLMアプリケーション開発プラットフ
- **企業/組織向けのDify</br>**
企業中心の機能を提供しています。[メールを送信](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry)して企業のニーズについて相談してください。 </br>
> AWSを使用しているスタートアップ企業や中小企業の場合は、[AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t23mebxzwjhu6)のDify Premiumをチェックして、ワンクリックで自分のAWS VPCにデプロイできます。さらに、手頃な価格のAMIオファリングして、ロゴやブランディングをカスタマイズしてアプリケーションを作成するオプションがあります。
> AWSを使用しているスタートアップ企業や中小企業の場合は、[AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-t22mebxzwjhu6)のDify Premiumをチェックして、ワンクリックで自分のAWS VPCにデプロイできます。さらに、手頃な価格のAMIオファリングして、ロゴやブランディングをカスタマイズしてアプリケーションを作成するオプションがあります。
## 最新の情報を入手

View File

@@ -1,5 +1,3 @@
from urllib.parse import quote
from flask import Response, request
from flask_restful import Resource, reqparse # type: ignore
from werkzeug.exceptions import NotFound
@@ -73,8 +71,7 @@ class FilePreviewApi(Resource):
if upload_file.size > 0:
response.headers["Content-Length"] = str(upload_file.size)
if args["as_attachment"]:
encoded_filename = quote(upload_file.name)
response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{encoded_filename}"
response.headers["Content-Disposition"] = f"attachment; filename={upload_file.name}"
return response

View File

@@ -336,10 +336,6 @@ class DocumentUpdateByFileApi(DatasetApiResource):
if not dataset:
raise ValueError("Dataset is not exist.")
# indexing_technique is already set in dataset since this is an update
args["indexing_technique"] = dataset.indexing_technique
if "file" in request.files:
# save file info
file = request.files["file"]

View File

@@ -42,6 +42,7 @@ class MessageBasedAppGenerator(BaseAppGenerator):
ChatAppGenerateEntity,
CompletionAppGenerateEntity,
AgentChatAppGenerateEntity,
AgentChatAppGenerateEntity,
],
queue_manager: AppQueueManager,
conversation: Conversation,

View File

@@ -101,13 +101,11 @@ class ProviderManager:
)
# append providers with langgenius/openai/openai
provider_name_list = list(provider_name_to_provider_records_dict.keys())
for provider_name in provider_name_list:
for provider_name in list(provider_name_to_provider_records_dict.keys()):
provider_id = ModelProviderID(provider_name)
if str(provider_id) not in provider_name_list:
provider_name_to_provider_records_dict[str(provider_id)] = provider_name_to_provider_records_dict[
provider_name
]
provider_name_to_provider_records_dict[str(provider_id)] = provider_name_to_provider_records_dict[
provider_name
]
# Get all provider model records of the workspace
provider_name_to_provider_model_records_dict = self._get_all_provider_models(tenant_id)

View File

@@ -111,9 +111,8 @@ class ChromaVector(BaseVector):
for index in range(len(ids)):
distance = distances[index]
metadata = dict(metadatas[index])
score = 1 - distance
if score > score_threshold:
metadata["score"] = score
if distance >= score_threshold:
metadata["score"] = distance
doc = Document(
page_content=documents[index],
metadata=metadata,

View File

@@ -105,10 +105,10 @@ class ApiTool(Tool):
needed_parameters = [parameter for parameter in (self.api_bundle.parameters or []) if parameter.required]
for parameter in needed_parameters:
if parameter.required and parameter.name not in parameters:
if parameter.default is not None:
parameters[parameter.name] = parameter.default
else:
raise ToolParameterValidationError(f"Missing required parameter {parameter.name}")
raise ToolParameterValidationError(f"Missing required parameter {parameter.name}")
if parameter.default is not None and parameter.name not in parameters:
parameters[parameter.name] = parameter.default
return headers

View File

@@ -246,11 +246,10 @@ class ToolEngine:
+ "you do not need to create it, just tell the user to check it now."
)
elif response.type == ToolInvokeMessage.MessageType.JSON:
result = json.dumps(
cast(ToolInvokeMessage.JsonMessage, response.message).json_object, ensure_ascii=False
)
text = json.dumps(cast(ToolInvokeMessage.JsonMessage, response.message).json_object, ensure_ascii=False)
result += f"tool response: {text}."
else:
result += str(response.message)
result += f"tool response: {response.message!r}."
return result

View File

@@ -590,7 +590,6 @@ class IterationNode(BaseNode[IterationNodeData]):
with flask_app.app_context():
parallel_mode_run_id = uuid.uuid4().hex
graph_engine_copy = graph_engine.create_copy()
graph_engine_copy.graph_runtime_state.total_tokens = 0
variable_pool_copy = graph_engine_copy.graph_runtime_state.variable_pool
variable_pool_copy.add([self.node_id, "index"], index)
variable_pool_copy.add([self.node_id, "item"], item)

View File

@@ -1,3 +1,6 @@
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
@@ -33,3 +36,16 @@ 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 {}

View File

@@ -1,5 +1,5 @@
from core.helper import marketplace
from core.plugin.entities.plugin import ModelProviderID, PluginDependency, PluginInstallationSource, ToolProviderID
from core.plugin.entities.plugin import GenericProviderID, PluginDependency, PluginInstallationSource
from core.plugin.manager.plugin import PluginInstallationManager
@@ -12,7 +12,10 @@ class DependenciesAnalysisService:
Convert the tool id to the plugin_id
"""
try:
return ToolProviderID(tool_id).plugin_id
tool_provider_id = GenericProviderID(tool_id)
if tool_id in ["jina", "siliconflow"]:
tool_provider_id.plugin_name = tool_provider_id.plugin_name + "_tool"
return tool_provider_id.plugin_id
except Exception as e:
raise e
@@ -24,7 +27,11 @@ class DependenciesAnalysisService:
Convert the model provider id to the plugin_id
"""
try:
return ModelProviderID(model_provider_id).plugin_id
generic_provider_id = GenericProviderID(model_provider_id)
if model_provider_id == "google":
generic_provider_id.plugin_name = "gemini"
return generic_provider_id.plugin_id
except Exception as e:
raise e

View File

@@ -14,8 +14,9 @@ from flask import Flask, current_app
from sqlalchemy.orm import Session
from core.agent.entities import AgentToolEntity
from core.entities import DEFAULT_PLUGIN_ID
from core.helper import marketplace
from core.plugin.entities.plugin import ModelProviderID, PluginInstallationSource, ToolProviderID
from core.plugin.entities.plugin import PluginInstallationSource
from core.plugin.entities.plugin_daemon import PluginInstallTaskStatus
from core.plugin.manager.plugin import PluginInstallationManager
from core.tools.entities.tool_entities import ToolProviderType
@@ -202,7 +203,13 @@ class PluginMigration:
result = []
for row in rs:
provider_name = str(row[0])
result.append(ModelProviderID(provider_name).plugin_id)
if provider_name and "/" not in provider_name:
if provider_name == "google":
provider_name = "gemini"
result.append(DEFAULT_PLUGIN_ID + "/" + provider_name)
elif provider_name:
result.append(provider_name)
return result
@@ -215,10 +222,30 @@ class PluginMigration:
rs = session.query(BuiltinToolProvider).filter(BuiltinToolProvider.tenant_id == tenant_id).all()
result = []
for row in rs:
result.append(ToolProviderID(row.provider).plugin_id)
if "/" not in row.provider:
result.append(DEFAULT_PLUGIN_ID + "/" + row.provider)
else:
result.append(row.provider)
return result
@classmethod
def _handle_builtin_tool_provider(cls, provider_name: str) -> str:
"""
Handle builtin tool provider.
"""
if provider_name == "jina":
provider_name = "jina_tool"
elif provider_name == "siliconflow":
provider_name = "siliconflow_tool"
elif provider_name == "stepfun":
provider_name = "stepfun_tool"
if "/" not in provider_name:
return DEFAULT_PLUGIN_ID + "/" + provider_name
else:
return provider_name
@classmethod
def extract_workflow_tables(cls, tenant_id: str) -> Sequence[str]:
"""
@@ -239,7 +266,8 @@ class PluginMigration:
provider_name = data.get("provider_name")
provider_type = data.get("provider_type")
if provider_name not in excluded_providers and provider_type == ToolProviderType.BUILT_IN.value:
result.append(ToolProviderID(provider_name).plugin_id)
provider_name = cls._handle_builtin_tool_provider(provider_name)
result.append(provider_name)
return result
@@ -270,7 +298,7 @@ class PluginMigration:
tool_entity.provider_type == ToolProviderType.BUILT_IN.value
and tool_entity.provider_id not in excluded_providers
):
result.append(ToolProviderID(tool_entity.provider_id).plugin_id)
result.append(cls._handle_builtin_tool_provider(tool_entity.provider_id))
except Exception:
logger.exception(f"Failed to process tool {tool}")
@@ -358,7 +386,7 @@ class PluginMigration:
batch_plugin_identifiers = [
plugins["plugins"][plugin_id]
for plugin_id in batch_plugin_ids
if plugin_id not in installed_plugins_ids and plugin_id in plugins["plugins"]
if plugin_id not in installed_plugins_ids
]
manager.install_from_identifiers(
tenant_id,

View File

@@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env
services:
# API service
api:
image: langgenius/dify-api:0.15.3
image: langgenius/dify-api:1.0.0
restart: always
environment:
# Use the shared environment variables.
@@ -27,7 +27,7 @@ services:
# worker service
# The Celery worker for processing the queue.
worker:
image: langgenius/dify-api:0.15.3
image: langgenius/dify-api:1.0.0
restart: always
environment:
# Use the shared environment variables.
@@ -51,7 +51,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:0.15.3
image: langgenius/dify-web:1.0.0
restart: always
environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
@@ -64,7 +64,6 @@ services:
MARKETPLACE_URL: ${MARKETPLACE_URL:-https://marketplace.dify.ai}
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-}
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
PM2_INSTANCES: ${PM2_INSTANCES:-2}
# The postgres database.
db:
@@ -129,7 +128,7 @@ services:
# plugin daemon
plugin_daemon:
image: langgenius/dify-plugin-daemon:0.0.2-local
image: langgenius/dify-plugin-daemon:0.0.1-local
restart: always
environment:
# Use the shared environment variables.

View File

@@ -66,7 +66,7 @@ services:
# plugin daemon
plugin_daemon:
image: langgenius/dify-plugin-daemon:0.0.2-local
image: langgenius/dify-plugin-daemon:0.0.1-local
restart: always
environment:
# Use the shared environment variables.

View File

@@ -414,7 +414,7 @@ x-shared-env: &shared-api-worker-env
services:
# API service
api:
image: langgenius/dify-api:0.15.3
image: langgenius/dify-api:1.0.0
restart: always
environment:
# Use the shared environment variables.
@@ -439,7 +439,7 @@ services:
# worker service
# The Celery worker for processing the queue.
worker:
image: langgenius/dify-api:0.15.3
image: langgenius/dify-api:1.0.0
restart: always
environment:
# Use the shared environment variables.
@@ -463,7 +463,7 @@ services:
# Frontend web application.
web:
image: langgenius/dify-web:0.15.3
image: langgenius/dify-web:1.0.0
restart: always
environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
@@ -476,7 +476,6 @@ services:
MARKETPLACE_URL: ${MARKETPLACE_URL:-https://marketplace.dify.ai}
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-}
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
PM2_INSTANCES: ${PM2_INSTANCES:-2}
# The postgres database.
db:
@@ -541,7 +540,7 @@ services:
# plugin daemon
plugin_daemon:
image: langgenius/dify-plugin-daemon:0.0.2-local
image: langgenius/dify-plugin-daemon:0.0.1-local
restart: always
environment:
# Use the shared environment variables.

View File

@@ -46,7 +46,6 @@ ENV MARKETPLACE_API_URL=http://127.0.0.1:5001
ENV MARKETPLACE_URL=http://127.0.0.1:5001
ENV PORT=3000
ENV NEXT_TELEMETRY_DISABLED=1
ENV PM2_INSTANCES=2
# set timezone
ENV TZ=UTC
@@ -59,6 +58,7 @@ COPY --from=builder /app/web/public ./public
COPY --from=builder /app/web/.next/standalone ./
COPY --from=builder /app/web/.next/static ./.next/static
COPY docker/pm2.json ./pm2.json
COPY docker/entrypoint.sh ./entrypoint.sh

View File

@@ -70,8 +70,6 @@ If you want to customize the host and port:
pnpm run start --port=3001 --host=0.0.0.0
```
If you want to customize the number of instances launched by PM2, you can configure `PM2_INSTANCES` in `docker-compose.yaml` or `Dockerfile`.
## Storybook
This project uses [Storybook](https://storybook.js.org/) for UI component development.

View File

@@ -53,7 +53,7 @@ export default function ChartView({ appId }: IChartViewProps) {
className='mt-0 !w-40'
onSelect={(item) => {
const id = item.value
const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1'
const value = TIME_PERIOD_MAPPING[id]?.value || '-1'
const name = item.name || t('appLog.filter.period.allTime')
onSelect({ value, name })
}}

View File

@@ -59,8 +59,8 @@ const Apps = () => {
const [activeTab, setActiveTab] = useTabSearchParams({
defaultTab: 'all',
})
const { query: { tagIDs = [], keywords = '', isCreatedByMe: queryIsCreatedByMe = false }, setQuery } = useAppsQueryState()
const [isCreatedByMe, setIsCreatedByMe] = useState(queryIsCreatedByMe)
const { query: { tagIDs = [], keywords = '' }, setQuery } = useAppsQueryState()
const [isCreatedByMe, setIsCreatedByMe] = useState(false)
const [tagFilterValue, setTagFilterValue] = useState<string[]>(tagIDs)
const [searchKeywords, setSearchKeywords] = useState(keywords)
const setKeywords = useCallback((keywords: string) => {
@@ -126,12 +126,6 @@ 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'>
@@ -145,7 +139,7 @@ const Apps = () => {
className='mr-2'
label={t('app.showMyCreatedAppsOnly')}
isChecked={isCreatedByMe}
onChange={handleCreatedByMeChange}
onChange={() => setIsCreatedByMe(!isCreatedByMe)}
/>
<TagFilter type='app' value={tagFilterValue} onChange={handleTagsChange} />
<Input

View File

@@ -4,20 +4,18 @@ 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
const isCreatedByMe = params.get('isCreatedByMe') === 'true'
return { tagIDs, keywords, isCreatedByMe }
return { tagIDs, keywords }
}
// Update the URL search string with the given query parameters.
function updateSearchParams(query: AppsQuery, current: URLSearchParams) {
const { tagIDs, keywords, isCreatedByMe } = query || {}
const { tagIDs, keywords } = query || {}
if (tagIDs && tagIDs.length > 0)
current.set('tagIDs', tagIDs.join(';'))
@@ -28,11 +26,6 @@ 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() {

View File

@@ -26,12 +26,12 @@ import { MAX_TOOLS_NUM } from '@/config'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import Tooltip from '@/app/components/base/tooltip'
import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other'
// import AddToolModal from '@/app/components/tools/add-tool-modal'
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
import { updateBuiltInToolCredential } from '@/service/tools'
import cn from '@/utils/classnames'
import ToolPicker from '@/app/components/workflow/block-selector/tool-picker'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
import { canFindTool } from '@/utils'
type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null
const AgentTools: FC = () => {
@@ -43,7 +43,7 @@ const AgentTools: FC = () => {
const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
const currentCollection = useMemo(() => {
if (!currentTool) return null
const collection = collectionList.find(collection => canFindTool(collection.id, currentTool?.provider_id) && collection.type === currentTool?.provider_type)
const collection = collectionList.find(collection => collection.id.split('/').pop() === currentTool?.provider_id.split('/').pop() && collection.type === currentTool?.provider_type)
return collection
}, [currentTool, collectionList])
const [isShowSettingTool, setIsShowSettingTool] = useState(false)
@@ -51,7 +51,7 @@ const AgentTools: FC = () => {
const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => {
const collection = collectionList.find(
collection =>
canFindTool(collection.id, item.provider_id)
collection.id.split('/').pop() === item.provider_id.split('/').pop()
&& collection.type === item.provider_type,
)
const icon = collection?.icon

View File

@@ -97,7 +97,7 @@ const Uploader: FC<Props> = ({
style={{ display: 'none' }}
type="file"
id="fileUploader"
accept='.yaml,.yml'
accept='.yml'
onChange={fileChangeHandle}
/>
<div ref={dropRef}>

View File

@@ -635,10 +635,9 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
const [currentConversation, setCurrentConversation] = useState<ChatConversationGeneralDetail | CompletionConversationGeneralDetail | undefined>() // Currently selected conversation
const isChatMode = appDetail.mode !== 'completion' // Whether the app is a chat app
const isChatflow = appDetail.mode === 'advanced-chat' // Whether the app is a chatflow app
const { setShowPromptLogModal, setShowAgentLogModal, setShowMessageLogModal } = useAppStore(useShallow(state => ({
const { setShowPromptLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({
setShowPromptLogModal: state.setShowPromptLogModal,
setShowAgentLogModal: state.setShowAgentLogModal,
setShowMessageLogModal: state.setShowMessageLogModal,
})))
// Annotated data needs to be highlighted
@@ -665,7 +664,6 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
setCurrentConversation(undefined)
setShowPromptLogModal(false)
setShowAgentLogModal(false)
setShowMessageLogModal(false)
}
if (!logs)

View File

@@ -100,7 +100,7 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch
let targetNode: ChatItemInTree | undefined
// find path to the target message
const stack = tree.slice().reverse().map(rootNode => ({
const stack = tree.toReversed().map(rootNode => ({
node: rootNode,
path: [rootNode],
}))

View File

@@ -68,9 +68,6 @@ export const ThinkBlock = ({ children, ...props }: any) => {
const displayContent = removeEndThink(children)
const { t } = useTranslation()
if (!(props['data-think'] ?? false))
return (<details {...props}>{children}</details>)
return (
<details {...(!isComplete && { open: true })} className="group">
<summary className="text-gray-500 font-bold list-none pl-2 flex items-center cursor-pointer select-none whitespace-nowrap">

View File

@@ -68,9 +68,13 @@ const preprocessLaTeX = (content: string) => {
}
const preprocessThinkTag = (content: string) => {
if (!(content.trim().startsWith('<think>\n') || content.trim().startsWith('<details style=')))
return content
return flow([
(str: string) => str.replace('<think>\n', '<details data-think=true>\n'),
(str: string) => str.replace('\n</think>', '\n[ENDTHINKFLAG]</details>'),
(str: string) => str.replaceAll('<think>\n', '<details>\n'),
(str: string) => str.replaceAll('\n</think>', '\n[ENDTHINKFLAG]</details>'),
(str: string) => str.replaceAll('\n</details>', '\n[ENDTHINKFLAG]</details>'),
])(content)
}
@@ -265,7 +269,7 @@ export function Markdown(props: { content: string; className?: string }) {
}
},
]}
disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', 'input']}
disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body']}
components={{
code: CodeBlock,
img: Img,

View File

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

View File

@@ -14,7 +14,7 @@
}
.dataSourceItem {
@apply w-full box-border relative flex items-center p-3 h-14 bg-white rounded-xl cursor-pointer;
@apply box-border relative grow shrink-0 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 shrink-0 mr-2 w-8 h-8 rounded-lg bg-center bg-no-repeat;
@apply flex 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;

View File

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

View File

@@ -50,6 +50,7 @@ const ChildSegmentDetail: FC<IChildSegmentDetailProps> = ({
const handleCancel = () => {
onCancel()
setContent(childChunkInfo?.content || '')
}
const handleSave = () => {

View File

@@ -80,7 +80,7 @@ export const useSegmentListContext = (selector: (value: SegmentListContextValue)
return useContextSelector(SegmentListContext, selector)
}
type ICompletedProps = {
interface ICompletedProps {
embeddingAvailable: boolean
showNewSegmentModal: boolean
onNewSegmentModalChange: (state: boolean) => void
@@ -357,7 +357,6 @@ const Completed: FC<ICompletedProps> = ({
if (seg.id === segmentId) {
seg.answer = res.data.answer
seg.content = res.data.content
seg.sign_content = res.data.sign_content
seg.keywords = res.data.keywords
seg.word_count = res.data.word_count
seg.hit_count = res.data.hit_count

View File

@@ -68,6 +68,7 @@ const NewChildSegmentModal: FC<NewChildSegmentModalProps> = ({
const handleCancel = (actionType: 'esc' | 'add' = 'esc') => {
if (actionType === 'esc' || !addAnother)
onCancel()
setContent('')
}
const { mutateAsync: addChildSegment } = useAddChildSegment()

View File

@@ -49,7 +49,7 @@ const ChunkContent: FC<ChunkContentProps> = ({
isFullDocMode ? 'line-clamp-3' : isCollapsed ? 'line-clamp-2' : 'line-clamp-20',
className,
)}
content={sign_content || content || ''}
content={sign_content || content}
/>
}

View File

@@ -57,6 +57,9 @@ const SegmentDetail: FC<ISegmentDetailProps> = ({
const handleCancel = () => {
onCancel()
setQuestion(segInfo?.content || '')
setAnswer(segInfo?.answer || '')
setKeywords(segInfo?.keywords || [])
}
const handleSave = () => {

View File

@@ -70,6 +70,9 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
const handleCancel = (actionType: 'esc' | 'add' = 'esc') => {
if (actionType === 'esc' || !addAnother)
onCancel()
setQuestion('')
setAnswer('')
setKeywords([])
}
const { mutateAsync: addSegment } = useAddSegment()

View File

@@ -8,6 +8,7 @@ import { switchWorkspace } from '@/service/common'
import { useWorkspacesContext } from '@/context/workspace-context'
import { useProviderContext } from '@/context/provider-context'
import { ToastContext } from '@/app/components/base/toast'
import PremiumBadge from '@/app/components/base/premium-badge'
const WorkplaceSelector = () => {
const { t } = useTranslation()
@@ -71,6 +72,15 @@ const WorkplaceSelector = () => {
<div className='flex py-1 pl-3 pr-2 items-center gap-2 self-stretch hover:bg-state-base-hover rounded-lg' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}>
<div className='flex items-center justify-center w-6 h-6 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600'>{workspace.name[0].toLocaleUpperCase()}</div>
<div className='line-clamp-1 grow overflow-hidden text-text-secondary text-ellipsis system-md-regular cursor-pointer'>{workspace.name}</div>
{
<PremiumBadge size='s' color='gray' allowHover={false}>
<div className='system-2xs-medium'>
<span className='p-[2px]'>
{plan.type === 'professional' ? 'PRO' : plan.type.toUpperCase()}
</span>
</div>
</PremiumBadge>
}
</div>
))
}

View File

@@ -41,7 +41,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
{
currentQuota && (
<div className='flex items-center h-4 text-xs text-text-tertiary'>
<span className='mr-0.5 system-md-semibold-uppercase text-text-secondary'>{formatNumber(Math.max((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0), 0))}</span>
<span className='mr-0.5 system-md-semibold-uppercase text-text-secondary'>{formatNumber((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0))}</span>
{
currentQuota?.quota_unit === QuotaUnitEnum.tokens && 'Tokens'
}

View File

@@ -4,6 +4,8 @@ import Link from 'next/link'
import { useBoolean } from 'ahooks'
import { useSelectedLayoutSegment } from 'next/navigation'
import { Bars3Icon } from '@heroicons/react/20/solid'
import { SparklesSoft } from '@/app/components/base/icons/src/public/common'
import PremiumBadge from '../base/premium-badge'
import AccountDropdown from './account-dropdown'
import AppNav from './app-nav'
import DatasetNav from './dataset-nav'
@@ -11,6 +13,7 @@ import EnvNav from './env-nav'
import PluginsNav from './plugins-nav'
import ExploreNav from './explore-nav'
import ToolsNav from './tools-nav'
import LicenseNav from './license-env'
import { WorkspaceProvider } from '@/context/workspace-context'
import { useAppContext } from '@/context/app-context'
import LogoSite from '@/app/components/base/logo/logo-site'
@@ -18,9 +21,7 @@ import WorkplaceSelector from '@/app/components/header/account-dropdown/workplac
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { useProviderContext } from '@/context/provider-context'
import { useModalContext } from '@/context/modal-context'
import LicenseNav from './license-env'
import PlanBadge from './plan-badge'
import { Plan } from '../billing/type'
import { useTranslation } from 'react-i18next'
const navClassName = `
flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl
@@ -30,13 +31,15 @@ const navClassName = `
const Header = () => {
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
const { t } = useTranslation()
const selectedSegment = useSelectedLayoutSegment()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false)
const { enableBilling, plan } = useProviderContext()
const { setShowPricingModal, setShowAccountSettingModal } = useModalContext()
const isFreePlan = plan.type === Plan.sandbox
const isFreePlan = plan.type === 'sandbox'
const handlePlanClick = useCallback(() => {
if (isFreePlan)
setShowPricingModal()
@@ -68,7 +71,18 @@ const Header = () => {
<WorkspaceProvider>
<WorkplaceSelector />
</WorkspaceProvider>
{enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />}
{enableBilling && (
<div className='select-none'>
<PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}>
<SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
<div className='system-xs-medium'>
<span className='p-1'>
{t('billing.upgradeBtn.encourageShort')}
</span>
</div>
</PremiumBadge>
</div>
)}
</div>
</div>
}
@@ -79,7 +93,20 @@ const Header = () => {
<LogoSite />
</Link>
<div className='font-light text-divider-deep'>/</div>
{enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />}
{
enableBilling && (
<div className='select-none'>
<PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}>
<SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
<div className='system-xs-medium'>
<span className='p-1'>
{t('billing.upgradeBtn.encourageShort')}
</span>
</div>
</PremiumBadge>
</div>
)
}
</div >
)}
{
@@ -93,6 +120,7 @@ const Header = () => {
)
}
<div className='flex items-center shrink-0'>
<LicenseNav />
<EnvNav />
<div className='mr-3'>
<PluginsNav />

View File

@@ -5,8 +5,6 @@ import { LicenseStatus } from '@/types/feature'
import { useTranslation } from 'react-i18next'
import { useContextSelector } from 'use-context-selector'
import dayjs from 'dayjs'
import PremiumBadge from '../../base/premium-badge'
import { RiHourglass2Fill } from '@remixicon/react'
const LicenseNav = () => {
const { t } = useTranslation()
@@ -15,16 +13,15 @@ const LicenseNav = () => {
if (systemFeatures.license?.status === LicenseStatus.EXPIRING) {
const expiredAt = systemFeatures.license?.expired_at
const count = dayjs(expiredAt).diff(dayjs(), 'days')
return <PremiumBadge color='orange' className='select-none'>
<RiHourglass2Fill className='flex items-center pl-0.5 size-3 text-components-premium-badge-indigo-text-stop-0' />
{count <= 1 && <span className='system-xs-medium px-0.5'>{t('common.license.expiring', { count })}</span>}
{count > 1 && <span className='system-xs-medium px-0.5'>{t('common.license.expiring_plural', { count })}</span>}
</PremiumBadge>
return <div className='px-2 py-1 mr-4 rounded-full bg-util-colors-orange-orange-50 border-util-colors-orange-orange-100 system-xs-medium text-util-colors-orange-orange-600'>
{count <= 1 && <span>{t('common.license.expiring', { count })}</span>}
{count > 1 && <span>{t('common.license.expiring_plural', { count })}</span>}
</div>
}
if (systemFeatures.license.status === LicenseStatus.ACTIVE) {
return <PremiumBadge color="indigo" className='select-none'>
<span className='system-xs-medium px-1'>Enterprise</span>
</PremiumBadge>
return <div className='px-2 py-1 mr-4 rounded-md bg-util-colors-indigo-indigo-50 border-util-colors-indigo-indigo-100 system-xs-medium text-util-colors-indigo-indigo-600'>
Enterprise
</div>
}
return null
}

View File

@@ -1,8 +1,8 @@
'use client'
import React, { useEffect, useState } from 'react'
import React, { useState } from 'react'
import Link from 'next/link'
import { usePathname, useSearchParams, useSelectedLayoutSegment } from 'next/navigation'
import { useSelectedLayoutSegment } from 'next/navigation'
import type { INavSelectorProps } from './nav-selector'
import NavSelector from './nav-selector'
import classNames from '@/utils/classnames'
@@ -35,14 +35,6 @@ 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={`
@@ -50,7 +42,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 + (linkLastSearchParams && `?${linkLastSearchParams}`)}>
<Link href={link}>
<div
onClick={() => setAppDetail()}
className={classNames(`

View File

@@ -1,70 +0,0 @@
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'
import PremiumBadge from '../../base/premium-badge'
import { Plan } from '../../billing/type'
type PlanBadgeProps = {
plan: Plan
size?: 's' | 'm'
allowHover?: boolean
sandboxAsUpgrade?: boolean
onClick?: () => void
}
const PlanBadge: FC<PlanBadgeProps> = ({ plan, allowHover, size = 'm', sandboxAsUpgrade = false, onClick }) => {
const { isFetchedPlan } = useProviderContext()
const { t } = useTranslation()
if (!isFetchedPlan) return null
if (plan === Plan.sandbox && sandboxAsUpgrade) {
return <div className='select-none'>
<PremiumBadge color='blue' allowHover={allowHover} onClick={onClick}>
<SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' />
<div className='system-xs-medium'>
<span className='p-1'>
{t('billing.upgradeBtn.encourageShort')}
</span>
</div>
</PremiumBadge>
</div>
}
if (plan === Plan.sandbox) {
return <div className='select-none'>
<PremiumBadge size={size} color='gray' allowHover={allowHover} onClick={onClick}>
<div className={classNames(size === 's' ? 'system-2xs-medium-uppercase' : 'system-xs-medium-uppercase')}>
<span className='p-1'>
{plan}
</span>
</div>
</PremiumBadge>
</div>
}
if (plan === Plan.professional) {
return <div className='select-none'>
<PremiumBadge size={size} color='blue' allowHover={allowHover} onClick={onClick}>
<div className={classNames(size === 's' ? 'system-2xs-medium-uppercase' : 'system-xs-medium-uppercase')}>
<span className='p-1'>
pro
</span>
</div>
</PremiumBadge>
</div>
}
if (plan === Plan.team) {
return <div className='select-none'>
<PremiumBadge size={size} color='indigo' allowHover={allowHover} onClick={onClick}>
<div className={classNames(size === 's' ? 'system-2xs-medium-uppercase' : 'system-xs-medium-uppercase')}>
<span className='p-1'>
{plan}
</span>
</div>
</PremiumBadge>
</div>
}
return null
}
export default PlanBadge

View File

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

View File

@@ -13,7 +13,7 @@ const ModelList = ({
detail,
}: Props) => {
const { t } = useTranslation()
const { data: res } = useModelProviderModelList(`${detail.plugin_id}/${detail.declaration.model.provider}`)
const { data: res } = useModelProviderModelList(`${detail.plugin_id}/${detail.name}`)
if (!res)
return null

View File

@@ -82,7 +82,6 @@ import {
initialNodes,
} from './utils'
import {
CUSTOM_EDGE,
CUSTOM_NODE,
DSL_EXPORT_CHECK,
ITERATION_CHILDREN_Z_INDEX,
@@ -104,7 +103,7 @@ const nodeTypes = {
[CUSTOM_ITERATION_START_NODE]: CustomIterationStartNode,
}
const edgeTypes = {
[CUSTOM_EDGE]: CustomEdge,
[CUSTOM_NODE]: CustomEdge,
}
type WorkflowProps = {

View File

@@ -28,4 +28,4 @@ export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST}
export NEXT_PUBLIC_TOP_K_MAX_VALUE=${TOP_K_MAX_VALUE}
export NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH}
pm2 start /app/web/server.js --name dify-web --cwd /app/web -i ${PM2_INSTANCES} --no-daemon
pm2 start ./pm2.json --no-daemon

View File

@@ -552,16 +552,16 @@ const translation = {
'variable': '변수',
'operations': {
'*=': '*=',
'overwrite': '덮어쓰기',
'overwrite': '덮어',
'-=': '-=',
'append': '추가',
'over-write': '덮어쓰기',
'append': '덧붙이다',
'over-write': '덮어',
'+=': '+=',
'title': '제목',
'extend': '연장',
'clear': '초기화',
'title': '수술',
'extend': '뻗치다',
'clear': '맑다',
'/=': '/=',
'set': '설정',
'set': '집합',
},
'variables': '변수',
'noAssignedVars': '사용 가능한 할당된 변수가 없습니다.',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

2
web/pnpm-lock.yaml generated
View File

@@ -18309,4 +18309,4 @@ snapshots:
immer: 9.0.21
react: 18.2.0
zwitch@2.0.4: {}
zwitch@2.0.4: {}

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect } from 'react'
import { useCallback } from 'react'
import type {
ModelProvider,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
@@ -39,7 +39,6 @@ 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'
@@ -327,7 +326,7 @@ export const useMutationPluginsFromMarketplace = () => {
pageSize = 40,
} = pluginsSearchParams
const pluginOrBundle = type === 'bundle' ? 'bundles' : 'plugins'
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/advanced`, {
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>(`/${pluginOrBundle}/search/basic`, {
body: {
page,
page_size: pageSize,
@@ -384,7 +383,6 @@ export const usePluginTaskList = (category?: PluginType) => {
const {
data,
isFetched,
isRefetching,
refetch,
...rest
} = useQuery({
@@ -394,24 +392,16 @@ 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])