| import { usePathname } from 'next/navigation' | import { usePathname } from 'next/navigation' | ||||
| import useSWR from 'swr' | import useSWR from 'swr' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { useBoolean } from 'ahooks' | |||||
| import { | import { | ||||
| RiEqualizer2Fill, | RiEqualizer2Fill, | ||||
| RiEqualizer2Line, | RiEqualizer2Line, | ||||
| } | } | ||||
| const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => { | const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => { | ||||
| const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile) | |||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const docLink = useDocLink() | const docLink = useDocLink() | ||||
| const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0 | const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0 | ||||
| const relatedAppsTotal = relatedApps?.data?.length || 0 | const relatedAppsTotal = relatedApps?.data?.length || 0 | ||||
| useEffect(() => { | |||||
| setShowTips(!isMobile) | |||||
| }, [isMobile, setShowTips]) | |||||
| return <div> | return <div> | ||||
| {/* Related apps for desktop */} | {/* Related apps for desktop */} | ||||
| <div className={classNames( | <div className={classNames( |
| const { data: embeddingsModelList } = useModelList(ModelTypeEnum.textEmbedding) | const { data: embeddingsModelList } = useModelList(ModelTypeEnum.textEmbedding) | ||||
| const { | const { | ||||
| modelList: rerankModelList, | modelList: rerankModelList, | ||||
| defaultModel: rerankDefaultModel, | |||||
| currentModel: isRerankDefaultModelValid, | |||||
| } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) | } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const docLink = useDocLink() | const docLink = useDocLink() |
| const echartsRef = useRef<any>(null) | const echartsRef = useRef<any>(null) | ||||
| const contentRef = useRef<string>('') | const contentRef = useRef<string>('') | ||||
| const processedRef = useRef<boolean>(false) // Track if content was successfully processed | const processedRef = useRef<boolean>(false) // Track if content was successfully processed | ||||
| const instanceIdRef = useRef<string>(`chart-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`) // Unique ID for logging | |||||
| const isInitialRenderRef = useRef<boolean>(true) // Track if this is initial render | const isInitialRenderRef = useRef<boolean>(true) // Track if this is initial render | ||||
| const chartInstanceRef = useRef<any>(null) // Direct reference to ECharts instance | const chartInstanceRef = useRef<any>(null) // Direct reference to ECharts instance | ||||
| const resizeTimerRef = useRef<NodeJS.Timeout | null>(null) // For debounce handling | const resizeTimerRef = useRef<NodeJS.Timeout | null>(null) // For debounce handling |
| import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' | |||||
| import React, { useCallback, useEffect, useRef, useState } from 'react' | |||||
| import mermaid, { type MermaidConfig } from 'mermaid' | import mermaid, { type MermaidConfig } from 'mermaid' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' | import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' | ||||
| const renderTimeoutRef = useRef<NodeJS.Timeout>() | const renderTimeoutRef = useRef<NodeJS.Timeout>() | ||||
| const [errMsg, setErrMsg] = useState('') | const [errMsg, setErrMsg] = useState('') | ||||
| const [imagePreviewUrl, setImagePreviewUrl] = useState('') | const [imagePreviewUrl, setImagePreviewUrl] = useState('') | ||||
| const [isCodeComplete, setIsCodeComplete] = useState(false) | |||||
| const codeCompletionCheckRef = useRef<NodeJS.Timeout>() | |||||
| const prevCodeRef = useRef<string>() | |||||
| // Create cache key from code, style and theme | |||||
| const cacheKey = useMemo(() => { | |||||
| return `${props.PrimitiveCode}-${look}-${currentTheme}` | |||||
| }, [props.PrimitiveCode, look, currentTheme]) | |||||
| /** | /** | ||||
| * Renders Mermaid chart | * Renders Mermaid chart | ||||
| {isLoading && !svgString && ( | {isLoading && !svgString && ( | ||||
| <div className='px-[26px] py-4'> | <div className='px-[26px] py-4'> | ||||
| <LoadingAnim type='text'/> | <LoadingAnim type='text'/> | ||||
| {!isCodeComplete && ( | |||||
| <div className="mt-2 text-sm text-gray-500"> | <div className="mt-2 text-sm text-gray-500"> | ||||
| {t('common.wait_for_completion', 'Waiting for diagram code to complete...')} | {t('common.wait_for_completion', 'Waiting for diagram code to complete...')} | ||||
| </div> | </div> | ||||
| )} | |||||
| </div> | </div> | ||||
| )} | )} | ||||
| import React, { useState } from 'react' | |||||
| import React from 'react' | |||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { RiBookOpenLine } from '@remixicon/react' | import { RiBookOpenLine } from '@remixicon/react' | ||||
| }) => { | }) => { | ||||
| const { t, i18n } = useTranslation() | const { t, i18n } = useTranslation() | ||||
| const docLink = useDocLink() | const docLink = useDocLink() | ||||
| const [changeKey, setChangeKey] = useState('') | |||||
| const handleFormChange = (key: string, val: string) => { | const handleFormChange = (key: string, val: string) => { | ||||
| setChangeKey(key) | |||||
| if (key === 'name') { | if (key === 'name') { | ||||
| onChange({ ...value, [key]: val }) | onChange({ ...value, [key]: val }) | ||||
| } | } |
| const [loading, setLoading] = useState(false) | const [loading, setLoading] = useState(false) | ||||
| const [name, setName] = useState<string>(dataset.name) | const [name, setName] = useState<string>(dataset.name) | ||||
| const [description, setDescription] = useState<string>(dataset.description) | const [description, setDescription] = useState<string>(dataset.description) | ||||
| const [externalKnowledgeId, setExternalKnowledgeId] = useState<string>(dataset.external_knowledge_info.external_knowledge_id) | |||||
| const [externalKnowledgeApiId, setExternalKnowledgeApiId] = useState<string>(dataset.external_knowledge_info.external_knowledge_api_id) | |||||
| const externalKnowledgeId = dataset.external_knowledge_info.external_knowledge_id | |||||
| const externalKnowledgeApiId = dataset.external_knowledge_info.external_knowledge_api_id | |||||
| const onConfirm: MouseEventHandler = async () => { | const onConfirm: MouseEventHandler = async () => { | ||||
| if (!name.trim()) { | if (!name.trim()) { |
| handleSearch() | handleSearch() | ||||
| } | } | ||||
| const [currentType, setCurrentType] = useState<string>('') | |||||
| const [currCategory, setCurrCategory] = useTabSearchParams({ | const [currCategory, setCurrCategory] = useTabSearchParams({ | ||||
| defaultTab: allCategoriesEn, | defaultTab: allCategoriesEn, | ||||
| disableSearchParams: false, | disableSearchParams: false, | ||||
| }, | }, | ||||
| ) | ) | ||||
| const filteredList = useMemo(() => { | |||||
| if (currCategory === allCategoriesEn) { | |||||
| if (!currentType) | |||||
| return allList | |||||
| else if (currentType === 'chatbot') | |||||
| return allList.filter(item => (item.app.mode === 'chat' || item.app.mode === 'advanced-chat')) | |||||
| else if (currentType === 'agent') | |||||
| return allList.filter(item => (item.app.mode === 'agent-chat')) | |||||
| else | |||||
| return allList.filter(item => (item.app.mode === 'workflow')) | |||||
| } | |||||
| else { | |||||
| if (!currentType) | |||||
| return allList.filter(item => item.category === currCategory) | |||||
| else if (currentType === 'chatbot') | |||||
| return allList.filter(item => (item.app.mode === 'chat' || item.app.mode === 'advanced-chat') && item.category === currCategory) | |||||
| else if (currentType === 'agent') | |||||
| return allList.filter(item => (item.app.mode === 'agent-chat') && item.category === currCategory) | |||||
| else | |||||
| return allList.filter(item => (item.app.mode === 'workflow') && item.category === currCategory) | |||||
| } | |||||
| }, [currentType, currCategory, allCategoriesEn, allList]) | |||||
| const filteredList = allList.filter(item => currCategory === allCategoriesEn || item.category === currCategory) | |||||
| const searchFilteredList = useMemo(() => { | const searchFilteredList = useMemo(() => { | ||||
| if (!searchKeywords || !filteredList || filteredList.length === 0) | if (!searchKeywords || !filteredList || filteredList.length === 0) |
| const segments = useSelectedLayoutSegments() | const segments = useSelectedLayoutSegments() | ||||
| const lastSegment = segments.slice(-1)[0] | const lastSegment = segments.slice(-1)[0] | ||||
| const isDiscoverySelected = lastSegment === 'apps' | const isDiscoverySelected = lastSegment === 'apps' | ||||
| const isChatSelected = lastSegment === 'chat' | |||||
| const { installedApps, setInstalledApps, setIsFetchingInstalledApps } = useContext(ExploreContext) | const { installedApps, setInstalledApps, setIsFetchingInstalledApps } = useContext(ExploreContext) | ||||
| const { isFetching: isFetchingInstalledApps, data: ret, refetch: fetchInstalledAppList } = useGetInstalledApps() | const { isFetching: isFetchingInstalledApps, data: ret, refetch: fetchInstalledAppList } = useGetInstalledApps() | ||||
| const { mutateAsync: uninstallApp } = useUninstallApp() | const { mutateAsync: uninstallApp } = useUninstallApp() |
| 'use client' | 'use client' | ||||
| import React, { useCallback, useEffect, useRef, useState } from 'react' | |||||
| import React, { useCallback, useEffect, useState } from 'react' | |||||
| import { t } from 'i18next' | import { t } from 'i18next' | ||||
| import copy from 'copy-to-clipboard' | import copy from 'copy-to-clipboard' | ||||
| import s from './index.module.css' | import s from './index.module.css' | ||||
| import type { SuccessInvitationResult } from '.' | import type { SuccessInvitationResult } from '.' | ||||
| import Tooltip from '@/app/components/base/tooltip' | import Tooltip from '@/app/components/base/tooltip' | ||||
| import { randomString } from '@/utils' | |||||
| type IInvitationLinkProps = { | type IInvitationLinkProps = { | ||||
| value: SuccessInvitationResult | value: SuccessInvitationResult | ||||
| value, | value, | ||||
| }: IInvitationLinkProps) => { | }: IInvitationLinkProps) => { | ||||
| const [isCopied, setIsCopied] = useState(false) | const [isCopied, setIsCopied] = useState(false) | ||||
| const selector = useRef(`invite-link-${randomString(4)}`) | |||||
| const copyHandle = useCallback(() => { | const copyHandle = useCallback(() => { | ||||
| // No prefix is needed here because the backend has already processed it | // No prefix is needed here because the backend has already processed it |
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import { useEffect, useRef, useState } from 'react' | import { useEffect, useRef, useState } from 'react' | ||||
| import type { ModelParameterRule } from '../declarations' | import type { ModelParameterRule } from '../declarations' | ||||
| import { useLanguage } from '../hooks' | |||||
| import { isNullOrUndefined } from '../utils' | import { isNullOrUndefined } from '../utils' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import Switch from '@/app/components/base/switch' | import Switch from '@/app/components/base/switch' | ||||
| import Radio from '@/app/components/base/radio' | import Radio from '@/app/components/base/radio' | ||||
| import { SimpleSelect } from '@/app/components/base/select' | import { SimpleSelect } from '@/app/components/base/select' | ||||
| import TagInput from '@/app/components/base/tag-input' | import TagInput from '@/app/components/base/tag-input' | ||||
| import { useTranslation } from 'react-i18next' | |||||
| export type ParameterValue = number | string | string[] | boolean | undefined | export type ParameterValue = number | string | string[] | boolean | undefined | ||||
| onSwitch, | onSwitch, | ||||
| isInWorkflow, | isInWorkflow, | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslation() | |||||
| const language = useLanguage() | |||||
| const [localValue, setLocalValue] = useState(value) | const [localValue, setLocalValue] = useState(value) | ||||
| const numberInputRef = useRef<HTMLInputElement>(null) | const numberInputRef = useRef<HTMLInputElement>(null) | ||||
| const { t } = useMixedTranslation(locale) | const { t } = useMixedTranslation(locale) | ||||
| const [open, setOpen] = useState(false) | const [open, setOpen] = useState(false) | ||||
| const [searchText, setSearchText] = useState('') | const [searchText, setSearchText] = useState('') | ||||
| const { tags: options, tagsMap } = useTags(t) | |||||
| const { tags: options } = useTags(t) | |||||
| const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchText.toLowerCase())) | const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchText.toLowerCase())) | ||||
| const handleCheck = (id: string) => { | const handleCheck = (id: string) => { | ||||
| if (tags.includes(id)) | if (tags.includes(id)) | ||||
| else | else | ||||
| onTagsChange([...tags, id]) | onTagsChange([...tags, id]) | ||||
| } | } | ||||
| const selectedTagsLength = tags.length | |||||
| return ( | return ( | ||||
| <PortalToFollowElem | <PortalToFollowElem |
| } | } | ||||
| export const getMarketplacePluginsByCollectionId = async (collectionId: string, query?: CollectionsAndPluginsSearchParams) => { | export const getMarketplacePluginsByCollectionId = async (collectionId: string, query?: CollectionsAndPluginsSearchParams) => { | ||||
| let plugins = [] as Plugin[] | |||||
| let plugins: Plugin[] | |||||
| try { | try { | ||||
| const url = `${MARKETPLACE_API_PREFIX}/collections/${collectionId}/plugins` | const url = `${MARKETPLACE_API_PREFIX}/collections/${collectionId}/plugins` |
| const pendingTaskList = allTaskList.filter(task => task.status === TaskStatus.pending) | const pendingTaskList = allTaskList.filter(task => task.status === TaskStatus.pending) | ||||
| const noPendingTask = pendingTaskList.length === 0 | const noPendingTask = pendingTaskList.length === 0 | ||||
| const showTaskList = allTaskList.filter(task => task.status !== TaskStatus.pending) | const showTaskList = allTaskList.filter(task => task.status !== TaskStatus.pending) | ||||
| const [currGroupNum, doSetCurrGroupNum] = useState(0) | |||||
| const currGroupNumRef = useRef(0) | const currGroupNumRef = useRef(0) | ||||
| const setCurrGroupNum = (num: number) => { | const setCurrGroupNum = (num: number) => { | ||||
| doSetCurrGroupNum(num) | |||||
| currGroupNumRef.current = num | currGroupNumRef.current = num | ||||
| } | } | ||||
| const getCurrGroupNum = () => { | const getCurrGroupNum = () => { | ||||
| const allFailedTaskList = allTaskList.filter(task => task.status === TaskStatus.failed) | const allFailedTaskList = allTaskList.filter(task => task.status === TaskStatus.failed) | ||||
| const allTasksFinished = allTaskList.every(task => task.status === TaskStatus.completed) | const allTasksFinished = allTaskList.every(task => task.status === TaskStatus.completed) | ||||
| const allTasksRun = allTaskList.every(task => [TaskStatus.completed, TaskStatus.failed].includes(task.status)) | const allTasksRun = allTaskList.every(task => [TaskStatus.completed, TaskStatus.failed].includes(task.status)) | ||||
| const [batchCompletionRes, doSetBatchCompletionRes] = useState<Record<string, string>>({}) | |||||
| const batchCompletionResRef = useRef<Record<string, string>>({}) | const batchCompletionResRef = useRef<Record<string, string>>({}) | ||||
| const setBatchCompletionRes = (res: Record<string, string>) => { | const setBatchCompletionRes = (res: Record<string, string>) => { | ||||
| doSetBatchCompletionRes(res) | |||||
| batchCompletionResRef.current = res | batchCompletionResRef.current = res | ||||
| } | } | ||||
| const getBatchCompletionRes = () => batchCompletionResRef.current | const getBatchCompletionRes = () => batchCompletionResRef.current |
| return | return | ||||
| const { height: wrapHeight, top: wrapTop } = wrapDom.getBoundingClientRect() | const { height: wrapHeight, top: wrapTop } = wrapDom.getBoundingClientRect() | ||||
| const { top: nextToStickyTop } = stickyDOM.getBoundingClientRect() | const { top: nextToStickyTop } = stickyDOM.getBoundingClientRect() | ||||
| let scrollPositionNew = ScrollPosition.belowTheWrap | |||||
| let scrollPositionNew: ScrollPosition | |||||
| if (nextToStickyTop - wrapTop >= wrapHeight) | if (nextToStickyTop - wrapTop >= wrapHeight) | ||||
| scrollPositionNew = ScrollPosition.belowTheWrap | scrollPositionNew = ScrollPosition.belowTheWrap |
| workflowTools: workflowTools || [], | workflowTools: workflowTools || [], | ||||
| }) | }) | ||||
| } | } | ||||
| if(type === 'mcp') { | |||||
| if (type === 'mcp') { | |||||
| const mcpTools = await fetchAllMCPTools() | const mcpTools = await fetchAllMCPTools() | ||||
| workflowStore.setState({ | workflowStore.setState({ | ||||
| const mcpTools = useStore(s => s.mcpTools) | const mcpTools = useStore(s => s.mcpTools) | ||||
| const toolIcon = useMemo(() => { | const toolIcon = useMemo(() => { | ||||
| if(!data) | |||||
| if (!data) | |||||
| return '' | return '' | ||||
| if (data.type === BlockEnum.Tool) { | if (data.type === BlockEnum.Tool) { | ||||
| let targetTools = buildInTools | |||||
| let targetTools = workflowTools | |||||
| if (data.provider_type === CollectionType.builtIn) | if (data.provider_type === CollectionType.builtIn) | ||||
| targetTools = buildInTools | targetTools = buildInTools | ||||
| else if (data.provider_type === CollectionType.custom) | else if (data.provider_type === CollectionType.custom) | ||||
| targetTools = customTools | targetTools = customTools | ||||
| else if (data.provider_type === CollectionType.mcp) | else if (data.provider_type === CollectionType.mcp) | ||||
| targetTools = mcpTools | targetTools = mcpTools | ||||
| else | |||||
| targetTools = workflowTools | |||||
| return targetTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.icon | return targetTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.icon | ||||
| } | } | ||||
| }, [data, buildInTools, customTools, mcpTools, workflowTools]) | }, [data, buildInTools, customTools, mcpTools, workflowTools]) |
| import { | import { | ||||
| useNodeDataUpdate, | useNodeDataUpdate, | ||||
| useNodesInteractions, | useNodesInteractions, | ||||
| useNodesSyncDraft, | |||||
| } from '../../../hooks' | } from '../../../hooks' | ||||
| import { type Node, NodeRunningStatus } from '../../../types' | import { type Node, NodeRunningStatus } from '../../../types' | ||||
| import { canRunBySingle } from '../../../utils' | import { canRunBySingle } from '../../../utils' | ||||
| const [open, setOpen] = useState(false) | const [open, setOpen] = useState(false) | ||||
| const { handleNodeDataUpdate } = useNodeDataUpdate() | const { handleNodeDataUpdate } = useNodeDataUpdate() | ||||
| const { handleNodeSelect } = useNodesInteractions() | const { handleNodeSelect } = useNodesInteractions() | ||||
| const { handleSyncWorkflowDraft } = useNodesSyncDraft() | |||||
| const isSingleRunning = data._singleRunningStatus === NodeRunningStatus.Running | const isSingleRunning = data._singleRunningStatus === NodeRunningStatus.Running | ||||
| const handleOpenChange = useCallback((newOpen: boolean) => { | const handleOpenChange = useCallback((newOpen: boolean) => { | ||||
| setOpen(newOpen) | setOpen(newOpen) |
| isShowSingleRun, | isShowSingleRun, | ||||
| hideSingleRun, | hideSingleRun, | ||||
| runningStatus, | runningStatus, | ||||
| handleStop, | |||||
| runInputData, | runInputData, | ||||
| runInputDataRef, | runInputDataRef, | ||||
| runResult, | runResult, |
| const { inputs, setInputs } = useNodeCrud<ListFilterNodeType>(id, payload) | const { inputs, setInputs } = useNodeCrud<ListFilterNodeType>(id, payload) | ||||
| const { getCurrentVariableType } = useWorkflowVariables() | const { getCurrentVariableType } = useWorkflowVariables() | ||||
| const getType = useCallback((variable?: ValueSelector) => { | const getType = useCallback((variable?: ValueSelector) => { | ||||
| const varType = getCurrentVariableType({ | const varType = getCurrentVariableType({ | ||||
| parentNode: isInIteration ? iterationNode : loopNode, | parentNode: isInIteration ? iterationNode : loopNode, | ||||
| isChatMode, | isChatMode, | ||||
| isConstant: false, | isConstant: false, | ||||
| }) | }) | ||||
| let itemVarType = VarType.string | |||||
| let itemVarType = varType | |||||
| switch (varType) { | switch (varType) { | ||||
| case VarType.arrayNumber: | case VarType.arrayNumber: | ||||
| itemVarType = VarType.number | itemVarType = VarType.number | ||||
| case VarType.arrayObject: | case VarType.arrayObject: | ||||
| itemVarType = VarType.object | itemVarType = VarType.object | ||||
| break | break | ||||
| default: | |||||
| itemVarType = varType | |||||
| } | } | ||||
| return { varType, itemVarType } | return { varType, itemVarType } | ||||
| }, [availableNodes, getCurrentVariableType, inputs.variable, isChatMode, isInIteration, iterationNode, loopNode]) | }, [availableNodes, getCurrentVariableType, inputs.variable, isChatMode, isInIteration, iterationNode, loopNode]) |
| 'sonarjs/single-char-in-character-classes': 'off', | 'sonarjs/single-char-in-character-classes': 'off', | ||||
| 'sonarjs/anchor-precedence': 'warn', | 'sonarjs/anchor-precedence': 'warn', | ||||
| 'sonarjs/updated-loop-counter': 'off', | 'sonarjs/updated-loop-counter': 'off', | ||||
| 'sonarjs/no-dead-store': 'warn', | |||||
| 'sonarjs/no-dead-store': 'error', | |||||
| 'sonarjs/no-duplicated-branches': 'warn', | 'sonarjs/no-duplicated-branches': 'warn', | ||||
| 'sonarjs/max-lines': 'warn', // max 1000 lines | 'sonarjs/max-lines': 'warn', // max 1000 lines | ||||
| 'sonarjs/no-variable-usage-before-declaration': 'error', | 'sonarjs/no-variable-usage-before-declaration': 'error', |