diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index 64186a1b1..ed1c995e2 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -8,6 +8,7 @@ import Header from '@/app/components/header' import { EventEmitterContextProvider } from '@/context/event-emitter' import { ProviderContextProvider } from '@/context/provider-context' import { ModalContextProvider } from '@/context/modal-context' +import GotoAnything from '@/app/components/goto-anything' const Layout = ({ children }: { children: ReactNode }) => { return ( @@ -22,6 +23,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
{children} + diff --git a/web/app/components/base/input/index.tsx b/web/app/components/base/input/index.tsx index 30fd90aff..ae171b0a7 100644 --- a/web/app/components/base/input/index.tsx +++ b/web/app/components/base/input/index.tsx @@ -32,7 +32,7 @@ export type InputProps = { unit?: string } & Omit, 'size'> & VariantProps -const Input = ({ +const Input = React.forwardRef(({ size, disabled, destructive, @@ -47,12 +47,13 @@ const Input = ({ onChange = noop, unit, ...props -}: InputProps) => { +}, ref) => { const { t } = useTranslation() return (
{showLeftIcon && } ) -} +}) + +Input.displayName = 'Input' export default Input diff --git a/web/app/components/goto-anything/actions/app.tsx b/web/app/components/goto-anything/actions/app.tsx new file mode 100644 index 000000000..9d3834f1b --- /dev/null +++ b/web/app/components/goto-anything/actions/app.tsx @@ -0,0 +1,53 @@ +import type { ActionItem, AppSearchResult } from './types' +import type { App } from '@/types/app' +import { fetchAppList } from '@/service/apps' +import AppIcon from '../../base/app-icon' +import { AppTypeIcon } from '../../app/type-selector' +import { getRedirectionPath } from '@/utils/app-redirection' + +const parser = (apps: App[]): AppSearchResult[] => { + return apps.map(app => ({ + id: app.id, + title: app.name, + description: app.description, + type: 'app' as const, + path: getRedirectionPath(true, { + id: app.id, + mode: app.mode, + }), + icon: ( +
+ + +
+ ), + data: app, + })) +} + +export const appAction: ActionItem = { + key: '@app', + shortcut: '@app', + title: 'Search Applications', + description: 'Search and navigate to your applications', + // action, + search: async (_, searchTerm = '', locale) => { + const response = (await fetchAppList({ + url: 'apps', + params: { + page: 1, + name: searchTerm, + }, + })) + const apps = response.data || [] + + return parser(apps) + }, +} diff --git a/web/app/components/goto-anything/actions/index.ts b/web/app/components/goto-anything/actions/index.ts new file mode 100644 index 000000000..d8d99a2b5 --- /dev/null +++ b/web/app/components/goto-anything/actions/index.ts @@ -0,0 +1,68 @@ +import { appAction } from './app' +import { knowledgeAction } from './knowledge' +import { pluginAction } from './plugin' +import { workflowNodesAction } from './workflow-nodes' +import type { ActionItem, SearchResult } from './types' + +export const Actions = { + app: appAction, + knowledge: knowledgeAction, + plugin: pluginAction, + node: workflowNodesAction, +} + +export const searchAnything = async ( + locale: string, + query: string, + actionItem?: ActionItem, +): Promise => { + if (actionItem) { + const searchTerm = query.replace(actionItem.key, '').replace(actionItem.shortcut, '').trim() + return await actionItem.search(query, searchTerm, locale) + } + + if (query.startsWith('@')) + return [] + + // Use Promise.allSettled to handle partial failures gracefully + const searchPromises = Object.values(Actions).map(async (action) => { + try { + const results = await action.search(query, query, locale) + return { success: true, data: results, actionType: action.key } + } + catch (error) { + console.warn(`Search failed for ${action.key}:`, error) + return { success: false, data: [], actionType: action.key, error } + } + }) + + const settledResults = await Promise.allSettled(searchPromises) + + const allResults: SearchResult[] = [] + const failedActions: string[] = [] + + settledResults.forEach((result, index) => { + if (result.status === 'fulfilled' && result.value.success) { + allResults.push(...result.value.data) + } + else { + const actionKey = Object.values(Actions)[index]?.key || 'unknown' + failedActions.push(actionKey) + } + }) + + if (failedActions.length > 0) + console.warn(`Some search actions failed: ${failedActions.join(', ')}`) + + return allResults +} + +export const matchAction = (query: string, actions: Record) => { + return Object.values(actions).find((action) => { + const reg = new RegExp(`^(${action.key}|${action.shortcut})(?:\\s|$)`) + return reg.test(query) + }) +} + +export * from './types' +export { appAction, knowledgeAction, pluginAction, workflowNodesAction } diff --git a/web/app/components/goto-anything/actions/knowledge.tsx b/web/app/components/goto-anything/actions/knowledge.tsx new file mode 100644 index 000000000..63619332c --- /dev/null +++ b/web/app/components/goto-anything/actions/knowledge.tsx @@ -0,0 +1,50 @@ +import type { ActionItem, KnowledgeSearchResult } from './types' +import type { DataSet } from '@/models/datasets' +import { fetchDatasets } from '@/service/datasets' +import { Folder } from '../../base/icons/src/vender/solid/files' +import cn from '@/utils/classnames' + +const EXTERNAL_PROVIDER = 'external' as const +const isExternalProvider = (provider: string): boolean => provider === EXTERNAL_PROVIDER + +const parser = (datasets: DataSet[]): KnowledgeSearchResult[] => { + return datasets.map((dataset) => { + const path = isExternalProvider(dataset.provider) ? `/datasets/${dataset.id}/hitTesting` : `/datasets/${dataset.id}/documents` + return { + id: dataset.id, + title: dataset.name, + description: dataset.description, + type: 'knowledge' as const, + path, + icon: ( +
+ +
+ ), + data: dataset, + } + }) +} + +export const knowledgeAction: ActionItem = { + key: '@knowledge', + shortcut: '@kb', + title: 'Search Knowledge Bases', + description: 'Search and navigate to your knowledge bases', + // action, + search: async (_, searchTerm = '', locale) => { + const response = await fetchDatasets({ + url: '/datasets', + params: { + page: 1, + limit: 10, + keyword: searchTerm, + }, + }) + + return parser(response.data) + }, +} diff --git a/web/app/components/goto-anything/actions/plugin.tsx b/web/app/components/goto-anything/actions/plugin.tsx new file mode 100644 index 000000000..f091f2593 --- /dev/null +++ b/web/app/components/goto-anything/actions/plugin.tsx @@ -0,0 +1,41 @@ +import type { ActionItem, PluginSearchResult } from './types' +import { renderI18nObject } from '@/i18n-config' +import Icon from '../../plugins/card/base/card-icon' +import { postMarketplace } from '@/service/base' +import type { Plugin, PluginsFromMarketplaceResponse } from '../../plugins/types' +import { getPluginIconInMarketplace } from '../../plugins/marketplace/utils' + +const parser = (plugins: Plugin[], locale: string): PluginSearchResult[] => { + return plugins.map((plugin) => { + return { + id: plugin.name, + title: renderI18nObject(plugin.label, locale) || plugin.name, + description: renderI18nObject(plugin.brief, locale) || '', + type: 'plugin' as const, + icon: , + data: plugin, + } + }) +} + +export const pluginAction: ActionItem = { + key: '@plugin', + shortcut: '@plugin', + title: 'Search Plugins', + description: 'Search and navigate to your plugins', + search: async (_, searchTerm = '', locale) => { + const response = await postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/search/advanced', { + body: { + page: 1, + page_size: 10, + query: searchTerm, + type: 'plugin', + }, + }) + const list = (response.data.plugins || []).map(plugin => ({ + ...plugin, + icon: getPluginIconInMarketplace(plugin), + })) + return parser(list, locale!) + }, +} diff --git a/web/app/components/goto-anything/actions/types.ts b/web/app/components/goto-anything/actions/types.ts new file mode 100644 index 000000000..7a838737c --- /dev/null +++ b/web/app/components/goto-anything/actions/types.ts @@ -0,0 +1,54 @@ +import type { ReactNode } from 'react' +import type { TypeWithI18N } from '../../base/form/types' +import type { App } from '@/types/app' +import type { Plugin } from '../../plugins/types' +import type { DataSet } from '@/models/datasets' +import type { CommonNodeType } from '../../workflow/types' + +export type SearchResultType = 'app' | 'knowledge' | 'plugin' | 'workflow-node' + +export type BaseSearchResult = { + id: string + title: string + description?: string + type: SearchResultType + path?: string + icon?: ReactNode + data: T +} + +export type AppSearchResult = { + type: 'app' +} & BaseSearchResult + +export type PluginSearchResult = { + type: 'plugin' +} & BaseSearchResult + +export type KnowledgeSearchResult = { + type: 'knowledge' +} & BaseSearchResult + +export type WorkflowNodeSearchResult = { + type: 'workflow-node' + metadata?: { + nodeId: string + nodeData: CommonNodeType + } +} & BaseSearchResult + +export type SearchResult = AppSearchResult | PluginSearchResult | KnowledgeSearchResult | WorkflowNodeSearchResult + +export type ActionItem = { + key: '@app' | '@knowledge' | '@plugin' | '@node' + shortcut: string + title: string | TypeWithI18N + description: string + action?: (data: SearchResult) => void + searchFn?: (searchTerm: string) => SearchResult[] + search: ( + query: string, + searchTerm: string, + locale?: string, + ) => (Promise | SearchResult[]) +} diff --git a/web/app/components/goto-anything/actions/workflow-nodes.tsx b/web/app/components/goto-anything/actions/workflow-nodes.tsx new file mode 100644 index 000000000..a93f3405c --- /dev/null +++ b/web/app/components/goto-anything/actions/workflow-nodes.tsx @@ -0,0 +1,44 @@ +import type { ActionItem } from './types' +import { BoltIcon } from '@heroicons/react/24/outline' +import i18n from 'i18next' + +// Create the workflow nodes action +export const workflowNodesAction: ActionItem = { + key: '@node', + shortcut: '@node', + title: 'Search Workflow Nodes', + description: 'Find and jump to nodes in the current workflow by name or type', + searchFn: undefined, // Will be set by useWorkflowSearch hook + search: async (_, searchTerm = '', locale) => { + try { + // Use the searchFn if available (set by useWorkflowSearch hook) + if (workflowNodesAction.searchFn) { + // searchFn already returns SearchResult[] type, no need to use parser + return workflowNodesAction.searchFn(searchTerm) + } + + // If not in workflow context or search function not registered + if (!searchTerm.trim()) { + return [{ + id: 'help', + title: i18n.t('app.gotoAnything.actions.searchWorkflowNodes', { lng: locale }), + description: i18n.t('app.gotoAnything.actions.searchWorkflowNodesHelp', { lng: locale }), + type: 'workflow-node', + path: '#', + data: {} as any, + icon: ( +
+ +
+ ), + }] + } + + return [] + } + catch (error) { + console.error('Error searching workflow nodes:', error) + return [] + } + }, +} diff --git a/web/app/components/goto-anything/context.tsx b/web/app/components/goto-anything/context.tsx new file mode 100644 index 000000000..bfe41bcfb --- /dev/null +++ b/web/app/components/goto-anything/context.tsx @@ -0,0 +1,50 @@ +'use client' + +import type { ReactNode } from 'react' +import React, { createContext, useContext, useEffect, useState } from 'react' +import { usePathname } from 'next/navigation' + +/** + * Interface for the GotoAnything context + */ +type GotoAnythingContextType = { + /** + * Whether the current page is a workflow page + */ + isWorkflowPage: boolean +} + +// Create context with default values +const GotoAnythingContext = createContext({ + isWorkflowPage: false, +}) + +/** + * Hook to use the GotoAnything context + */ +export const useGotoAnythingContext = () => useContext(GotoAnythingContext) + +type GotoAnythingProviderProps = { + children: ReactNode +} + +/** + * Provider component for GotoAnything context + */ +export const GotoAnythingProvider: React.FC = ({ children }) => { + const [isWorkflowPage, setIsWorkflowPage] = useState(false) + const pathname = usePathname() + + // Update context based on current pathname + useEffect(() => { + // Check if current path contains workflow + const isWorkflow = pathname?.includes('/workflow') || false + setIsWorkflowPage(isWorkflow) + }, [pathname]) + + return ( + + {children} + + ) +} diff --git a/web/app/components/goto-anything/index.tsx b/web/app/components/goto-anything/index.tsx new file mode 100644 index 000000000..d9b3958eb --- /dev/null +++ b/web/app/components/goto-anything/index.tsx @@ -0,0 +1,395 @@ +'use client' + +import type { FC } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useRouter } from 'next/navigation' +import Modal from '@/app/components/base/modal' +import Input from '@/app/components/base/input' +import { useDebounce, useKeyPress } from 'ahooks' +import { getKeyboardKeyCodeBySystem, isEventTargetInputArea, isMac } from '@/app/components/workflow/utils/common' +import { selectWorkflowNode } from '@/app/components/workflow/utils/node-navigation' +import { RiSearchLine } from '@remixicon/react' +import { Actions as AllActions, type SearchResult, matchAction, searchAnything } from './actions' +import { GotoAnythingProvider, useGotoAnythingContext } from './context' +import { useQuery } from '@tanstack/react-query' +import { useGetLanguage } from '@/context/i18n' +import { useTranslation } from 'react-i18next' +import InstallFromMarketplace from '../plugins/install-plugin/install-from-marketplace' +import type { Plugin } from '../plugins/types' +import { Command } from 'cmdk' + +type Props = { + onHide?: () => void +} +const GotoAnything: FC = ({ + onHide, +}) => { + const router = useRouter() + const defaultLocale = useGetLanguage() + const { isWorkflowPage } = useGotoAnythingContext() + const { t } = useTranslation() + const [show, setShow] = useState(false) + const [searchQuery, setSearchQuery] = useState('') + const [cmdVal, setCmdVal] = useState('') + const inputRef = useRef(null) + + // Filter actions based on context + const Actions = useMemo(() => { + // Create a filtered copy of actions based on current page context + if (isWorkflowPage) { + // Include all actions on workflow pages + return AllActions + } + else { + // Exclude node action on non-workflow pages + const { app, knowledge, plugin } = AllActions + return { app, knowledge, plugin } + } + }, [isWorkflowPage]) + + const [activePlugin, setActivePlugin] = useState() + + // Handle keyboard shortcuts + const handleToggleModal = useCallback((e: KeyboardEvent) => { + // Allow closing when modal is open, even if focus is in the search input + if (!show && isEventTargetInputArea(e.target as HTMLElement)) + return + e.preventDefault() + setShow((prev) => { + if (!prev) { + // Opening modal - reset search state + setSearchQuery('') + } + return !prev + }) + }, [show]) + + useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.k`, handleToggleModal, { + exactMatch: true, + useCapture: true, + }) + + useKeyPress(['esc'], (e) => { + if (show) { + e.preventDefault() + setShow(false) + setSearchQuery('') + } + }) + + const searchQueryDebouncedValue = useDebounce(searchQuery.trim(), { + wait: 300, + }) + + const searchMode = useMemo(() => { + const query = searchQueryDebouncedValue.toLowerCase() + const action = matchAction(query, Actions) + return action ? action.key : 'general' + }, [searchQueryDebouncedValue, Actions]) + + const { data: searchResults = [], isLoading, isError, error } = useQuery( + { + queryKey: [ + 'goto-anything', + 'search-result', + searchQueryDebouncedValue, + searchMode, + isWorkflowPage, + defaultLocale, + Object.keys(Actions).sort().join(','), + ], + queryFn: async () => { + const query = searchQueryDebouncedValue.toLowerCase() + const action = matchAction(query, Actions) + return await searchAnything(defaultLocale, query, action) + }, + enabled: !!searchQueryDebouncedValue, + staleTime: 30000, + gcTime: 300000, + }, + ) + + // Handle navigation to selected result + const handleNavigate = useCallback((result: SearchResult) => { + setShow(false) + setSearchQuery('') + + switch (result.type) { + case 'plugin': + setActivePlugin(result.data) + break + case 'workflow-node': + // Handle workflow node selection and navigation + if (result.metadata?.nodeId) + selectWorkflowNode(result.metadata.nodeId, true) + + break + default: + if (result.path) + router.push(result.path) + } + }, [router]) + + // Group results by type + const groupedResults = useMemo(() => searchResults.reduce((acc, result) => { + if (!acc[result.type]) + acc[result.type] = [] + + acc[result.type].push(result) + return acc + }, {} as { [key: string]: SearchResult[] }), + [searchResults]) + + const emptyResult = useMemo(() => { + if (searchResults.length || !searchQueryDebouncedValue.trim() || isLoading) + return null + + const isCommandSearch = searchMode !== 'general' + const commandType = isCommandSearch ? searchMode.replace('@', '') : '' + + if (isError) { + return ( +
+
+
{t('app.gotoAnything.searchTemporarilyUnavailable')}
+
+ {t('app.gotoAnything.servicesUnavailableMessage')} +
+
+
+ ) + } + + return ( +
+
+
+ {isCommandSearch + ? (() => { + const keyMap: Record = { + app: 'app.gotoAnything.emptyState.noAppsFound', + plugin: 'app.gotoAnything.emptyState.noPluginsFound', + knowledge: 'app.gotoAnything.emptyState.noKnowledgeBasesFound', + node: 'app.gotoAnything.emptyState.noWorkflowNodesFound', + } + return t(keyMap[commandType] || 'app.gotoAnything.noResults') + })() + : t('app.gotoAnything.noResults') + } +
+
+ {isCommandSearch + ? t('app.gotoAnything.emptyState.tryDifferentTerm', { mode: searchMode }) + : t('app.gotoAnything.emptyState.trySpecificSearch', { shortcuts: Object.values(Actions).map(action => action.shortcut).join(', ') }) + } +
+
+
+ ) + }, [searchResults, searchQueryDebouncedValue, Actions, searchMode, isLoading, isError]) + + const defaultUI = useMemo(() => { + if (searchQueryDebouncedValue.trim()) + return null + + return (
+
+
{t('app.gotoAnything.searchTitle')}
+
+ {Object.values(Actions).map(action => ( +
+ {action.shortcut} + {(() => { + const keyMap: Record = { + '@app': 'app.gotoAnything.actions.searchApplicationsDesc', + '@plugin': 'app.gotoAnything.actions.searchPluginsDesc', + '@knowledge': 'app.gotoAnything.actions.searchKnowledgeBasesDesc', + '@node': 'app.gotoAnything.actions.searchWorkflowNodesDesc', + } + return t(keyMap[action.key]) + })()} +
+ ))} +
+
+
) + }, [searchQueryDebouncedValue, Actions]) + + useEffect(() => { + if (show) { + requestAnimationFrame(() => { + inputRef.current?.focus() + }) + } + return () => { + setCmdVal('') + } + }, [show]) + + return ( + <> + { + setShow(false) + setSearchQuery('') + onHide?.() + }} + closable={false} + className='!w-[480px] !p-0' + > +
+ +
+ +
+ { + setCmdVal('') + setSearchQuery(e.target.value) + }} + className='flex-1 !border-0 !bg-transparent !shadow-none' + wrapperClassName='flex-1 !border-0 !bg-transparent' + autoFocus + /> + {searchMode !== 'general' && ( +
+ {searchMode.replace('@', '').toUpperCase()} +
+ )} +
+
+ + {isMac() ? '⌘' : 'Ctrl'} + + + K + +
+
+ + + {isLoading && ( +
+
+
+ {t('app.gotoAnything.searching')} +
+
+ )} + {isError && ( +
+
+
{t('app.gotoAnything.searchFailed')}
+
+ {error.message} +
+
+
+ )} + {!isLoading && !isError && ( + <> + {Object.entries(groupedResults).map(([type, results], groupIndex) => ( + { + const typeMap: Record = { + 'app': 'app.gotoAnything.groups.apps', + 'plugin': 'app.gotoAnything.groups.plugins', + 'knowledge': 'app.gotoAnything.groups.knowledgeBases', + 'workflow-node': 'app.gotoAnything.groups.workflowNodes', + } + return t(typeMap[type] || `${type}s`) + })()} className='p-2 capitalize text-text-secondary'> + {results.map(result => ( + handleNavigate(result)} + > + {result.icon} +
+
+ {result.title} +
+ {result.description && ( +
+ {result.description} +
+ )} +
+
+ {result.type} +
+
+ ))} +
+ ))} + {emptyResult} + {defaultUI} + + )} +
+ + {(!!searchResults.length || isError) && ( +
+
+ + {isError ? ( + {t('app.gotoAnything.someServicesUnavailable')} + ) : ( + <> + {t('app.gotoAnything.resultCount', { count: searchResults.length })} + {searchMode !== 'general' && ( + +{t('app.gotoAnything.inScope', { scope: searchMode.replace('@', '') })} + + )} + + )} + + + {searchMode !== 'general' + ? t('app.gotoAnything.clearToSearchAll') + : t('app.gotoAnything.useAtForSpecific') + } + +
+
+ )} +
+
+ +
+ { + activePlugin && ( + setActivePlugin(undefined)} + onSuccess={() => setActivePlugin(undefined)} + /> + ) + } + + ) +} + +/** + * GotoAnything component with context provider + */ +const GotoAnythingWithContext: FC = (props) => { + return ( + + + + ) +} + +export default GotoAnythingWithContext diff --git a/web/app/components/workflow/hooks/index.ts b/web/app/components/workflow/hooks/index.ts index d58bfa51c..784a1c313 100644 --- a/web/app/components/workflow/hooks/index.ts +++ b/web/app/components/workflow/hooks/index.ts @@ -18,3 +18,4 @@ export * from './use-workflow-mode' export * from './use-workflow-refresh-draft' export * from './use-inspect-vars-crud' export * from './use-set-workflow-vars-with-value' +export * from './use-workflow-search' diff --git a/web/app/components/workflow/hooks/use-workflow-search.tsx b/web/app/components/workflow/hooks/use-workflow-search.tsx new file mode 100644 index 000000000..fa5994cba --- /dev/null +++ b/web/app/components/workflow/hooks/use-workflow-search.tsx @@ -0,0 +1,123 @@ +'use client' + +import { useCallback, useEffect, useMemo } from 'react' +import { useNodes } from 'reactflow' +import { useNodesInteractions } from './use-nodes-interactions' +import type { CommonNodeType } from '../types' +import { workflowNodesAction } from '@/app/components/goto-anything/actions/workflow-nodes' +import BlockIcon from '@/app/components/workflow/block-icon' +import { setupNodeSelectionListener } from '../utils/node-navigation' + +/** + * Hook to register workflow nodes search functionality + */ +export const useWorkflowSearch = () => { + const nodes = useNodes() + const { handleNodeSelect } = useNodesInteractions() + + // Filter and process nodes for search + const searchableNodes = useMemo(() => { + const filteredNodes = nodes.filter((node) => { + if (!node.id || !node.data || node.type === 'sticky') return false + + const nodeData = node.data as CommonNodeType + const nodeType = nodeData?.type + + const internalStartNodes = ['iteration-start', 'loop-start'] + return !internalStartNodes.includes(nodeType) + }) + + const result = filteredNodes + .map((node) => { + const nodeData = node.data as CommonNodeType + + return { + id: node.id, + title: nodeData?.title || nodeData?.type || 'Untitled', + type: nodeData?.type || '', + desc: nodeData?.desc || '', + blockType: nodeData?.type, + nodeData, + } + }) + + return result + }, [nodes]) + + // Create search function for workflow nodes + const searchWorkflowNodes = useCallback((query: string) => { + if (!searchableNodes.length || !query.trim()) return [] + + const searchTerm = query.toLowerCase() + + const results = searchableNodes + .map((node) => { + const titleMatch = node.title.toLowerCase() + const typeMatch = node.type.toLowerCase() + const descMatch = node.desc?.toLowerCase() || '' + + let score = 0 + + if (titleMatch.startsWith(searchTerm)) score += 100 + else if (titleMatch.includes(searchTerm)) score += 50 + else if (typeMatch === searchTerm) score += 80 + else if (typeMatch.includes(searchTerm)) score += 30 + else if (descMatch.includes(searchTerm)) score += 20 + + return score > 0 + ? { + id: node.id, + title: node.title, + description: node.desc || node.type, + type: 'workflow-node' as const, + path: `#${node.id}`, + icon: ( + + ), + metadata: { + nodeId: node.id, + nodeData: node.nodeData, + }, + // Add required data property for SearchResult type + data: node.nodeData, + } + : null + }) + .filter((node): node is NonNullable => node !== null) + .sort((a, b) => { + const aTitle = a.title.toLowerCase() + const bTitle = b.title.toLowerCase() + + if (aTitle.startsWith(searchTerm) && !bTitle.startsWith(searchTerm)) return -1 + if (!aTitle.startsWith(searchTerm) && bTitle.startsWith(searchTerm)) return 1 + + return 0 + }) + + return results + }, [searchableNodes]) + + // Directly set the search function on the action object + useEffect(() => { + if (searchableNodes.length > 0) { + // Set the search function directly on the action + workflowNodesAction.searchFn = searchWorkflowNodes + } + + return () => { + // Clean up when component unmounts + workflowNodesAction.searchFn = undefined + } + }, [searchableNodes, searchWorkflowNodes]) + + // Set up node selection event listener using the utility function + useEffect(() => { + return setupNodeSelectionListener(handleNodeSelect) + }, [handleNodeSelect]) + + return null +} diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 2ebb040f0..d58329b04 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -58,6 +58,7 @@ import { CUSTOM_LOOP_START_NODE } from './nodes/loop-start/constants' import CustomSimpleNode from './simple-node' import { CUSTOM_SIMPLE_NODE } from './simple-node/constants' import Operator from './operator' +import { useWorkflowSearch } from './hooks/use-workflow-search' import Control from './operator/control' import CustomEdge from './custom-edge' import CustomConnectionLine from './custom-connection-line' @@ -68,6 +69,7 @@ import NodeContextmenu from './node-contextmenu' import SelectionContextmenu from './selection-contextmenu' import SyncingDataModal from './syncing-data-modal' import LimitTips from './limit-tips' +import { setupScrollToNodeListener } from './utils/node-navigation' import { useStore, useWorkflowStore, @@ -280,6 +282,14 @@ export const Workflow: FC = memo(({ }) useShortcuts() + // Initialize workflow node search functionality + useWorkflowSearch() + + // Set up scroll to node event listener using the utility function + useEffect(() => { + return setupScrollToNodeListener(nodes, reactflow) + }, [nodes, reactflow]) + const { fetchInspectVars } = useSetWorkflowVarsWithValue() useEffect(() => { fetchInspectVars() diff --git a/web/app/components/workflow/operator/index.tsx b/web/app/components/workflow/operator/index.tsx index 4a472a755..1100a7a90 100644 --- a/web/app/components/workflow/operator/index.tsx +++ b/web/app/components/workflow/operator/index.tsx @@ -52,7 +52,9 @@ const Operator = ({ handleUndo, handleRedo }: OperatorProps) => { } >
- +
+ +
{ const historyWorkflowData = useStore(s => s.historyWorkflowData) const { handleUpdateWorkflowCanvas } = useWorkflowUpdate() - const handleResultCallback = useCallback((res: any) => { - const graph: WorkflowDataUpdater = res.graph + const handleResultCallback = useCallback((res: WorkflowRunDetailResponse) => { + const graph = res.graph handleUpdateWorkflowCanvas({ nodes: graph.nodes, edges: graph.edges, - viewport: graph.viewport, + viewport: graph.viewport || { x: 0, y: 0, zoom: 1 }, }) }, [handleUpdateWorkflowCanvas]) diff --git a/web/app/components/workflow/utils/node-navigation.ts b/web/app/components/workflow/utils/node-navigation.ts new file mode 100644 index 000000000..552276494 --- /dev/null +++ b/web/app/components/workflow/utils/node-navigation.ts @@ -0,0 +1,124 @@ +/** + * Node navigation utilities for workflow + * This module provides functions for node selection, focusing and scrolling in workflow + */ + +/** + * Interface for node selection event detail + */ +export type NodeSelectionDetail = { + nodeId: string; + focus?: boolean; +} + +/** + * Select a node in the workflow + * @param nodeId - The ID of the node to select + * @param focus - Whether to focus/scroll to the node + */ +export function selectWorkflowNode(nodeId: string, focus = false): void { + // Create and dispatch a custom event for node selection + const event = new CustomEvent('workflow:select-node', { + detail: { + nodeId, + focus, + }, + }) + document.dispatchEvent(event) +} + +/** + * Scroll to a specific node in the workflow + * @param nodeId - The ID of the node to scroll to + */ +export function scrollToWorkflowNode(nodeId: string): void { + // Create and dispatch a custom event for scrolling to node + const event = new CustomEvent('workflow:scroll-to-node', { + detail: { nodeId }, + }) + document.dispatchEvent(event) +} + +/** + * Setup node selection event listener + * @param handleNodeSelect - Function to handle node selection + * @returns Cleanup function + */ +export function setupNodeSelectionListener( + handleNodeSelect: (nodeId: string) => void, +): () => void { + // Event handler for node selection + const handleNodeSelection = (event: CustomEvent) => { + const { nodeId, focus } = event.detail + if (nodeId) { + // Select the node + handleNodeSelect(nodeId) + + // If focus is requested, scroll to the node + if (focus) { + // Use a small timeout to ensure node selection happens first + setTimeout(() => { + scrollToWorkflowNode(nodeId) + }, 100) + } + } + } + + // Add event listener + document.addEventListener( + 'workflow:select-node', + handleNodeSelection as EventListener, + ) + + // Return cleanup function + return () => { + document.removeEventListener( + 'workflow:select-node', + handleNodeSelection as EventListener, + ) + } +} + +/** + * Setup scroll to node event listener with ReactFlow + * @param nodes - The workflow nodes + * @param reactflow - The ReactFlow instance + * @returns Cleanup function + */ +export function setupScrollToNodeListener( + nodes: any[], + reactflow: any, +): () => void { + // Event handler for scrolling to node + const handleScrollToNode = (event: CustomEvent) => { + const { nodeId } = event.detail + if (nodeId) { + // Find the target node + const node = nodes.find(n => n.id === nodeId) + if (node) { + // Use ReactFlow's fitView API to scroll to the node + reactflow.fitView({ + nodes: [node], + padding: 0.2, + duration: 800, + minZoom: 0.5, + maxZoom: 1, + }) + } + } + } + + // Add event listener + document.addEventListener( + 'workflow:scroll-to-node', + handleScrollToNode as EventListener, + ) + + // Return cleanup function + return () => { + document.removeEventListener( + 'workflow:scroll-to-node', + handleScrollToNode as EventListener, + ) + } +} diff --git a/web/i18n/de-DE/app.ts b/web/i18n/de-DE/app.ts index c3826e658..0b5cbb07f 100644 --- a/web/i18n/de-DE/app.ts +++ b/web/i18n/de-DE/app.ts @@ -254,6 +254,41 @@ const translation = { maxActiveRequests: 'Maximale gleichzeitige Anfragen', maxActiveRequestsPlaceholder: 'Geben Sie 0 für unbegrenzt ein', maxActiveRequestsTip: 'Maximale Anzahl gleichzeitiger aktiver Anfragen pro App (0 für unbegrenzt)', + gotoAnything: { + actions: { + searchPlugins: 'Such-Plugins', + searchKnowledgeBases: 'Wissensdatenbanken durchsuchen', + searchWorkflowNodes: 'Workflow-Knoten durchsuchen', + searchKnowledgeBasesDesc: 'Suchen und navigieren Sie zu Ihren Wissensdatenbanken', + searchApplications: 'Anwendungen suchen', + searchWorkflowNodesHelp: 'Diese Funktion funktioniert nur, wenn ein Workflow angezeigt wird. Navigieren Sie zuerst zu einem Workflow.', + searchApplicationsDesc: 'Suchen und navigieren Sie zu Ihren Anwendungen', + searchPluginsDesc: 'Suchen und navigieren Sie zu Ihren Plugins', + searchWorkflowNodesDesc: 'Suchen und Springen zu Knoten im aktuellen Workflow nach Name oder Typ', + }, + emptyState: { + noPluginsFound: 'Keine Plugins gefunden', + noWorkflowNodesFound: 'Keine Workflow-Knoten gefunden', + noKnowledgeBasesFound: 'Keine Wissensdatenbanken gefunden', + noAppsFound: 'Keine Apps gefunden', + }, + groups: { + knowledgeBases: 'Wissensdatenbanken', + plugins: 'Plugins', + apps: 'Apps', + workflowNodes: 'Workflow-Knoten', + }, + clearToSearchAll: 'Löschen Sie @, um alle zu durchsuchen', + searchTemporarilyUnavailable: 'Suche vorübergehend nicht verfügbar', + searchFailed: 'Suche fehlgeschlagen', + someServicesUnavailable: 'Einige Suchdienste sind nicht verfügbar', + servicesUnavailableMessage: 'Bei einigen Suchdiensten können Probleme auftreten. Versuchen Sie es gleich noch einmal.', + noResults: 'Keine Ergebnisse gefunden', + searchPlaceholder: 'Suchen Sie nach Befehlen, oder geben Sie @ ein...', + useAtForSpecific: 'Verwenden von @ für bestimmte Typen', + searchTitle: 'Suchen Sie nach irgendetwas', + searching: 'Suche...', + }, } export default translation diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index 28b8b8405..722d591eb 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -252,6 +252,46 @@ const translation = { maxActiveRequests: 'Max concurrent requests', maxActiveRequestsPlaceholder: 'Enter 0 for unlimited', maxActiveRequestsTip: 'Maximum number of concurrent active requests per app (0 for unlimited)', + gotoAnything: { + searchPlaceholder: 'Search or type @ for commands...', + searchTitle: 'Search for anything', + searching: 'Searching...', + noResults: 'No results found', + searchFailed: 'Search failed', + searchTemporarilyUnavailable: 'Search temporarily unavailable', + servicesUnavailableMessage: 'Some search services may be experiencing issues. Try again in a moment.', + someServicesUnavailable: 'Some search services unavailable', + resultCount: '{{count}} result', + resultCount_other: '{{count}} results', + inScope: 'in {{scope}}s', + clearToSearchAll: 'Clear @ to search all', + useAtForSpecific: 'Use @ for specific types', + actions: { + searchApplications: 'Search Applications', + searchApplicationsDesc: 'Search and navigate to your applications', + searchPlugins: 'Search Plugins', + searchPluginsDesc: 'Search and navigate to your plugins', + searchKnowledgeBases: 'Search Knowledge Bases', + searchKnowledgeBasesDesc: 'Search and navigate to your knowledge bases', + searchWorkflowNodes: 'Search Workflow Nodes', + searchWorkflowNodesDesc: 'Find and jump to nodes in the current workflow by name or type', + searchWorkflowNodesHelp: 'This feature only works when viewing a workflow. Navigate to a workflow first.', + }, + emptyState: { + noAppsFound: 'No apps found', + noPluginsFound: 'No plugins found', + noKnowledgeBasesFound: 'No knowledge bases found', + noWorkflowNodesFound: 'No workflow nodes found', + tryDifferentTerm: 'Try a different search term or remove the {{mode}} filter', + trySpecificSearch: 'Try {{shortcuts}} for specific searches', + }, + groups: { + apps: 'Apps', + plugins: 'Plugins', + knowledgeBases: 'Knowledge Bases', + workflowNodes: 'Workflow Nodes', + }, + }, } export default translation diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 467044d89..d1229ec14 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -958,6 +958,11 @@ const translation = { settingsTab: 'Settings', lastRunTab: 'Last Run', relationsTab: 'Relations', + copyLastRun: 'Copy Last Run', + noLastRunFound: 'No previous run found', + noMatchingInputsFound: 'No matching inputs found from last run', + lastRunInputsCopied: '{{count}} input(s) copied from last run', + copyLastRunError: 'Failed to copy last run inputs', noData: { description: 'The results of the last run will be displayed here', runThisNode: 'Run this node', diff --git a/web/i18n/es-ES/app.ts b/web/i18n/es-ES/app.ts index f2df9a45a..1f50ae2a4 100644 --- a/web/i18n/es-ES/app.ts +++ b/web/i18n/es-ES/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequestsPlaceholder: 'Introduce 0 para ilimitado', maxActiveRequests: 'Máximas solicitudes concurrentes', maxActiveRequestsTip: 'Número máximo de solicitudes activas concurrentes por aplicación (0 para ilimitado)', + gotoAnything: { + actions: { + searchApplications: 'Aplicaciones de búsqueda', + searchKnowledgeBasesDesc: 'Busque y navegue por sus bases de conocimiento', + searchWorkflowNodes: 'Buscar nodos de flujo de trabajo', + searchPlugins: 'Complementos de búsqueda', + searchWorkflowNodesDesc: 'Buscar y saltar a nodos en el flujo de trabajo actual por nombre o tipo', + searchKnowledgeBases: 'Buscar en las bases de conocimiento', + searchApplicationsDesc: 'Buscar y navegar a sus aplicaciones', + searchPluginsDesc: 'Busca y navega a tus plugins', + searchWorkflowNodesHelp: 'Esta función solo funciona cuando se visualiza un flujo de trabajo. Primero vaya a un flujo de trabajo.', + }, + emptyState: { + noAppsFound: 'No se encontraron aplicaciones', + noPluginsFound: 'No se encontraron complementos', + noWorkflowNodesFound: 'No se encontraron nodos de flujo de trabajo', + noKnowledgeBasesFound: 'No se han encontrado bases de conocimiento', + }, + groups: { + apps: 'Aplicaciones', + workflowNodes: 'Nodos de flujo de trabajo', + knowledgeBases: 'Bases de conocimiento', + plugins: 'Complementos', + }, + clearToSearchAll: 'Borrar @ para buscar todo', + noResults: 'No se han encontrado resultados', + searching: 'Minucioso...', + searchTemporarilyUnavailable: 'La búsqueda no está disponible temporalmente', + searchFailed: 'Error de búsqueda', + useAtForSpecific: 'Use @ para tipos específicos', + searchPlaceholder: 'Busque o escriba @ para los comandos...', + searchTitle: 'Busca cualquier cosa', + someServicesUnavailable: 'Algunos servicios de búsqueda no están disponibles', + servicesUnavailableMessage: 'Algunos servicios de búsqueda pueden estar experimentando problemas. Inténtalo de nuevo en un momento.', + }, } export default translation diff --git a/web/i18n/fa-IR/app.ts b/web/i18n/fa-IR/app.ts index cd848ec21..e7d06e946 100644 --- a/web/i18n/fa-IR/app.ts +++ b/web/i18n/fa-IR/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequests: 'بیشترین درخواست‌های همزمان', maxActiveRequestsPlaceholder: 'برای نامحدود، 0 را وارد کنید', maxActiveRequestsTip: 'حداکثر تعداد درخواست‌های فعال همزمان در هر برنامه (0 برای نامحدود)', + gotoAnything: { + actions: { + searchPlugins: 'افزونه های جستجو', + searchWorkflowNodes: 'گره های گردش کار جستجو', + searchApplications: 'جستجوی برنامه ها', + searchKnowledgeBases: 'جستجو در پایگاه های دانش', + searchWorkflowNodesHelp: 'این ویژگی فقط هنگام مشاهده گردش کار کار می کند. ابتدا به گردش کار بروید.', + searchApplicationsDesc: 'جستجو و پیمایش به برنامه های خود', + searchKnowledgeBasesDesc: 'پایگاه های دانش خود را جستجو کرده و به آن ناوبری کنید', + searchPluginsDesc: 'افزونه های خود را جستجو کرده و به آنها پیمایش کنید', + searchWorkflowNodesDesc: 'گره ها را در گردش کار فعلی بر اساس نام یا نوع پیدا کنید و به آنها بروید', + }, + emptyState: { + noKnowledgeBasesFound: 'هیچ پایگاه دانش یافت نشد', + noAppsFound: 'هیچ برنامه ای یافت نشد', + noPluginsFound: 'هیچ افزونه ای یافت نشد', + noWorkflowNodesFound: 'هیچ گره گردش کاری یافت نشد', + }, + groups: { + plugins: 'پلاگین', + apps: 'واژهنامه', + knowledgeBases: 'پایگاه های دانش', + workflowNodes: 'گره های گردش کار', + }, + searching: 'جستجو...', + searchFailed: 'جستجو انجام نشد', + useAtForSpecific: 'از @ برای انواع خاص استفاده کنید', + clearToSearchAll: 'پاک کردن @ برای جستجوی همه', + noResults: 'هیچ نتیجه ای یافت نشد', + searchTitle: 'هر چیزی را جستجو کنید', + searchPlaceholder: 'جستجو یا تایپ @ برای دستورات...', + searchTemporarilyUnavailable: 'جستجو به طور موقت در دسترس نیست', + servicesUnavailableMessage: 'برخی از سرویس های جستجو ممکن است با مشکل مواجه شوند. یک لحظه دیگر دوباره امتحان کنید.', + someServicesUnavailable: 'برخی از سرویس های جستجو دردسترس نیستند', + }, } export default translation diff --git a/web/i18n/fr-FR/app.ts b/web/i18n/fr-FR/app.ts index 8c0e0f516..1aa7c2a4d 100644 --- a/web/i18n/fr-FR/app.ts +++ b/web/i18n/fr-FR/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequestsPlaceholder: 'Entrez 0 pour illimité', maxActiveRequests: 'Nombre maximal de requêtes simultanées', maxActiveRequestsTip: 'Nombre maximum de requêtes actives concurrentes par application (0 pour illimité)', + gotoAnything: { + actions: { + searchPluginsDesc: 'Recherchez et naviguez vers vos plugins', + searchKnowledgeBasesDesc: 'Recherchez et accédez à vos bases de connaissances', + searchWorkflowNodesDesc: 'Recherchez et accédez aux nœuds du flux de travail actuel par nom ou type', + searchApplicationsDesc: 'Recherchez et accédez à vos applications', + searchPlugins: 'Plugins de recherche', + searchWorkflowNodes: 'Rechercher des nœuds de workflow', + searchKnowledgeBases: 'Rechercher dans les bases de connaissances', + searchApplications: 'Rechercher des applications', + searchWorkflowNodesHelp: 'Cette fonctionnalité ne fonctionne que lors de l’affichage d’un flux de travail. Accédez d’abord à un flux de travail.', + }, + emptyState: { + noKnowledgeBasesFound: 'Aucune base de connaissances trouvée', + noAppsFound: 'Aucune application trouvée', + noPluginsFound: 'Aucun plugin trouvé', + noWorkflowNodesFound: 'Aucun nœud de workflow trouvé', + }, + groups: { + apps: 'Apps', + workflowNodes: 'Nœuds de flux de travail', + knowledgeBases: 'Bases de connaissances', + plugins: 'Plug-ins', + }, + someServicesUnavailable: 'Certains services de recherche indisponibles', + servicesUnavailableMessage: 'Certains services de recherche peuvent rencontrer des problèmes. Réessayez dans un instant.', + useAtForSpecific: 'Utilisez @ pour des types spécifiques', + searchTemporarilyUnavailable: 'Recherche temporairement indisponible', + searchTitle: 'Recherchez n’importe quoi', + clearToSearchAll: 'Effacer @ pour rechercher tout', + searching: 'Recherche...', + searchPlaceholder: 'Recherchez ou tapez @ pour les commandes...', + searchFailed: 'Echec de la recherche', + noResults: 'Aucun résultat trouvé', + }, } export default translation diff --git a/web/i18n/hi-IN/app.ts b/web/i18n/hi-IN/app.ts index cce3fe1f4..c97d4e255 100644 --- a/web/i18n/hi-IN/app.ts +++ b/web/i18n/hi-IN/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequests: 'अधिकतम समवर्ती अनुरोध', maxActiveRequestsPlaceholder: 'असीमित के लिए 0 दर्ज करें', maxActiveRequestsTip: 'प्रति ऐप सक्रिय अनुरोधों की अधिकतम संख्या (असीमित के लिए 0)', + gotoAnything: { + actions: { + searchPlugins: 'खोज प्लगइन्स', + searchWorkflowNodes: 'खोज कार्यप्रवाह नोड्स', + searchKnowledgeBases: 'ज्ञान आधार खोजें', + searchApplications: 'अनुसंधान एप्लिकेशन', + searchPluginsDesc: 'अपने प्लगइन्स को खोजें और नेविगेट करें', + searchWorkflowNodesDesc: 'वर्तमान कार्यप्रवाह में नाम या प्रकार द्वारा नोड्स को खोजें और उन पर कूदें', + searchKnowledgeBasesDesc: 'अपने ज्ञान आधारों की खोज करें और उन्हें नेविगेट करें', + searchApplicationsDesc: 'अपने अनुप्रयोगों की खोज करें और उन्हें नेविगेट करें', + searchWorkflowNodesHelp: 'यह सुविधा केवल तब काम करती है जब आप एक कार्यप्रवाह देख रहे हों। पहले एक कार्यप्रवाह पर जाएं।', + }, + emptyState: { + noPluginsFound: 'कोई प्लगइन नहीं मिले', + noAppsFound: 'कोई ऐप्स नहीं मिले', + noKnowledgeBasesFound: 'कोई ज्ञान आधार नहीं मिले', + noWorkflowNodesFound: 'कोई कार्यप्रवाह नोड नहीं मिला', + }, + groups: { + apps: 'ऐप्स', + knowledgeBases: 'ज्ञान आधार', + plugins: 'प्लगइन्स', + workflowNodes: 'कार्यप्रवाह नोड्स', + }, + noResults: 'कोई परिणाम नहीं मिले', + clearToSearchAll: 'साफ़ @ सभी खोजने के लिए', + searchTitle: 'किसी भी चीज़ की खोज करें', + useAtForSpecific: '@ का उपयोग विशिष्ट प्रकारों के लिए करें', + someServicesUnavailable: 'कुछ खोज सेवाएँ उपलब्ध नहीं हैं', + searching: 'खोजना...', + searchFailed: 'खोज विफल रहा', + searchPlaceholder: 'कमांड के लिए खोजें या टाइप करें @...', + searchTemporarilyUnavailable: 'खोज अस्थायी रूप से उपलब्ध नहीं है', + servicesUnavailableMessage: 'कुछ खोज सेवाएँ समस्याओं का सामना कर सकती हैं। थोड़ी देर बाद फिर से प्रयास करें।', + }, } export default translation diff --git a/web/i18n/it-IT/app.ts b/web/i18n/it-IT/app.ts index 9c58c5e80..926bd4551 100644 --- a/web/i18n/it-IT/app.ts +++ b/web/i18n/it-IT/app.ts @@ -258,6 +258,41 @@ const translation = { maxActiveRequestsPlaceholder: 'Inserisci 0 per illimitato', maxActiveRequests: 'Massimo numero di richieste concorrenti', maxActiveRequestsTip: 'Numero massimo di richieste attive concorrenti per app (0 per illimitato)', + gotoAnything: { + actions: { + searchWorkflowNodesHelp: 'Questa funzione funziona solo durante la visualizzazione di un flusso di lavoro. Passa prima a un flusso di lavoro.', + searchApplicationsDesc: 'Cerca e naviga alle tue applicazioni', + searchWorkflowNodes: 'Ricerca nei nodi del flusso di lavoro', + searchApplications: 'Cerca applicazioni', + searchPluginsDesc: 'Cerca e naviga verso i tuoi plugin', + searchKnowledgeBasesDesc: 'Cerca e naviga nelle tue knowledge base', + searchPlugins: 'Plugin di ricerca', + searchWorkflowNodesDesc: 'Trovare e passare ai nodi nel flusso di lavoro corrente in base al nome o al tipo', + searchKnowledgeBases: 'Cerca nelle Basi di Conoscenza', + }, + emptyState: { + noKnowledgeBasesFound: 'Nessuna base di conoscenza trovata', + noAppsFound: 'Nessuna app trovata', + noWorkflowNodesFound: 'Nessun nodo del flusso di lavoro trovato', + noPluginsFound: 'Nessun plugin trovato', + }, + groups: { + knowledgeBases: 'Basi di conoscenza', + workflowNodes: 'Nodi del flusso di lavoro', + plugins: 'Plugin', + apps: 'Applicazioni', + }, + searchTitle: 'Cerca qualsiasi cosa', + searchPlaceholder: 'Cerca o digita @ per i comandi...', + searching: 'Indagatore...', + searchTemporarilyUnavailable: 'Ricerca temporaneamente non disponibile', + searchFailed: 'Ricerca non riuscita', + servicesUnavailableMessage: 'Alcuni servizi di ricerca potrebbero riscontrare problemi. Riprova tra un attimo.', + someServicesUnavailable: 'Alcuni servizi di ricerca non sono disponibili', + noResults: 'Nessun risultato trovato', + useAtForSpecific: 'Utilizzare @ per tipi specifici', + clearToSearchAll: 'Cancella @ per cercare tutto', + }, } export default translation diff --git a/web/i18n/ja-JP/app.ts b/web/i18n/ja-JP/app.ts index 3e399debc..ea391c79f 100644 --- a/web/i18n/ja-JP/app.ts +++ b/web/i18n/ja-JP/app.ts @@ -251,6 +251,46 @@ const translation = { maxActiveRequestsPlaceholder: '無制限のために0を入力してください', maxActiveRequests: '最大同時リクエスト数', maxActiveRequestsTip: 'アプリごとの同時アクティブリクエストの最大数(無制限の場合は0)', + gotoAnything: { + searchPlaceholder: '検索するか、@ を入力してコマンドを使用...', + searchTitle: '何でも検索', + searching: '検索中...', + noResults: '結果が見つかりません', + searchFailed: '検索に失敗しました', + searchTemporarilyUnavailable: '検索が一時的に利用できません', + servicesUnavailableMessage: '一部の検索サービスで問題が発生している可能性があります。しばらくしてからもう一度お試しください。', + someServicesUnavailable: '一部の検索サービスが利用できません', + resultCount: '{{count}} 件の結果', + resultCount_other: '{{count}} 件の結果', + inScope: '{{scope}}s 内', + clearToSearchAll: '@ をクリアしてすべてを検索', + useAtForSpecific: '特定のタイプには @ を使用', + actions: { + searchApplications: 'アプリケーションを検索', + searchApplicationsDesc: 'アプリケーションを検索してナビゲート', + searchPlugins: 'プラグインを検索', + searchPluginsDesc: 'プラグインを検索してナビゲート', + searchKnowledgeBases: 'ナレッジベースを検索', + searchKnowledgeBasesDesc: 'ナレッジベースを検索してナビゲート', + searchWorkflowNodes: 'ワークフローノードを検索', + searchWorkflowNodesDesc: '現在のワークフロー内のノードを名前またはタイプで検索してジャンプ', + searchWorkflowNodesHelp: 'この機能はワークフロー表示時のみ利用できます。まずワークフローに移動してください。', + }, + emptyState: { + noAppsFound: 'アプリが見つかりません', + noPluginsFound: 'プラグインが見つかりません', + noKnowledgeBasesFound: 'ナレッジベースが見つかりません', + noWorkflowNodesFound: 'ワークフローノードが見つかりません', + tryDifferentTerm: '別の検索語句を試すか、{{mode}} フィルターを削除してください', + trySpecificSearch: '特定検索には {{shortcuts}} を試してください', + }, + groups: { + apps: 'アプリケーション', + plugins: 'プラグイン', + knowledgeBases: 'ナレッジベース', + workflowNodes: 'ワークフローノード', + }, + }, } export default translation diff --git a/web/i18n/ko-KR/app.ts b/web/i18n/ko-KR/app.ts index 5ddb82399..821072ed7 100644 --- a/web/i18n/ko-KR/app.ts +++ b/web/i18n/ko-KR/app.ts @@ -272,6 +272,41 @@ const translation = { maxActiveRequests: '동시 최대 요청 수', maxActiveRequestsPlaceholder: '무제한 사용을 원하시면 0을 입력하세요.', maxActiveRequestsTip: '앱당 최대 동시 활성 요청 수(무제한은 0)', + gotoAnything: { + actions: { + searchWorkflowNodes: '워크플로 노드 검색', + searchApplicationsDesc: '애플리케이션 검색 및 탐색', + searchPlugins: '플러그인 검색', + searchApplications: '응용 프로그램 검색', + searchPluginsDesc: '플러그인을 검색하고 탐색합니다.', + searchWorkflowNodesDesc: '이름 또는 유형별로 현재 워크플로의 노드를 찾아 이동', + searchKnowledgeBasesDesc: '기술 자료를 검색하고 탐색합니다.', + searchWorkflowNodesHelp: '이 기능은 워크플로를 볼 때만 작동합니다. 먼저 워크플로로 이동합니다.', + searchKnowledgeBases: '기술 자료 검색', + }, + emptyState: { + noAppsFound: '앱을 찾을 수 없습니다.', + noPluginsFound: '플러그인을 찾을 수 없습니다.', + noKnowledgeBasesFound: '기술 자료를 찾을 수 없습니다.', + noWorkflowNodesFound: '워크플로 노드를 찾을 수 없습니다.', + }, + groups: { + apps: '앱', + plugins: '플러그인', + knowledgeBases: '기술 자료', + workflowNodes: '워크플로 노드', + }, + searching: '검색...', + searchTitle: '무엇이든 검색', + useAtForSpecific: '특정 형식에 @ 사용', + searchTemporarilyUnavailable: '일시적으로 검색할 수 없음', + noResults: '결과를 찾을 수 없습니다.', + someServicesUnavailable: '일부 검색 서비스를 사용할 수 없습니다.', + servicesUnavailableMessage: '일부 검색 서비스에서 문제가 발생할 수 있습니다. 잠시 후에 다시 시도하십시오.', + searchFailed: '검색 실패', + searchPlaceholder: '명령을 검색하거나 @를 입력합니다...', + clearToSearchAll: '@를 지우면 모두 검색됩니다.', + }, } export default translation diff --git a/web/i18n/pl-PL/app.ts b/web/i18n/pl-PL/app.ts index 0a5e6a56d..eeadf1985 100644 --- a/web/i18n/pl-PL/app.ts +++ b/web/i18n/pl-PL/app.ts @@ -253,6 +253,41 @@ const translation = { maxActiveRequests: 'Maksymalne równoczesne żądania', maxActiveRequestsPlaceholder: 'Wprowadź 0, aby uzyskać nielimitowane', maxActiveRequestsTip: 'Maksymalna liczba jednoczesnych aktywnych żądań na aplikację (0 dla nieograniczonej)', + gotoAnything: { + actions: { + searchPlugins: 'Szukaj wtyczek', + searchWorkflowNodesHelp: 'Ta funkcja działa tylko podczas wyświetlania przepływu pracy. Najpierw przejdź do przepływu pracy.', + searchApplicationsDesc: 'Wyszukiwanie aplikacji i przechodzenie do nich', + searchPluginsDesc: 'Wyszukiwanie i przechodzenie do wtyczek', + searchApplications: 'Szukaj aplikacji', + searchKnowledgeBasesDesc: 'Wyszukiwanie i przechodzenie do baz wiedzy', + searchWorkflowNodesDesc: 'Znajdowanie węzłów w bieżącym przepływie pracy i przechodzenie do nich według nazwy lub typu', + searchKnowledgeBases: 'Szukaj w bazach wiedzy', + searchWorkflowNodes: 'Wyszukiwanie węzłów przepływu pracy', + }, + emptyState: { + noAppsFound: 'Nie znaleziono aplikacji', + noKnowledgeBasesFound: 'Nie znaleziono baz wiedzy', + noWorkflowNodesFound: 'Nie znaleziono węzłów przepływu pracy', + noPluginsFound: 'Nie znaleziono wtyczek', + }, + groups: { + apps: 'Aplikacje', + workflowNodes: 'Węzły przepływu pracy', + knowledgeBases: 'Bazy wiedzy', + plugins: 'Wtyczki', + }, + useAtForSpecific: 'Użyj @ dla określonych typów', + searchPlaceholder: 'Wyszukaj lub wpisz @ dla poleceń...', + searching: 'Wyszukiwanie...', + noResults: 'Nie znaleziono wyników', + searchTitle: 'Szukaj czegokolwiek', + someServicesUnavailable: 'Niektóre usługi wyszukiwania są niedostępne', + clearToSearchAll: 'Wyczyść @, aby przeszukać wszystko', + searchTemporarilyUnavailable: 'Wyszukiwanie chwilowo niedostępne', + servicesUnavailableMessage: 'W przypadku niektórych usług wyszukiwania mogą występować problemy. Spróbuj ponownie za chwilę.', + searchFailed: 'Wyszukiwanie nie powiodło się', + }, } export default translation diff --git a/web/i18n/pt-BR/app.ts b/web/i18n/pt-BR/app.ts index bbb420351..cfda16e9a 100644 --- a/web/i18n/pt-BR/app.ts +++ b/web/i18n/pt-BR/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequestsPlaceholder: 'Digite 0 para ilimitado', maxActiveRequests: 'Máximo de solicitações simultâneas', maxActiveRequestsTip: 'Número máximo de solicitações ativas simultâneas por aplicativo (0 para ilimitado)', + gotoAnything: { + actions: { + searchPlugins: 'Pesquisar Plugins', + searchApplicationsDesc: 'Pesquise e navegue até seus aplicativos', + searchPluginsDesc: 'Pesquise e navegue até seus plug-ins', + searchKnowledgeBases: 'Pesquisar bases de conhecimento', + searchApplications: 'Aplicativos de pesquisa', + searchWorkflowNodesDesc: 'Localizar e ir para nós no fluxo de trabalho atual por nome ou tipo', + searchWorkflowNodesHelp: 'Esse recurso só funciona ao visualizar um fluxo de trabalho. Navegue até um fluxo de trabalho primeiro.', + searchKnowledgeBasesDesc: 'Pesquise e navegue até suas bases de conhecimento', + searchWorkflowNodes: 'Nós de fluxo de trabalho de pesquisa', + }, + emptyState: { + noAppsFound: 'Nenhum aplicativo encontrado', + noPluginsFound: 'Nenhum plugin encontrado', + noWorkflowNodesFound: 'Nenhum nó de fluxo de trabalho encontrado', + noKnowledgeBasesFound: 'Nenhuma base de conhecimento encontrada', + }, + groups: { + apps: 'Apps', + knowledgeBases: 'Bases de conhecimento', + plugins: 'Plugins', + workflowNodes: 'Nós de fluxo de trabalho', + }, + searching: 'Procurando...', + searchTitle: 'Pesquisar qualquer coisa', + someServicesUnavailable: 'Alguns serviços de pesquisa indisponíveis', + searchTemporarilyUnavailable: 'Pesquisa temporariamente indisponível', + servicesUnavailableMessage: 'Alguns serviços de pesquisa podem estar enfrentando problemas. Tente novamente em um momento.', + searchPlaceholder: 'Pesquise ou digite @ para comandos...', + noResults: 'Nenhum resultado encontrado', + useAtForSpecific: 'Use @ para tipos específicos', + clearToSearchAll: 'Desmarque @ para pesquisar tudo', + searchFailed: 'Falha na pesquisa', + }, } export default translation diff --git a/web/i18n/ro-RO/app.ts b/web/i18n/ro-RO/app.ts index 73b01ac70..09bda641a 100644 --- a/web/i18n/ro-RO/app.ts +++ b/web/i18n/ro-RO/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequestsPlaceholder: 'Introduceți 0 pentru nelimitat', maxActiveRequests: 'Maxime cereri simultane', maxActiveRequestsTip: 'Numărul maxim de cereri active concurente pe aplicație (0 pentru nelimitat)', + gotoAnything: { + actions: { + searchKnowledgeBasesDesc: 'Căutați și navigați la bazele de cunoștințe', + searchWorkflowNodes: 'Căutare în noduri de flux de lucru', + searchKnowledgeBases: 'Căutare în baze de cunoștințe', + searchApplicationsDesc: 'Căutați și navigați la aplicațiile dvs.', + searchApplications: 'Căutare aplicații', + searchPluginsDesc: 'Căutați și navigați la plugin-urile dvs.', + searchWorkflowNodesDesc: 'Găsiți și treceți la nodurile din fluxul de lucru curent după nume sau tip', + searchWorkflowNodesHelp: 'Această caracteristică funcționează numai atunci când vizualizați un flux de lucru. Navigați mai întâi la un flux de lucru.', + searchPlugins: 'Căutare plugin-uri', + }, + emptyState: { + noAppsFound: 'Nu s-au găsit aplicații', + noPluginsFound: 'Nu au fost găsite plugin-uri', + noWorkflowNodesFound: 'Nu au fost găsite noduri de flux de lucru', + noKnowledgeBasesFound: 'Nu au fost găsite baze de cunoștințe', + }, + groups: { + knowledgeBases: 'Baze de cunoștințe', + workflowNodes: 'Noduri de flux de lucru', + plugins: 'Pluginuri', + apps: 'Aplicații', + }, + useAtForSpecific: 'Utilizați @ pentru anumite tipuri', + searchTemporarilyUnavailable: 'Căutare temporar indisponibilă', + searchPlaceholder: 'Căutați sau tastați @ pentru comenzi...', + searchTitle: 'Căutați orice', + searching: 'Căutarea...', + noResults: 'Nu s-au găsit rezultate', + searchFailed: 'Căutarea a eșuat', + servicesUnavailableMessage: 'Este posibil ca unele servicii de căutare să întâmpine probleme. Încercați din nou într-o clipă.', + someServicesUnavailable: 'Unele servicii de căutare nu sunt disponibile', + clearToSearchAll: 'Ștergeți @ pentru a căuta toate', + }, } export default translation diff --git a/web/i18n/ru-RU/app.ts b/web/i18n/ru-RU/app.ts index fa7b79a37..933f48747 100644 --- a/web/i18n/ru-RU/app.ts +++ b/web/i18n/ru-RU/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequests: 'Максимальное количество параллельных запросов', maxActiveRequestsPlaceholder: 'Введите 0 для неограниченного количества', maxActiveRequestsTip: 'Максимальное количество одновременно активных запросов на одно приложение (0 для неограниченного количества)', + gotoAnything: { + actions: { + searchPlugins: 'Поисковые плагины', + searchKnowledgeBases: 'Поиск в базах знаний', + searchApplications: 'Поиск приложений', + searchKnowledgeBasesDesc: 'Поиск и переход к базам знаний', + searchPluginsDesc: 'Поиск и переход к вашим плагинам', + searchWorkflowNodes: 'Поиск узлов рабочего процесса', + searchApplicationsDesc: 'Поиск и переход к приложениям', + searchWorkflowNodesHelp: 'Эта функция работает только при просмотре рабочего процесса. Сначала перейдите к рабочему процессу.', + searchWorkflowNodesDesc: 'Поиск узлов в текущем рабочем процессе и переход к ним по имени или типу', + }, + emptyState: { + noPluginsFound: 'Плагины не найдены', + noKnowledgeBasesFound: 'Базы знаний не найдены', + noAppsFound: 'Приложения не найдены', + noWorkflowNodesFound: 'Узлы расчетной схемы не найдены', + }, + groups: { + knowledgeBases: 'Базы знаний', + plugins: 'Плагины', + apps: 'Приложения', + workflowNodes: 'Узлы рабочих процессов', + }, + searching: 'Поиск...', + noResults: 'Ничего не найдено', + searchFailed: 'Ошибка поиска', + searchTitle: 'Ищите что угодно', + useAtForSpecific: 'Используйте @ для определенных типов', + clearToSearchAll: 'Очистите @ для поиска по всем', + searchTemporarilyUnavailable: 'Поиск временно недоступен', + searchPlaceholder: 'Найдите или введите @ для команд...', + someServicesUnavailable: 'Некоторые поисковые сервисы недоступны', + servicesUnavailableMessage: 'В некоторых поисковых службах могут возникать проблемы. Повторите попытку через мгновение.', + }, } export default translation diff --git a/web/i18n/sl-SI/app.ts b/web/i18n/sl-SI/app.ts index 0d286b4c1..29cc9b805 100644 --- a/web/i18n/sl-SI/app.ts +++ b/web/i18n/sl-SI/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequestsPlaceholder: 'Vnesite 0 za neomejeno', maxActiveRequests: 'Maksimalno število hkratnih zahtevkov', maxActiveRequestsTip: 'Največje število hkrati aktivnih zahtevkov na aplikacijo (0 za neomejeno)', + gotoAnything: { + actions: { + searchWorkflowNodes: 'Iskanje vozlišč poteka dela', + searchKnowledgeBasesDesc: 'Iskanje in krmarjenje do zbirk znanja', + searchWorkflowNodesHelp: 'Ta funkcija deluje le pri ogledu poteka dela. Najprej se pomaknite do poteka dela.', + searchApplicationsDesc: 'Iskanje in krmarjenje do aplikacij', + searchPlugins: 'Iskalni vtičniki', + searchApplications: 'Iskanje aplikacij', + searchWorkflowNodesDesc: 'Iskanje vozlišč in skok nanje v trenutnem poteku dela po imenu ali vrsti', + searchKnowledgeBases: 'Iskanje po zbirkah znanja', + searchPluginsDesc: 'Iskanje in krmarjenje do vtičnikov', + }, + emptyState: { + noPluginsFound: 'Vtičnikov ni mogoče najti', + noWorkflowNodesFound: 'Vozlišča poteka dela niso bila najdena', + noKnowledgeBasesFound: 'Zbirk znanja ni mogoče najti', + noAppsFound: 'Ni bilo najdenih aplikacij', + }, + groups: { + workflowNodes: 'Vozlišča poteka dela', + apps: 'Apps', + knowledgeBases: 'Baze znanja', + plugins: 'Vtičniki', + }, + searching: 'Iskanje...', + searchTitle: 'Poiščite karkoli', + searchTemporarilyUnavailable: 'Iskanje začasno ni na voljo', + someServicesUnavailable: 'Nekatere iskalne storitve niso na voljo', + noResults: 'Ni najdenih rezultatov', + clearToSearchAll: 'Počisti @ za iskanje vseh', + searchPlaceholder: 'Poiščite ali vnesite @ za ukaze ...', + searchFailed: 'Iskanje ni uspelo', + useAtForSpecific: 'Uporaba znaka @ za določene vrste', + servicesUnavailableMessage: 'Pri nekaterih iskalnih storitvah se morda pojavljajo težave. Poskusite znova čez trenutek.', + }, } export default translation diff --git a/web/i18n/th-TH/app.ts b/web/i18n/th-TH/app.ts index adb377d6f..d1ef99459 100644 --- a/web/i18n/th-TH/app.ts +++ b/web/i18n/th-TH/app.ts @@ -248,6 +248,40 @@ const translation = { maxActiveRequestsPlaceholder: 'ใส่ 0 สำหรับไม่จำกัด', maxActiveRequests: 'จำนวนคำขอพร้อมกันสูงสุด', maxActiveRequestsTip: 'จำนวนการร้องขอที่ใช้งานพร้อมกันสูงสุดต่อแอป (0 หมายถึงไม่จำกัด)', + gotoAnything: { + actions: { + searchKnowledgeBases: 'ค้นหาฐานความรู้', + searchPlugins: 'ค้นหาปลั๊กอิน', + searchWorkflowNodes: 'ค้นหาโหนดเวิร์กโฟลว์', + searchApplications: 'ค้นหาแอปพลิเคชัน', + searchKnowledgeBasesDesc: 'ค้นหาและนําทางไปยังฐานความรู้ของคุณ', + searchPluginsDesc: 'ค้นหาและนําทางไปยังปลั๊กอินของคุณ', + searchApplicationsDesc: 'ค้นหาและนําทางไปยังแอปพลิเคชันของคุณ', + searchWorkflowNodesHelp: 'คุณลักษณะนี้ใช้ได้เฉพาะเมื่อดูเวิร์กโฟลว์เท่านั้น นําทางไปยังเวิร์กโฟลว์ก่อน', + searchWorkflowNodesDesc: 'ค้นหาและข้ามไปยังโหนดในเวิร์กโฟลว์ปัจจุบันตามชื่อหรือประเภท', + }, + emptyState: { + noPluginsFound: 'ไม่พบปลั๊กอิน', + noAppsFound: 'ไม่พบแอป', + noWorkflowNodesFound: 'ไม่พบโหนดเวิร์กโฟลว์', + noKnowledgeBasesFound: 'ไม่พบฐานความรู้', + }, + groups: { + apps: 'ปพลิ เค ชัน', + knowledgeBases: 'ฐานความรู้', + plugins: 'ปลั๊กอิน', + workflowNodes: 'โหนดเวิร์กโฟลว์', + }, + searchTitle: 'ค้นหาอะไรก็ได้', + searchFailed: 'การค้นหาล้มเหลว', + useAtForSpecific: 'ใช้ @ สําหรับบางประเภท', + noResults: 'ไม่พบผลลัพธ์', + searchTemporarilyUnavailable: 'การค้นหาไม่พร้อมใช้งานชั่วคราว', + someServicesUnavailable: 'บริการค้นหาบางบริการไม่พร้อมใช้งาน', + clearToSearchAll: 'ล้าง @ เพื่อค้นหาทั้งหมด', + searchPlaceholder: 'ค้นหาหรือพิมพ์ @ สําหรับคําสั่ง...', + servicesUnavailableMessage: 'บริการค้นหาบางบริการอาจประสบปัญหา ลองอีกครั้งในอีกสักครู่', + }, } export default translation diff --git a/web/i18n/tr-TR/app.ts b/web/i18n/tr-TR/app.ts index 61a7eb926..61203684d 100644 --- a/web/i18n/tr-TR/app.ts +++ b/web/i18n/tr-TR/app.ts @@ -248,6 +248,41 @@ const translation = { maxActiveRequestsPlaceholder: 'Sınırsız için 0 girin', maxActiveRequests: 'Maksimum eş zamanlı istekler', maxActiveRequestsTip: 'Her uygulama için maksimum eşzamanlı aktif istek sayısı (sınırsız için 0)', + gotoAnything: { + actions: { + searchKnowledgeBasesDesc: 'Bilgi bankalarınızda arama yapın ve bu forumlara gidin', + searchWorkflowNodesDesc: 'Geçerli iş akışındaki düğümleri ada veya türe göre bulun ve atlayın', + searchApplications: 'Arama Uygulamaları', + searchKnowledgeBases: 'Bilgi Bankalarında Ara', + searchWorkflowNodes: 'Arama İş Akışı Düğümleri', + searchPluginsDesc: 'Eklentilerinizi arayın ve eklentilerinize gidin', + searchPlugins: 'Arama Eklentileri', + searchWorkflowNodesHelp: 'Bu özellik yalnızca bir iş akışını görüntülerken çalışır. Önce bir iş akışına gidin.', + searchApplicationsDesc: 'Uygulamalarınızı arayın ve uygulamalarınıza gidin', + }, + emptyState: { + noAppsFound: 'Uygulama bulunamadı', + noWorkflowNodesFound: 'İş akışı düğümü bulunamadı', + noKnowledgeBasesFound: 'Bilgi bankası bulunamadı', + noPluginsFound: 'Eklenti bulunamadı', + }, + groups: { + apps: 'Apps', + plugins: 'Eklentiler', + knowledgeBases: 'Bilgi Tabanları', + workflowNodes: 'İş Akışı Düğümleri', + }, + searchFailed: 'Arama başarısız oldu', + clearToSearchAll: 'Tümünü aramak için @ işaretini kaldırın', + someServicesUnavailable: 'Bazı arama hizmetleri kullanılamıyor', + searchPlaceholder: 'Komutlar için @ arayın veya yazın...', + useAtForSpecific: 'Belirli türler için @ kullanın', + searchTemporarilyUnavailable: 'Arama geçici olarak kullanılamıyor', + searchTitle: 'Her şeyi arayın', + noResults: 'Sonuç bulunamadı', + servicesUnavailableMessage: 'Bazı arama hizmetlerinde sorunlar yaşanıyor olabilir. Kısa bir süre sonra tekrar deneyin.', + searching: 'Araştırıcı...', + }, } export default translation diff --git a/web/i18n/uk-UA/app.ts b/web/i18n/uk-UA/app.ts index aab4498ac..4f3e60082 100644 --- a/web/i18n/uk-UA/app.ts +++ b/web/i18n/uk-UA/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequestsPlaceholder: 'Введіть 0 для необмеженого', maxActiveRequests: 'Максимальна кількість одночасних запитів', maxActiveRequestsTip: 'Максимальна кількість одночасних активних запитів на додаток (0 для необмеженої кількості)', + gotoAnything: { + actions: { + searchApplications: 'Пошук додатків', + searchKnowledgeBases: 'Пошук по базах знань', + searchWorkflowNodes: 'Вузли документообігу пошуку', + searchApplicationsDesc: 'Шукайте та переходьте до своїх програм', + searchPluginsDesc: 'Пошук і навігація до ваших плагінів', + searchWorkflowNodesHelp: 'Ця функція працює лише під час перегляду робочого процесу. Спочатку перейдіть до робочого процесу.', + searchPlugins: 'Пошукові плагіни', + searchKnowledgeBasesDesc: 'Шукайте та переходьте до своїх баз знань', + searchWorkflowNodesDesc: 'Знаходьте вузли в поточному робочому процесі та переходьте до них за іменем або типом', + }, + emptyState: { + noPluginsFound: 'Плагінів не знайдено', + noKnowledgeBasesFound: 'Баз знань не знайдено', + noAppsFound: 'Не знайдено додатків', + noWorkflowNodesFound: 'Вузли бізнес-процесу не знайдено', + }, + groups: { + knowledgeBases: 'Бази знань', + plugins: 'Плагіни', + apps: 'Програми', + workflowNodes: 'Вузли документообігу', + }, + searching: 'Пошук...', + searchTitle: 'Шукайте що завгодно', + searchFailed: 'Пошук не вдався', + clearToSearchAll: 'Clear @ для пошуку всіх', + noResults: 'Результатів не знайдено', + searchPlaceholder: 'Виконайте пошук або введіть @ для команд...', + searchTemporarilyUnavailable: 'Пошук тимчасово недоступний', + useAtForSpecific: 'Використовуйте @ для конкретних типів', + someServicesUnavailable: 'Деякі пошукові сервіси недоступні', + servicesUnavailableMessage: 'У деяких пошукових службах можуть виникати проблеми. Повторіть спробу за мить.', + }, } export default translation diff --git a/web/i18n/vi-VN/app.ts b/web/i18n/vi-VN/app.ts index 00e1a0bd5..45d43d36e 100644 --- a/web/i18n/vi-VN/app.ts +++ b/web/i18n/vi-VN/app.ts @@ -252,6 +252,41 @@ const translation = { maxActiveRequestsPlaceholder: 'Nhập 0 để không giới hạn', maxActiveRequests: 'Số yêu cầu đồng thời tối đa', maxActiveRequestsTip: 'Số yêu cầu hoạt động đồng thời tối đa cho mỗi ứng dụng (0 để không giới hạn)', + gotoAnything: { + actions: { + searchPlugins: 'Tìm kiếm Plugin', + searchPluginsDesc: 'Tìm kiếm và điều hướng đến plugin của bạn', + searchKnowledgeBases: 'Tìm kiếm cơ sở kiến thức', + searchApplicationsDesc: 'Tìm kiếm và điều hướng đến các ứng dụng của bạn', + searchWorkflowNodesHelp: 'Tính năng này chỉ hoạt động khi xem quy trình làm việc. Điều hướng đến quy trình làm việc trước.', + searchWorkflowNodes: 'Tìm kiếm các nút quy trình làm việc', + searchApplications: 'Tìm kiếm ứng dụng', + searchWorkflowNodesDesc: 'Tìm và chuyển đến các nút trong quy trình làm việc hiện tại theo tên hoặc loại', + searchKnowledgeBasesDesc: 'Tìm kiếm và điều hướng đến cơ sở kiến thức của bạn', + }, + emptyState: { + noWorkflowNodesFound: 'Không tìm thấy nút quy trình làm việc', + noKnowledgeBasesFound: 'Không tìm thấy cơ sở kiến thức', + noPluginsFound: 'Không tìm thấy plugin', + noAppsFound: 'Không tìm thấy ứng dụng nào', + }, + groups: { + plugins: 'Plugin', + workflowNodes: 'Nút quy trình làm việc', + knowledgeBases: 'Cơ sở kiến thức', + apps: 'Ứng dụng', + }, + searchTemporarilyUnavailable: 'Tìm kiếm tạm thời không khả dụng', + clearToSearchAll: 'Xóa @ để tìm kiếm tất cả', + noResults: 'Không tìm thấy kết quả', + searching: 'Tìm kiếm...', + searchPlaceholder: 'Tìm kiếm hoặc nhập @ cho các lệnh...', + searchTitle: 'Tìm kiếm bất cứ thứ gì', + searchFailed: 'Tìm kiếm không thành công', + useAtForSpecific: 'Sử dụng @ cho các loại cụ thể', + someServicesUnavailable: 'Một số dịch vụ tìm kiếm không khả dụng', + servicesUnavailableMessage: 'Một số dịch vụ tìm kiếm có thể gặp sự cố. Thử lại trong giây lát.', + }, } export default translation diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index 32cfc4cf3..5acc4c13c 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -251,6 +251,46 @@ const translation = { maxActiveRequests: '最大活跃请求数', maxActiveRequestsPlaceholder: '0 表示不限制', maxActiveRequestsTip: '当前应用的最大活跃请求数(0 表示不限制)', + gotoAnything: { + searchPlaceholder: '搜索或输入 @ 以使用命令...', + searchTitle: '搜索任何内容', + searching: '搜索中...', + noResults: '未找到结果', + searchFailed: '搜索失败', + searchTemporarilyUnavailable: '搜索暂时不可用', + servicesUnavailableMessage: '某些搜索服务可能遇到问题,请稍后再试。', + someServicesUnavailable: '某些搜索服务不可用', + resultCount: '{{count}} 个结果', + resultCount_other: '{{count}} 个结果', + inScope: '在 {{scope}}s 中', + clearToSearchAll: '清除 @ 以搜索全部', + useAtForSpecific: '使用 @ 进行特定类型搜索', + actions: { + searchApplications: '搜索应用程序', + searchApplicationsDesc: '搜索并导航到您的应用程序', + searchPlugins: '搜索插件', + searchPluginsDesc: '搜索并导航到您的插件', + searchKnowledgeBases: '搜索知识库', + searchKnowledgeBasesDesc: '搜索并导航到您的知识库', + searchWorkflowNodes: '搜索工作流节点', + searchWorkflowNodesDesc: '按名称或类型查找并跳转到当前工作流中的节点', + searchWorkflowNodesHelp: '此功能仅在查看工作流时有效。首先导航到工作流。', + }, + emptyState: { + noAppsFound: '未找到应用', + noPluginsFound: '未找到插件', + noKnowledgeBasesFound: '未找到知识库', + noWorkflowNodesFound: '未找到工作流节点', + tryDifferentTerm: '尝试不同的搜索词或移除 {{mode}} 过滤器', + trySpecificSearch: '尝试使用 {{shortcuts}} 进行特定搜索', + }, + groups: { + apps: '应用程序', + plugins: '插件', + knowledgeBases: '知识库', + workflowNodes: '工作流节点', + }, + }, } export default translation diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 6a74dc7e0..018404805 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -958,6 +958,11 @@ const translation = { settingsTab: '设置', lastRunTab: '上次运行', relationsTab: '关系', + copyLastRun: '复制上次运行值', + noLastRunFound: '未找到上次运行记录', + noMatchingInputsFound: '上次运行中未找到匹配的输入', + lastRunInputsCopied: '已复制{{count}}个输入值', + copyLastRunError: '复制上次运行输入失败', noData: { description: '上次运行的结果将显示在这里', runThisNode: '运行此节点', diff --git a/web/i18n/zh-Hant/app.ts b/web/i18n/zh-Hant/app.ts index 8d1cc69f2..1164db0ad 100644 --- a/web/i18n/zh-Hant/app.ts +++ b/web/i18n/zh-Hant/app.ts @@ -251,6 +251,41 @@ const translation = { maxActiveRequestsPlaceholder: '輸入 0 以表示無限', maxActiveRequests: '同時最大請求數', maxActiveRequestsTip: '每個應用程式可同時活躍請求的最大數量(0為無限制)', + gotoAnything: { + actions: { + searchWorkflowNodes: '搜索工作流節點', + searchPluginsDesc: '搜索並導航到您的外掛程式', + searchApplications: '搜索應用程式', + searchKnowledgeBases: '搜索知識庫', + searchKnowledgeBasesDesc: '搜索並導航到您的知識庫', + searchWorkflowNodesHelp: '此功能僅在查看工作流時有效。首先導航到工作流。', + searchApplicationsDesc: '搜索並導航到您的應用程式', + searchPlugins: '搜索外掛程式', + searchWorkflowNodesDesc: '按名稱或類型查找並跳轉到當前工作流中的節點', + }, + emptyState: { + noAppsFound: '未找到應用', + noWorkflowNodesFound: '未找到工作流節點', + noKnowledgeBasesFound: '未找到知識庫', + noPluginsFound: '未找到外掛程式', + }, + groups: { + apps: '應用程式', + knowledgeBases: '知識庫', + plugins: '外掛程式', + workflowNodes: '工作流節點', + }, + searchPlaceholder: '搜尋或鍵入 @ 以取得命令...', + searching: '搜索。。。', + searchTitle: '搜索任何內容', + noResults: '未找到結果', + clearToSearchAll: '清除 @ 以搜尋全部', + searchFailed: '搜索失敗', + servicesUnavailableMessage: '某些搜索服務可能遇到問題。稍後再試一次。', + someServicesUnavailable: '某些搜索服務不可用', + useAtForSpecific: '對特定類型使用 @', + searchTemporarilyUnavailable: '搜索暫時不可用', + }, } export default translation diff --git a/web/package.json b/web/package.json index a03334e4a..57027b571 100644 --- a/web/package.json +++ b/web/package.json @@ -75,6 +75,7 @@ "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "copy-to-clipboard": "^3.3.3", "crypto-js": "^4.2.0", "dayjs": "^1.11.13", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 1e36696a8..2f03968bc 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -153,6 +153,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) copy-to-clipboard: specifier: ^3.3.3 version: 3.3.3 @@ -2471,6 +2474,177 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.14': + resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==} + peerDependencies: + '@types/react': ~19.1.8 + '@types/react-dom': ~19.1.6 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.10': + resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} + peerDependencies: + '@types/react': ~19.1.8 + '@types/react-dom': ~19.1.6 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': ~19.1.8 + '@types/react-dom': ~19.1.6 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': ~19.1.8 + '@types/react-dom': ~19.1.6 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.4': + resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} + peerDependencies: + '@types/react': ~19.1.8 + '@types/react-dom': ~19.1.6 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': ~19.1.8 + '@types/react-dom': ~19.1.6 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@react-aria/focus@3.20.5': resolution: {integrity: sha512-JpFtXmWQ0Oca7FcvkqgjSyo6xEP7v3oQOLUId6o0xTvm4AD5W0mU2r3lYrbhsJ+XxdUUX4AVR5473sZZ85kU4A==} peerDependencies: @@ -3651,6 +3825,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -4042,6 +4220,12 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -4490,6 +4674,9 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devalue@5.1.1: resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} @@ -5252,6 +5439,10 @@ packages: resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} engines: {node: '>=18'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -7068,6 +7259,26 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react-rnd@10.5.2: resolution: {integrity: sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==} peerDependencies: @@ -7087,6 +7298,16 @@ packages: react-dom: '>=16.9.0' sortablejs: '1' + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react-syntax-highlighter@15.6.1: resolution: {integrity: sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==} peerDependencies: @@ -7959,6 +8180,16 @@ packages: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + use-composed-ref@1.4.0: resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==} peerDependencies: @@ -7992,6 +8223,16 @@ packages: '@types/react': optional: true + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ~19.1.8 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + use-strict@1.0.1: resolution: {integrity: sha512-IeiWvvEXfW5ltKVMkxq6FvNf2LojMKvB2OCeja6+ct24S1XOmQw2dGr2JyndwACWAGJva9B7yPHwAmeA9QCqAQ==} @@ -10470,6 +10711,149 @@ snapshots: '@polka/url@1.0.0-next.29': {} + '@radix-ui/primitive@1.1.2': {} + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + '@react-aria/focus@3.20.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@react-aria/interactions': 3.25.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -11968,6 +12352,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -12388,6 +12776,18 @@ snapshots: clsx@2.1.1: {} + cmdk@1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + co@4.6.0: {} code-inspector-core@0.18.3: @@ -12862,6 +13262,8 @@ snapshots: detect-newline@3.1.0: {} + detect-node-es@1.1.0: {} + devalue@5.1.1: {} devlop@1.1.0: @@ -13887,6 +14289,8 @@ snapshots: get-east-asian-width@1.3.0: {} + get-nonce@1.0.1: {} + get-package-type@0.1.0: {} get-stream@5.2.0: @@ -16275,6 +16679,25 @@ snapshots: react-refresh@0.14.2: {} + react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): + dependencies: + react: 19.1.0 + react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + + react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.0): + dependencies: + react: 19.1.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.8)(react@19.1.0) + react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.0) + use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + react-rnd@10.5.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: re-resizable: 6.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -16297,6 +16720,14 @@ snapshots: sortablejs: 1.15.6 tiny-invariant: 1.2.0 + react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0): + dependencies: + get-nonce: 1.0.1 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + react-syntax-highlighter@15.6.1(react@19.1.0): dependencies: '@babel/runtime': 7.27.6 @@ -17322,6 +17753,13 @@ snapshots: punycode: 1.4.1 qs: 6.14.0 + use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.0): + dependencies: + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + use-composed-ref@1.4.0(@types/react@19.1.8)(react@19.1.0): dependencies: react: 19.1.0 @@ -17346,6 +17784,14 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + use-strict@1.0.1: {} use-sync-external-store@1.5.0(react@19.1.0): diff --git a/web/utils/app-redirection.ts b/web/utils/app-redirection.ts index 93273129d..dfecbd17d 100644 --- a/web/utils/app-redirection.ts +++ b/web/utils/app-redirection.ts @@ -1,17 +1,25 @@ import type { AppMode } from '@/types/app' +export const getRedirectionPath = ( + isCurrentWorkspaceEditor: boolean, + app: { id: string, mode: AppMode }, +) => { + if (!isCurrentWorkspaceEditor) { + return `/app/${app.id}/overview` + } + else { + if (app.mode === 'workflow' || app.mode === 'advanced-chat') + return `/app/${app.id}/workflow` + else + return `/app/${app.id}/configuration` + } +} + export const getRedirection = ( isCurrentWorkspaceEditor: boolean, app: { id: string, mode: AppMode }, redirectionFunc: (href: string) => void, ) => { - if (!isCurrentWorkspaceEditor) { - redirectionFunc(`/app/${app.id}/overview`) - } - else { - if (app.mode === 'workflow' || app.mode === 'advanced-chat') - redirectionFunc(`/app/${app.id}/workflow`) - else - redirectionFunc(`/app/${app.id}/configuration`) - } + const redirectionPath = getRedirectionPath(isCurrentWorkspaceEditor, app) + redirectionFunc(redirectionPath) }