diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx index 9f828c9001..3190fe77bb 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx @@ -17,13 +17,13 @@ import * as React from 'react' import { useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import AppSideBar from '@/app/components/app-sidebar' -import { useStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import Loading from '@/app/components/base/loading' import { useStore as useTagStore } from '@/app/components/base/tag-management/store' import { useAppContext } from '@/context/app-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useDocumentTitle from '@/hooks/use-document-title' -import { useAppDetail } from '@/service/use-apps' +import { usePrefetchAppDetail } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' import { cn } from '@/utils/classnames' import s from './style.module.css' @@ -46,10 +46,10 @@ const AppDetailLayout: FC = (props) => { const isMobile = media === MediaType.mobile const { isCurrentWorkspaceEditor, isLoadingCurrentWorkspace } = useAppContext() - const setAppSidebarExpand = useStore(s => s.setAppSidebarExpand) + const setAppSidebarExpand = useAppStore(s => s.setAppSidebarExpand) const showTagManagementModal = useTagStore(s => s.showTagManagementModal) - const { data: appDetail, isPending, error } = useAppDetail(appId) + const { data: appDetail, isPending, error } = usePrefetchAppDetail(appId) const navigation = useMemo(() => { if (!appDetail) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx index 9365d03fea..c614ce884c 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx @@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import AppCard from '@/app/components/app/overview/app-card' import TriggerCard from '@/app/components/app/overview/trigger-card' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Loading from '@/app/components/base/loading' import { ToastContext } from '@/app/components/base/toast' import MCPServiceCard from '@/app/components/tools/mcp/mcp-service-card' @@ -21,7 +22,7 @@ import { updateAppSiteConfig, updateAppSiteStatus, } from '@/service/apps' -import { useAppDetail, useInvalidateAppDetail } from '@/service/use-apps' +import { useInvalidateAppDetail } from '@/service/use-apps' import { useAppWorkflow } from '@/service/use-workflow' import { AppModeEnum } from '@/types/app' import { asyncRunSafe } from '@/utils' @@ -36,7 +37,7 @@ const CardView: FC = ({ appId, isInPanel, className }) => { const { t } = useTranslation() const docLink = useDocLink() const { notify } = useContext(ToastContext) - const { data: appDetail } = useAppDetail(appId) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId)) const invalidateAppDetail = useInvalidateAppDetail() const isWorkflowApp = appDetail?.mode === AppModeEnum.WORKFLOW diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx index ada41d2403..099df7470d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx @@ -8,8 +8,8 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import { TIME_PERIOD_MAPPING as LONG_TIME_PERIOD_MAPPING } from '@/app/components/app/log/filter' import { AvgResponseTime, AvgSessionInteractions, AvgUserInteractions, ConversationsChart, CostChart, EndUsersChart, MessagesChart, TokenPerSecond, UserSatisfactionRate, WorkflowCostChart, WorkflowDailyTerminalsChart, WorkflowMessagesChart } from '@/app/components/app/overview/app-chart' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { IS_CLOUD_EDITION } from '@/config' -import { useAppDetail } from '@/service/use-apps' import LongTimeRangePicker from './long-time-range-picker' import TimeRangePicker from './time-range-picker' @@ -34,7 +34,7 @@ export type IChartViewProps = { export default function ChartView({ appId, headerRight }: IChartViewProps) { const { t } = useTranslation() - const { data: appDetail } = useAppDetail(appId) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId)) const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow' const isWorkflow = appDetail?.mode === 'workflow' const [period, setPeriod] = useState(IS_CLOUD_EDITION diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 1c5434924f..e7bdc84edd 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -14,7 +14,7 @@ import * as React from 'react' import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import AppSideBar from '@/app/components/app-sidebar' -import { useStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import { PipelineFill, PipelineLine } from '@/app/components/base/icons/src/vender/pipeline' import Loading from '@/app/components/base/loading' import ExtraInfo from '@/app/components/datasets/extra-info' @@ -107,7 +107,7 @@ const DatasetDetailLayout: FC = (props) => { useDocumentTitle(datasetRes?.name || t('menus.datasets', { ns: 'common' })) - const setAppSidebarExpand = useStore(state => state.setAppSidebarExpand) + const setAppSidebarExpand = useAppStore(state => state.setAppSidebarExpand) useEffect(() => { const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 588ffbcbe5..6edd009b1e 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -18,6 +18,7 @@ import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' import ContentDialog from '@/app/components/base/content-dialog' import { ToastContext } from '@/app/components/base/toast' @@ -25,7 +26,7 @@ import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps' -import { useAppDetail, useInvalidateAppDetail, useInvalidateAppList } from '@/service/use-apps' +import { useInvalidateAppDetail, useInvalidateAppList } from '@/service/use-apps' import { fetchWorkflowDraft } from '@/service/workflow' import { AppModeEnum } from '@/types/app' import { getRedirection } from '@/utils/app-redirection' @@ -65,7 +66,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx const { replace } = useRouter() const { appId } = useParams() const { onPlanInfoChanged } = useProviderContext() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const invalidateAppDetail = useInvalidateAppDetail() const invalidateAppList = useInvalidateAppList() const [open, setOpen] = useState(openState) diff --git a/web/app/components/app-sidebar/app-sidebar-dropdown.tsx b/web/app/components/app-sidebar/app-sidebar-dropdown.tsx index d116ed4bba..a03bd5be9a 100644 --- a/web/app/components/app-sidebar/app-sidebar-dropdown.tsx +++ b/web/app/components/app-sidebar/app-sidebar-dropdown.tsx @@ -7,13 +7,13 @@ import { useParams } from 'next/navigation' import * as React from 'react' import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import { useAppContext } from '@/context/app-context' -import { useAppDetail } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' import { cn } from '@/utils/classnames' import AppIcon from '../base/app-icon' @@ -34,7 +34,7 @@ const AppSidebarDropdown = ({ navigation }: Props) => { const { t } = useTranslation() const { appId } = useParams() const { isCurrentWorkspaceEditor } = useAppContext() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const [detailExpand, setDetailExpand] = useState(false) const [open, doSetOpen] = useState(false) diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index afc6bd0f13..f6e22c9c87 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -4,7 +4,7 @@ import { usePathname } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' import { useShallow } from 'zustand/react/shallow' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import { useEventEmitterContextContext } from '@/context/event-emitter' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index a2e4a527b3..8680357a8e 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -42,7 +42,7 @@ import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { AccessMode } from '@/models/access-control' import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/access-control' import { fetchInstalledAppList } from '@/service/explore' -import { useAppDetail, useInvalidateAppDetail } from '@/service/use-apps' +import { useInvalidateAppDetail } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' import { basePath } from '@/utils/var' import Divider from '../../base/divider' @@ -51,6 +51,7 @@ import Toast from '../../base/toast' import Tooltip from '../../base/tooltip' import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '../../workflow/utils' import AccessControl from '../app-access-control' +import { appStoreSelectors, useAppStore } from '../store' import PublishWithMultipleModel from './publish-with-multiple-model' import SuggestedAction from './suggested-action' @@ -146,7 +147,7 @@ const AppPublisher = ({ const [showAppAccessControl, setShowAppAccessControl] = useState(false) const [embeddingModalOpen, setEmbeddingModalOpen] = useState(false) - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const invalidateAppDetail = useInvalidateAppDetail() const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const { formatTimeFromNow } = useFormatTimeFromNow() diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx index a9124398e1..9bf8eeb55b 100644 --- a/web/app/components/app/configuration/config-var/config-modal/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -22,9 +22,9 @@ import FileUploadSetting from '@/app/components/workflow/nodes/_base/components/ import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { ChangeType, InputVarType, SupportUploadFileTypes } from '@/app/components/workflow/types' import ConfigContext from '@/context/debug-configuration' -import { useAppDetail } from '@/service/use-apps' import { AppModeEnum, TransferMethod } from '@/types/app' import { checkKeys, getNewVarInWorkflow, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' +import { appStoreSelectors, useAppStore } from '../../../store' import ConfigSelect from '../config-select' import ConfigString from '../config-string' import ModalFoot from '../modal-foot' @@ -74,7 +74,7 @@ const ConfigModal: FC = ({ const { modelConfig } = useContext(ConfigContext) const { t } = useTranslation() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const [tempPayload, setTempPayload] = useState(() => normalizeSelectDefaultValue(payload || getNewVarInWorkflow('') as any)) const { type, label, variable, options, max_length } = tempPayload const modalRef = useRef(null) diff --git a/web/app/components/app/configuration/config-var/index.spec.tsx b/web/app/components/app/configuration/config-var/index.spec.tsx index 69a2fb6f9e..210f9c4150 100644 --- a/web/app/components/app/configuration/config-var/index.spec.tsx +++ b/web/app/components/app/configuration/config-var/index.spec.tsx @@ -8,7 +8,7 @@ import * as React from 'react' import { vi } from 'vitest' import Toast from '@/app/components/base/toast' import DebugConfigurationContext from '@/context/debug-configuration' -import { useAppDetail } from '@/service/use-apps' +import { usePrefetchAppDetail } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' import ConfigVar, { ADD_EXTERNAL_DATA_TOOL } from './index' @@ -47,7 +47,7 @@ vi.mock('next/navigation', () => ({ })) vi.mock('@/service/use-apps') -const mockUseAppDetail = vi.mocked(useAppDetail) +const mockUseAppDetail = vi.mocked(usePrefetchAppDetail) type SortableItem = { id: string @@ -105,7 +105,7 @@ function setupUseAppDetailMock() { isLoading: false, isPending: false, error: null, - } as ReturnType) + } as ReturnType) } const renderConfigVar = (props: Partial = {}, debugOverrides: Partial = {}) => { diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx index 188086246a..765a732acd 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx @@ -7,7 +7,7 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { Inputs, ModelConfig } from '@/models/debug' import type { PromptVariable } from '@/types/app' import { fireEvent, render, screen } from '@testing-library/react' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import { DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' import { AppModeEnum, ModelModeType, Resolution, TransferMethod } from '@/types/app' import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types' diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/index.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/index.tsx index c73eb54329..6946617329 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/index.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/index.tsx @@ -7,7 +7,7 @@ import { useCallback, useMemo, } from 'react' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import ChatInputArea from '@/app/components/base/chat/chat/chat-input-area' import { useFeatures } from '@/app/components/base/features/hooks' import { useDebugConfigurationContext } from '@/context/debug-configuration' diff --git a/web/app/components/app/configuration/debug/debug-with-single-model/index.spec.tsx b/web/app/components/app/configuration/debug/debug-with-single-model/index.spec.tsx index b9a1c5ba8b..415d8e1204 100644 --- a/web/app/components/app/configuration/debug/debug-with-single-model/index.spec.tsx +++ b/web/app/components/app/configuration/debug/debug-with-single-model/index.spec.tsx @@ -7,7 +7,7 @@ import type { ProviderContextState } from '@/context/provider-context' import type { DatasetConfigs, ModelConfig } from '@/models/debug' import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' import { createRef } from 'react' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import { ConfigurationMethodEnum, ModelFeatureEnum, ModelStatusEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { CollectionType } from '@/app/components/tools/types' import { PromptMode } from '@/models/debug' diff --git a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx index f0d41b16f0..4986ee9933 100644 --- a/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx +++ b/web/app/components/app/configuration/debug/debug-with-single-model/index.tsx @@ -2,7 +2,7 @@ import type { InputForm } from '@/app/components/base/chat/chat/type' import type { ChatConfig, ChatItem, OnSend } from '@/app/components/base/chat/types' import type { FileEntity } from '@/app/components/base/file-uploader/types' import { memo, useCallback, useImperativeHandle, useMemo } from 'react' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import Avatar from '@/app/components/base/avatar' import Chat from '@/app/components/base/chat/chat' import { useChat } from '@/app/components/base/chat/chat/hooks' diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index b97bd68c5d..3efe59f3ed 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -21,7 +21,7 @@ import { useContext } from 'use-context-selector' import { useShallow } from 'zustand/react/shallow' import ChatUserInput from '@/app/components/app/configuration/debug/chat-user-input' import PromptValuePanel from '@/app/components/app/configuration/prompt-value-panel' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import TextGeneration from '@/app/components/app/text-generate/item' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import AgentLogModal from '@/app/components/base/agent-log-modal' diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index febf8d583c..0548540202 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -40,7 +40,7 @@ import { useFormattingChangedDispatcher, } from '@/app/components/app/configuration/debug/hooks' import useAdvancedPromptConfig from '@/app/components/app/configuration/hooks/use-advanced-prompt-config' -import { useStore as useAppStore } from '@/app/components/app/store' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' import Confirm from '@/app/components/base/confirm' import Divider from '@/app/components/base/divider' @@ -74,7 +74,6 @@ import { PromptMode } from '@/models/debug' import { updateAppModelConfig } from '@/service/apps' import { fetchDatasets } from '@/service/datasets' import { fetchCollectionList } from '@/service/tools' -import { useAppDetail } from '@/service/use-apps' import { useFileUploadConfig } from '@/service/use-common' import { AgentStrategy, AppModeEnum, ModelModeType, Resolution, RETRIEVE_TYPE, TransferMethod } from '@/types/app' import { @@ -105,7 +104,7 @@ const Configuration: FC = () => { const pathname = usePathname() const matched = pathname.match(/\/app\/([^/]+)/) const appId = (matched?.length && matched[1]) ? matched[1] : '' - const { data: appDetail } = useAppDetail(appId) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId)) const latestPublishedAt = useMemo(() => appDetail?.model_config?.updated_at, [appDetail]) const [formattingChanged, setFormattingChanged] = useState(false) diff --git a/web/app/components/app/configuration/prompt-value-panel/index.tsx b/web/app/components/app/configuration/prompt-value-panel/index.tsx index 613efb8710..06b8ad262e 100644 --- a/web/app/components/app/configuration/prompt-value-panel/index.tsx +++ b/web/app/components/app/configuration/prompt-value-panel/index.tsx @@ -11,7 +11,7 @@ import * as React from 'react' import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' import FeatureBar from '@/app/components/base/features/new-feature-panel/feature-bar' import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader' diff --git a/web/app/components/app/log-annotation/index.spec.tsx b/web/app/components/app/log-annotation/index.spec.tsx index 73679a38b0..35c23d71ca 100644 --- a/web/app/components/app/log-annotation/index.spec.tsx +++ b/web/app/components/app/log-annotation/index.spec.tsx @@ -2,12 +2,12 @@ import type { App, AppIconType } from '@/types/app' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { PageType } from '@/app/components/base/features/new-feature-panel/annotation-reply/type' -import { useAppDetail } from '@/service/use-apps' +import { usePrefetchAppDetail } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' import LogAnnotation from './index' vi.mock('@/service/use-apps') -const mockUseAppDetail = vi.mocked(useAppDetail) +const mockUseAppDetail = vi.mocked(usePrefetchAppDetail) const mockRouterPush = vi.fn() vi.mock('next/navigation', () => ({ @@ -72,7 +72,7 @@ function mockAppDetailReturn(app: App | undefined) { data: app, isLoading: false, error: null, - } as ReturnType) + } as ReturnType) } describe('LogAnnotation', () => { diff --git a/web/app/components/app/log-annotation/index.tsx b/web/app/components/app/log-annotation/index.tsx index 217d385f86..a08547f801 100644 --- a/web/app/components/app/log-annotation/index.tsx +++ b/web/app/components/app/log-annotation/index.tsx @@ -10,9 +10,9 @@ import WorkflowLog from '@/app/components/app/workflow-log' import { PageType } from '@/app/components/base/features/new-feature-panel/annotation-reply/type' import Loading from '@/app/components/base/loading' import TabSlider from '@/app/components/base/tab-slider-plain' -import { useAppDetail } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' import { cn } from '@/utils/classnames' +import { appStoreSelectors, useAppStore } from '../store' type Props = { pageType: PageType @@ -24,7 +24,7 @@ const LogAnnotation: FC = ({ const { t } = useTranslation() const router = useRouter() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const options = useMemo(() => { if (appDetail?.mode === AppModeEnum.COMPLETION) diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 410953ccf7..7a5895c444 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -21,7 +21,7 @@ import { useTranslation } from 'react-i18next' import { createContext, useContext } from 'use-context-selector' import { useShallow } from 'zustand/react/shallow' import ModelInfo from '@/app/components/app/log/model-info' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import TextGeneration from '@/app/components/app/text-generate/item' import ActionButton from '@/app/components/base/action-button' import Chat from '@/app/components/base/chat/chat' diff --git a/web/app/components/app/overview/app-card.tsx b/web/app/components/app/overview/app-card.tsx index 7e6fcd12fb..f8a861be5a 100644 --- a/web/app/components/app/overview/app-card.tsx +++ b/web/app/components/app/overview/app-card.tsx @@ -34,12 +34,13 @@ import { useGlobalPublicStore } from '@/context/global-public-context' import { useDocLink } from '@/context/i18n' import { AccessMode } from '@/models/access-control' import { useAppWhiteListSubjects } from '@/service/access-control' -import { useAppDetail, useInvalidateAppDetail } from '@/service/use-apps' +import { useInvalidateAppDetail } from '@/service/use-apps' import { useAppWorkflow } from '@/service/use-workflow' import { AppModeEnum } from '@/types/app' import { asyncRunSafe } from '@/utils' import { basePath } from '@/utils/var' import AccessControl from '../app-access-control' +import { appStoreSelectors, useAppStore } from '../store' import CustomizeModal from './customize' import EmbeddedModal from './embedded' import SettingsModal from './settings' @@ -76,7 +77,7 @@ function AppCard({ const { isCurrentWorkspaceManager, isCurrentWorkspaceEditor } = useAppContext() const { data: currentWorkflow } = useAppWorkflow(appInfo.mode === AppModeEnum.WORKFLOW ? appInfo.id : '') const docLink = useDocLink() - const { data: appDetail } = useAppDetail(appInfo.id) + const appDetail = useAppStore(appStoreSelectors.appDetails(appInfo.id)) const [showSettingsModal, setShowSettingsModal] = useState(false) const [showEmbedded, setShowEmbedded] = useState(false) const [showCustomizeModal, setShowCustomizeModal] = useState(false) diff --git a/web/app/components/app/store.ts b/web/app/components/app/store.ts index 7e9fe934f8..c0626dd80f 100644 --- a/web/app/components/app/store.ts +++ b/web/app/components/app/store.ts @@ -1,7 +1,13 @@ import type { IChatItem } from '@/app/components/base/chat/chat/type' -import { create } from 'zustand' +import type { App, AppSSO } from '@/types/app' +import { shallow } from 'zustand/shallow' +import { createWithEqualityFn } from 'zustand/traditional' +import { get as serviceGet } from '@/service/base' + +type AppDetail = App & Partial type State = { + appDetails?: Record appSidebarExpand: string currentLogItem?: IChatItem currentLogModalActiveTab: string @@ -21,7 +27,8 @@ type Action = { setShowAppConfigureFeaturesModal: (showAppConfigureFeaturesModal: boolean) => void } -export const useStore = create(set => ({ +export const useAppStore = createWithEqualityFn(set => ({ + appDetails: undefined, appSidebarExpand: '', setAppSidebarExpand: appSidebarExpand => set(() => ({ appSidebarExpand })), currentLogItem: undefined, @@ -46,4 +53,39 @@ export const useStore = create(set => ({ }), showAppConfigureFeaturesModal: false, setShowAppConfigureFeaturesModal: showAppConfigureFeaturesModal => set(() => ({ showAppConfigureFeaturesModal })), -})) +}), shallow) + +const appDetails = (appID: string | undefined) => (state: State) => { + if (!appID) + return undefined + return state.appDetails ? state.appDetails[appID] : undefined +} + +export const appStoreSelectors = { + appDetails, +} + +const get = useAppStore.getState +const set = useAppStore.setState + +async function fetchAppDetail(appID: string | undefined) { + if (!appID) + return null + + const cur = get().appDetails?.[appID] + if (cur) + return cur + + const appDetail = await serviceGet(`/apps/${appID}`) + set(state => ({ + appDetails: { + ...state.appDetails, + [appID]: appDetail, + }, + })) + return appDetail +} + +export const appStoreActions = { + fetchAppDetail, +} diff --git a/web/app/components/app/text-generate/item/index.tsx b/web/app/components/app/text-generate/item/index.tsx index 78f4f426f5..24a2bd0320 100644 --- a/web/app/components/app/text-generate/item/index.tsx +++ b/web/app/components/app/text-generate/item/index.tsx @@ -20,7 +20,7 @@ import { useParams } from 'next/navigation' import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import WorkflowProcessItem from '@/app/components/base/chat/chat/answer/workflow-process' import { useChatContext } from '@/app/components/base/chat/chat/context' diff --git a/web/app/components/app/workflow-log/detail.spec.tsx b/web/app/components/app/workflow-log/detail.spec.tsx index a3f8be2d5c..078c7fb108 100644 --- a/web/app/components/app/workflow-log/detail.spec.tsx +++ b/web/app/components/app/workflow-log/detail.spec.tsx @@ -11,7 +11,7 @@ import type { App, AppIconType, AppModeEnum } from '@/types/app' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { useAppDetail } from '@/service/use-apps' +import { usePrefetchAppDetail } from '@/service/use-apps' import DetailPanel from './detail' // ============================================================================ @@ -19,7 +19,7 @@ import DetailPanel from './detail' // ============================================================================ vi.mock('@/service/use-apps') -const mockUseAppDetail = vi.mocked(useAppDetail) +const mockUseAppDetail = vi.mocked(usePrefetchAppDetail) const mockRouterPush = vi.fn() vi.mock('next/navigation', () => ({ @@ -103,7 +103,7 @@ function mockAppDetailReturn(app: App | undefined) { data: app, isLoading: false, error: null, - } as ReturnType) + } as ReturnType) } // ============================================================================ diff --git a/web/app/components/app/workflow-log/detail.tsx b/web/app/components/app/workflow-log/detail.tsx index 959f65b75a..0d2e38f8d7 100644 --- a/web/app/components/app/workflow-log/detail.tsx +++ b/web/app/components/app/workflow-log/detail.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next' import TooltipPlus from '@/app/components/base/tooltip' import { WorkflowContextProvider } from '@/app/components/workflow/context' import Run from '@/app/components/workflow/run' -import { useAppDetail } from '@/service/use-apps' +import { appStoreSelectors, useAppStore } from '../store' type ILogDetail = { runID: string @@ -17,7 +17,7 @@ type ILogDetail = { const DetailPanel: FC = ({ runID, onClose, canReplay = false }) => { const { t } = useTranslation() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const router = useRouter() const handleReplay = () => { diff --git a/web/app/components/base/agent-log-modal/detail.tsx b/web/app/components/base/agent-log-modal/detail.tsx index 391e30a57a..11b31ab2a8 100644 --- a/web/app/components/base/agent-log-modal/detail.tsx +++ b/web/app/components/base/agent-log-modal/detail.tsx @@ -9,10 +9,10 @@ import * as React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Loading from '@/app/components/base/loading' import { ToastContext } from '@/app/components/base/toast' import { fetchAgentLogDetail } from '@/service/log' -import { useAppDetail } from '@/service/use-apps' import { cn } from '@/utils/classnames' import ResultPanel from './result' import TracingPanel from './tracing' @@ -34,7 +34,7 @@ const AgentLogDetail: FC = ({ const { notify } = useContext(ToastContext) const [currentTab, setCurrentTab] = useState(activeTab) const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const [loading, setLoading] = useState(true) const [runDetail, setRunDetail] = useState() const [list, setList] = useState([]) diff --git a/web/app/components/base/chat/chat/index.tsx b/web/app/components/base/chat/chat/index.tsx index 9485591451..a95c3ed74a 100644 --- a/web/app/components/base/chat/chat/index.tsx +++ b/web/app/components/base/chat/chat/index.tsx @@ -23,7 +23,7 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import AgentLogModal from '@/app/components/base/agent-log-modal' import Button from '@/app/components/base/button' import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' diff --git a/web/app/components/base/chat/chat/log/index.tsx b/web/app/components/base/chat/chat/log/index.tsx index f48da4739d..35591a7d3d 100644 --- a/web/app/components/base/chat/chat/log/index.tsx +++ b/web/app/components/base/chat/chat/log/index.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { IChatItem } from '@/app/components/base/chat/chat/type' import { RiFileList3Line } from '@remixicon/react' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import ActionButton from '@/app/components/base/action-button' type LogProps = { diff --git a/web/app/components/base/message-log-modal/index.tsx b/web/app/components/base/message-log-modal/index.tsx index d7626f7320..4ac86b09bb 100644 --- a/web/app/components/base/message-log-modal/index.tsx +++ b/web/app/components/base/message-log-modal/index.tsx @@ -5,8 +5,8 @@ import { useClickAway } from 'ahooks' import { useParams } from 'next/navigation' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Run from '@/app/components/workflow/run' -import { useAppDetail } from '@/service/use-apps' import { cn } from '@/utils/classnames' type MessageLogModalProps = { @@ -27,7 +27,7 @@ const MessageLogModal: FC = ({ const ref = useRef(null) const [mounted, setMounted] = useState(false) const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) useClickAway(() => { if (mounted) diff --git a/web/app/components/base/prompt-log-modal/index.stories.tsx b/web/app/components/base/prompt-log-modal/index.stories.tsx index 39fab32030..d762b550cf 100644 --- a/web/app/components/base/prompt-log-modal/index.stories.tsx +++ b/web/app/components/base/prompt-log-modal/index.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/nextjs' import type { IChatItem } from '@/app/components/base/chat/chat/type' import { useEffect } from 'react' -import { useStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import PromptLogModal from '.' type PromptLogModalProps = React.ComponentProps @@ -28,9 +28,9 @@ const mockLogItem: IChatItem = { const usePromptLogMocks = () => { useEffect(() => { - useStore.getState().setCurrentLogItem(mockLogItem) + useAppStore.getState().setCurrentLogItem(mockLogItem) return () => { - useStore.getState().setCurrentLogItem(undefined) + useAppStore.getState().setCurrentLogItem(undefined) } }, []) } diff --git a/web/app/components/datasets/documents/detail/completed/new-child-segment.tsx b/web/app/components/datasets/documents/detail/completed/new-child-segment.tsx index 89143662c6..0db5d388e7 100644 --- a/web/app/components/datasets/documents/detail/completed/new-child-segment.tsx +++ b/web/app/components/datasets/documents/detail/completed/new-child-segment.tsx @@ -6,7 +6,7 @@ import { memo, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useShallow } from 'zustand/react/shallow' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import Divider from '@/app/components/base/divider' import { ToastContext } from '@/app/components/base/toast' import { ChunkingMode } from '@/models/datasets' diff --git a/web/app/components/datasets/documents/detail/new-segment.tsx b/web/app/components/datasets/documents/detail/new-segment.tsx index 3a58d6ac06..b56186f3d6 100644 --- a/web/app/components/datasets/documents/detail/new-segment.tsx +++ b/web/app/components/datasets/documents/detail/new-segment.tsx @@ -7,7 +7,7 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useShallow } from 'zustand/react/shallow' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import Divider from '@/app/components/base/divider' import { ToastContext } from '@/app/components/base/toast' import ImageUploaderInChunk from '@/app/components/datasets/common/image-uploader/image-uploader-in-chunk' diff --git a/web/app/components/develop/index.tsx b/web/app/components/develop/index.tsx index e603a32aca..daed481c27 100644 --- a/web/app/components/develop/index.tsx +++ b/web/app/components/develop/index.tsx @@ -2,14 +2,14 @@ import Loading from '@/app/components/base/loading' import ApiServer from '@/app/components/develop/ApiServer' import Doc from '@/app/components/develop/doc' -import { useAppDetail } from '@/service/use-apps' +import { usePrefetchAppDetail } from '@/service/use-apps' type IDevelopMainProps = { appId: string } const DevelopMain = ({ appId }: IDevelopMainProps) => { - const { data: appDetail, isPending } = useAppDetail(appId) + const { data: appDetail, isPending } = usePrefetchAppDetail(appId) if (isPending || !appDetail) { return ( diff --git a/web/app/components/header/app-nav/index.tsx b/web/app/components/header/app-nav/index.tsx index 02043c555b..bd79edf75b 100644 --- a/web/app/components/header/app-nav/index.tsx +++ b/web/app/components/header/app-nav/index.tsx @@ -12,8 +12,9 @@ import { useTranslation } from 'react-i18next' import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog' import CreateAppModal from '@/app/components/app/create-app-modal' import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { useAppContext } from '@/context/app-context' -import { useAppDetail, useInfiniteAppList } from '@/service/use-apps' +import { useInfiniteAppList } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' import Nav from '../nav' @@ -21,7 +22,7 @@ const AppNav = () => { const { t } = useTranslation() const { appId } = useParams() const { isCurrentWorkspaceEditor } = useAppContext() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const [showNewAppDialog, setShowNewAppDialog] = useState(false) const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false) const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false) diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx index c7280c7508..eb5e228e79 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx @@ -8,7 +8,7 @@ import Loading from '@/app/components/base/loading' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' import AppInputsForm from '@/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-form' import { BlockEnum, InputVarType, SupportUploadFileTypes } from '@/app/components/workflow/types' -import { useAppDetail } from '@/service/use-apps' +import { usePrefetchAppDetail } from '@/service/use-apps' import { useFileUploadConfig } from '@/service/use-common' import { useAppWorkflow } from '@/service/use-workflow' import { AppModeEnum, Resolution } from '@/types/app' @@ -33,7 +33,7 @@ const AppInputsPanel = ({ const inputsRef = useRef(value?.inputs || {}) const isBasicApp = appDetail.mode !== AppModeEnum.ADVANCED_CHAT && appDetail.mode !== AppModeEnum.WORKFLOW const { data: fileUploadConfig } = useFileUploadConfig() - const { data: currentApp, isFetching: isAppLoading } = useAppDetail(appDetail.id) + const { data: currentApp, isFetching: isAppLoading } = usePrefetchAppDetail(appDetail.id) const { data: currentWorkflow, isFetching: isWorkflowLoading } = useAppWorkflow(isBasicApp ? '' : appDetail.id) const isLoading = isAppLoading || isWorkflowLoading diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx index 40b0ba9205..79b412fa3c 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx @@ -8,6 +8,7 @@ import type { App } from '@/types/app' import * as React from 'react' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { PortalToFollowElem, PortalToFollowElemContent, @@ -16,7 +17,7 @@ import { import AppInputsPanel from '@/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel' import AppPicker from '@/app/components/plugins/plugin-detail-panel/app-selector/app-picker' import AppTrigger from '@/app/components/plugins/plugin-detail-panel/app-selector/app-trigger' -import { useAppDetail, useInfiniteAppList } from '@/service/use-apps' +import { useInfiniteAppList } from '@/service/use-apps' const PAGE_SIZE = 20 @@ -71,7 +72,7 @@ const AppSelector: FC = ({ }, [pages]) // fetch selected app by id to avoid pagination gaps - const { data: selectedAppDetail } = useAppDetail(value?.app_id || '') + const selectedAppDetail = useAppStore(appStoreSelectors.appDetails(value?.app_id || '')) // Ensure the currently selected app is available for display and in the picker options const currentAppInfo = useMemo(() => { diff --git a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx index 4706a3d61d..a82f53109f 100644 --- a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx @@ -2,7 +2,7 @@ import type { IChatItem } from '@/app/components/base/chat/chat/type' import type { HeaderProps } from '@/app/components/workflow/header' import type { App } from '@/types/app' import { cleanup, fireEvent, render, screen } from '@testing-library/react' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import { AppModeEnum } from '@/types/app' import WorkflowHeader from './index' diff --git a/web/app/components/workflow-app/components/workflow-header/index.tsx b/web/app/components/workflow-app/components/workflow-header/index.tsx index 8117cf0371..61ce6f904f 100644 --- a/web/app/components/workflow-app/components/workflow-header/index.tsx +++ b/web/app/components/workflow-app/components/workflow-header/index.tsx @@ -5,9 +5,8 @@ import { useCallback, useMemo, } from 'react' -import { useStore as useAppStore } from '@/app/components/app/store' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Header from '@/app/components/workflow/header' -import { useAppDetail } from '@/service/use-apps' import { useResetWorkflowVersionHistory } from '@/service/use-workflow' import { useIsChatMode } from '../../hooks' import ChatVariableTrigger from './chat-variable-trigger' @@ -15,7 +14,7 @@ import FeaturesTrigger from './features-trigger' const WorkflowHeader = () => { const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const setCurrentLogItem = useAppStore(state => state.setCurrentLogItem) const setShowMessageLogModal = useAppStore(state => state.setShowMessageLogModal) const resetWorkflowVersionHistory = useResetWorkflowVersionHistory() diff --git a/web/app/components/workflow-app/components/workflow-panel.tsx b/web/app/components/workflow-app/components/workflow-panel.tsx index 9628842d2f..818f62c515 100644 --- a/web/app/components/workflow-app/components/workflow-panel.tsx +++ b/web/app/components/workflow-app/components/workflow-panel.tsx @@ -6,10 +6,9 @@ import { useMemo, } from 'react' import { useShallow } from 'zustand/react/shallow' -import { useStore as useAppStore } from '@/app/components/app/store' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Panel from '@/app/components/workflow/panel' import { useStore } from '@/app/components/workflow/store' -import { useAppDetail } from '@/service/use-apps' import { useIsChatMode, } from '../hooks' @@ -107,7 +106,7 @@ const WorkflowPanelOnRight = () => { } const WorkflowPanel = () => { const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const versionHistoryPanelProps = useMemo(() => { return { getVersionListUrl: `/apps/${appId}/workflows`, diff --git a/web/app/components/workflow-app/hooks/use-DSL.ts b/web/app/components/workflow-app/hooks/use-DSL.ts index a4f1931a08..a7b32354ee 100644 --- a/web/app/components/workflow-app/hooks/use-DSL.ts +++ b/web/app/components/workflow-app/hooks/use-DSL.ts @@ -4,13 +4,13 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { useToastContext } from '@/app/components/base/toast' import { DSL_EXPORT_CHECK, } from '@/app/components/workflow/constants' import { useEventEmitterContextContext } from '@/context/event-emitter' import { exportAppConfig } from '@/service/apps' -import { useAppDetail } from '@/service/use-apps' import { fetchWorkflowDraft } from '@/service/workflow' import { useNodesSyncDraft } from './use-nodes-sync-draft' @@ -22,7 +22,7 @@ export const useDSL = () => { const { doSyncWorkflowDraft } = useNodesSyncDraft() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const handleExportDSL = useCallback(async (include = false, workflowId?: string) => { if (!appDetail) diff --git a/web/app/components/workflow-app/hooks/use-is-chat-mode.ts b/web/app/components/workflow-app/hooks/use-is-chat-mode.ts index 266dd1da51..a4a5c5bb9c 100644 --- a/web/app/components/workflow-app/hooks/use-is-chat-mode.ts +++ b/web/app/components/workflow-app/hooks/use-is-chat-mode.ts @@ -1,10 +1,10 @@ import { useParams } from 'next/navigation' -import { useAppDetail } from '@/service/use-apps' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { AppModeEnum } from '@/types/app' export const useIsChatMode = () => { const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) return appDetail?.mode === AppModeEnum.ADVANCED_CHAT } diff --git a/web/app/components/workflow-app/hooks/use-workflow-init.ts b/web/app/components/workflow-app/hooks/use-workflow-init.ts index 3e9c01eb41..f555e1f08c 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-init.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-init.ts @@ -1,18 +1,19 @@ import type { Edge, Node } from '@/app/components/workflow/types' import type { FileUploadConfigResponse } from '@/models/common' import type { FetchWorkflowDraftResponse } from '@/types/workflow' +import { isEqual } from 'es-toolkit/compat' import { useParams } from 'next/navigation' import { useCallback, useEffect, useState, } from 'react' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { useStore, useWorkflowStore, } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' -import { useAppDetail } from '@/service/use-apps' import { useWorkflowConfig } from '@/service/use-workflow' import { fetchNodesDefaultConfigs, @@ -40,7 +41,7 @@ export const useWorkflowInit = () => { edges: edgesTemplate, } = useWorkflowTemplate() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string), isEqual) const setSyncWorkflowDraftHash = useStore(s => s.setSyncWorkflowDraftHash) const [data, setData] = useState() const [isLoading, setIsLoading] = useState(true) @@ -115,9 +116,9 @@ export const useWorkflowInit = () => { }, [appDetail, nodesTemplate, edgesTemplate, workflowStore, setSyncWorkflowDraftHash]) useEffect(() => { - if (appDetail) + if (appDetail?.id) handleGetInitialWorkflowData() - }, [appDetail, handleGetInitialWorkflowData]) + }, [appDetail?.id]) const handleFetchPreloadData = useCallback(async () => { try { diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts index 5084d038e0..0e53ee7004 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -11,6 +11,7 @@ import { useStoreApi, } from 'reactflow' import { v4 as uuidV4 } from 'uuid' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { trackEvent } from '@/app/components/base/amplitude' import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager' import { useFeaturesStore } from '@/app/components/base/features/hooks' @@ -22,7 +23,6 @@ import { useWorkflowStore } from '@/app/components/workflow/store' import { WorkflowRunningStatus } from '@/app/components/workflow/types' import { handleStream, post, ssePost } from '@/service/base' import { ContentType } from '@/service/fetch' -import { useAppDetail } from '@/service/use-apps' import { useInvalidAllLastRun } from '@/service/use-workflow' import { stopWorkflowRun } from '@/service/workflow' import { AppModeEnum } from '@/types/app' @@ -64,7 +64,7 @@ export const useWorkflowRun = () => { const { handleUpdateWorkflowCanvas } = useWorkflowUpdate() const pathname = usePathname() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const configsMap = useConfigsMap() const { flowId, flowType } = configsMap const invalidAllLastRun = useInvalidAllLastRun(flowType, flowId) diff --git a/web/app/components/workflow-app/index.tsx b/web/app/components/workflow-app/index.tsx index 800ac07611..32cdfd6766 100644 --- a/web/app/components/workflow-app/index.tsx +++ b/web/app/components/workflow-app/index.tsx @@ -25,11 +25,11 @@ import { } from '@/app/components/workflow/utils' import { useAppContext } from '@/context/app-context' import { fetchRunDetail } from '@/service/log' -import { useAppDetail } from '@/service/use-apps' import { useAppTriggers } from '@/service/use-tools' import { AppModeEnum } from '@/types/app' -import WorkflowAppMain from './components/workflow-main' +import { appStoreSelectors, useAppStore } from '../app/store' +import WorkflowAppMain from './components/workflow-main' import { useGetRunAndTraceUrl } from './hooks/use-get-run-and-trace-url' import { useWorkflowInit, @@ -48,7 +48,7 @@ const WorkflowAppWithAdditionalContext = () => { // Initialize trigger status at application level const { setTriggerStatuses } = useTriggerStatusStore() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const isWorkflowMode = appDetail?.mode === AppModeEnum.WORKFLOW const { data: triggersResponse } = useAppTriggers(isWorkflowMode ? appId as string : undefined, { staleTime: 5 * 60 * 1000, // 5 minutes cache diff --git a/web/app/components/workflow/header/view-workflow-history.tsx b/web/app/components/workflow/header/view-workflow-history.tsx index 10c0e84c42..e0db2885b9 100644 --- a/web/app/components/workflow/header/view-workflow-history.tsx +++ b/web/app/components/workflow/header/view-workflow-history.tsx @@ -12,7 +12,7 @@ import { import { useTranslation } from 'react-i18next' import { useStoreApi } from 'reactflow' import { useShallow } from 'zustand/react/shallow' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index 2a9bfb218c..df047aa260 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -22,6 +22,7 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import { useEdges, useStoreApi } from 'reactflow' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { useToastContext } from '@/app/components/base/toast' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -29,7 +30,6 @@ import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { MAX_TREE_DEPTH } from '@/config' import { useGetLanguage } from '@/context/i18n' import { fetchDatasets } from '@/service/datasets' -import { useAppDetail } from '@/service/use-apps' import { useStrategyProviders } from '@/service/use-strategy' import { useAllBuiltInTools, @@ -98,7 +98,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { const datasetsDetail = useDatasetsDetailStore(s => s.datasetsDetail) const getToolIcon = useGetToolIcon() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const appMode = appDetail?.mode const shouldCheckStartNode = appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT @@ -274,7 +274,7 @@ export const useChecklistBeforePublish = () => { const { data: customTools } = useAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const appMode = appDetail?.mode const shouldCheckStartNode = appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index c2d0f3bdf6..4d464e3663 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -19,9 +19,9 @@ import { getOutgoers, useStoreApi, } from 'reactflow' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants' -import { useAppDetail } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' import { useNodesMetaData } from '.' import { @@ -45,7 +45,7 @@ import { useAvailableBlocks } from './use-available-blocks' export const useIsChatMode = () => { const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) return appDetail?.mode === AppModeEnum.ADVANCED_CHAT } diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index 3f8aa7fdc3..412bf164a8 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -20,7 +20,7 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' -import { useStore as useAppStore } from '@/app/components/app/store' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import Tooltip from '@/app/components/base/tooltip' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' @@ -61,7 +61,6 @@ import { isSupportCustomRunForm, } from '@/app/components/workflow/utils' import { useModalContext } from '@/context/modal-context' -import { useAppDetail } from '@/service/use-apps' import { useAllBuiltInTools } from '@/service/use-tools' import { useAllTriggerPlugins } from '@/service/use-triggers' import { FlowType } from '@/types/common' @@ -112,7 +111,7 @@ const BasePanel: FC = ({ const { t } = useTranslation() const language = useLanguage() const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const { showMessageLogModal } = useAppStore(useShallow(state => ({ showMessageLogModal: state.showMessageLogModal, }))) diff --git a/web/app/components/workflow/operator/more-actions.tsx b/web/app/components/workflow/operator/more-actions.tsx index e9fc1ea87d..94c6b96f2e 100644 --- a/web/app/components/workflow/operator/more-actions.tsx +++ b/web/app/components/workflow/operator/more-actions.tsx @@ -10,7 +10,7 @@ import { import { useTranslation } from 'react-i18next' import { getNodesBounds, useReactFlow } from 'reactflow' import { useShallow } from 'zustand/react/shallow' -import { useStore as useAppStore } from '@/app/components/app/store' +import { useAppStore } from '@/app/components/app/store' import ImagePreview from '@/app/components/base/image-uploader/image-preview' import { PortalToFollowElem, diff --git a/web/app/components/workflow/panel/chat-record/index.tsx b/web/app/components/workflow/panel/chat-record/index.tsx index f66460aafa..59f4e06677 100644 --- a/web/app/components/workflow/panel/chat-record/index.tsx +++ b/web/app/components/workflow/panel/chat-record/index.tsx @@ -8,12 +8,12 @@ import { useEffect, useState, } from 'react' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Chat from '@/app/components/base/chat/chat' import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils' import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' import Loading from '@/app/components/base/loading' import { fetchConversationMessages } from '@/service/debug' -import { useAppDetail } from '@/service/use-apps' import { useWorkflowRun } from '../../hooks' import { useStore, @@ -53,7 +53,7 @@ const ChatRecord = () => { const [chatItemTree, setChatItemTree] = useState([]) const [threadChatItems, setThreadChatItems] = useState([]) const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const workflowStore = useWorkflowStore() const { handleLoadBackupDraft } = useWorkflowRun() const historyWorkflowData = useStore(s => s.historyWorkflowData) diff --git a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx index 72529e44b0..e3316388ed 100644 --- a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx @@ -5,6 +5,7 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types' import { useParams } from 'next/navigation' import { memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react' import { useNodes } from 'reactflow' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Chat from '@/app/components/base/chat/chat' import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils' import { useFeatures } from '@/app/components/base/features/hooks' @@ -14,7 +15,6 @@ import { fetchSuggestedQuestions, stopChatMessageResponding, } from '@/service/debug' -import { useAppDetail } from '@/service/use-apps' import { useStore, useWorkflowStore, @@ -47,7 +47,7 @@ const ChatWrapper = ( const startNode = nodes.find(node => node.data.type === BlockEnum.Start) const startVariables = startNode?.data.variables const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const workflowStore = useWorkflowStore() const inputs = useStore(s => s.inputs) const setInputs = useStore(s => s.setInputs) diff --git a/web/app/components/workflow/update-dsl-modal.tsx b/web/app/components/workflow/update-dsl-modal.tsx index 07cb3f56ae..ed78cccb5b 100644 --- a/web/app/components/workflow/update-dsl-modal.tsx +++ b/web/app/components/workflow/update-dsl-modal.tsx @@ -21,6 +21,7 @@ import { import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Uploader from '@/app/components/app/create-from-dsl-modal/uploader' +import { appStoreSelectors, useAppStore } from '@/app/components/app/store' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' @@ -35,7 +36,6 @@ import { importDSL, importDSLConfirm, } from '@/service/apps' -import { useAppDetail } from '@/service/use-apps' import { fetchWorkflowDraft } from '@/service/workflow' import { AppModeEnum } from '@/types/app' import { WORKFLOW_DATA_UPDATE } from './constants' @@ -62,7 +62,7 @@ const UpdateDSLModal = ({ const { t } = useTranslation() const { notify } = useContext(ToastContext) const { appId } = useParams() - const { data: appDetail } = useAppDetail(appId as string) + const appDetail = useAppStore(appStoreSelectors.appDetails(appId as string)) const [currentFile, setDSLFile] = useState() const [fileContent, setFileContent] = useState() const [loading, setLoading] = useState(false) diff --git a/web/service/use-apps.ts b/web/service/use-apps.ts index eb6f212e42..2b82d87382 100644 --- a/web/service/use-apps.ts +++ b/web/service/use-apps.ts @@ -17,6 +17,7 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query' +import { appStoreActions } from '@/app/components/app/store' import { AppModeEnum } from '@/types/app' import { get, post } from './base' import { useInvalid } from './use-base' @@ -86,10 +87,10 @@ export const useGenerateRuleTemplate = (type: GeneratorType, disabled?: boolean) }) } -export const useAppDetail = (appID: string) => { +export const usePrefetchAppDetail = (appID: string) => { return useQuery({ queryKey: [NAME_SPACE, 'detail', appID], - queryFn: () => get(`/apps/${appID}`), + queryFn: () => appStoreActions.fetchAppDetail(appID) as Promise, enabled: !!appID, }) }