Browse Source

fix: toggling AppDetailNav causes unnecessary component rerenders (#3718)

legao 1 year ago
parent
commit
40e36e9b52

+ 6 - 1
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx

@@ -5,6 +5,7 @@ import React, { useCallback, useEffect, useState } from 'react'
 import { usePathname, useRouter } from 'next/navigation'
 import { usePathname, useRouter } from 'next/navigation'
 import cn from 'classnames'
 import cn from 'classnames'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
+import { useShallow } from 'zustand/react/shallow'
 import s from './style.module.css'
 import s from './style.module.css'
 import { useStore } from '@/app/components/app/store'
 import { useStore } from '@/app/components/app/store'
 import AppSideBar from '@/app/components/app-sidebar'
 import AppSideBar from '@/app/components/app-sidebar'
@@ -32,7 +33,11 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
   const media = useBreakpoints()
   const media = useBreakpoints()
   const isMobile = media === MediaType.mobile
   const isMobile = media === MediaType.mobile
   const { isCurrentWorkspaceManager } = useAppContext()
   const { isCurrentWorkspaceManager } = useAppContext()
-  const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore()
+  const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({
+    appDetail: state.appDetail,
+    setAppDetail: state.setAppDetail,
+    setAppSiderbarExpand: state.setAppSiderbarExpand,
+  })))
   const [navigation, setNavigation] = useState<Array<{
   const [navigation, setNavigation] = useState<Array<{
     name: string
     name: string
     href: string
     href: string

+ 2 - 1
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx

@@ -26,7 +26,8 @@ export type ICardViewProps = {
 const CardView: FC<ICardViewProps> = ({ appId }) => {
 const CardView: FC<ICardViewProps> = ({ appId }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { notify } = useContext(ToastContext)
   const { notify } = useContext(ToastContext)
-  const { appDetail, setAppDetail } = useAppStore()
+  const appDetail = useAppStore(state => state.appDetail)
+  const setAppDetail = useAppStore(state => state.setAppDetail)
 
 
   const updateAppDetail = async () => {
   const updateAppDetail = async () => {
     fetchAppDetail({ url: '/apps', id: appId }).then((res) => {
     fetchAppDetail({ url: '/apps', id: appId }).then((res) => {

+ 1 - 1
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx

@@ -22,7 +22,7 @@ export type IChartViewProps = {
 
 
 export default function ChartView({ appId }: IChartViewProps) {
 export default function ChartView({ appId }: IChartViewProps) {
   const { t } = useTranslation()
   const { t } = useTranslation()
-  const { appDetail } = useAppStore()
+  const appDetail = useAppStore(state => state.appDetail)
   const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow'
   const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow'
   const isWorkflow = appDetail?.mode === 'workflow'
   const isWorkflow = appDetail?.mode === 'workflow'
   const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } })
   const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } })

+ 248 - 248
web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx

@@ -1,248 +1,248 @@
-'use client'
+'use client'
-import type { FC, SVGProps } from 'react'
+import type { FC, SVGProps } from 'react'
-import React, { useEffect } from 'react'
+import React, { useEffect } from 'react'
-import { usePathname } from 'next/navigation'
+import { usePathname } from 'next/navigation'
-import useSWR from 'swr'
+import useSWR from 'swr'
-import { useTranslation } from 'react-i18next'
+import { useTranslation } from 'react-i18next'
-import classNames from 'classnames'
+import classNames from 'classnames'
-import { useBoolean } from 'ahooks'
+import { useBoolean } from 'ahooks'
-import {
+import {
-  Cog8ToothIcon,
+  Cog8ToothIcon,
-  // CommandLineIcon,
+  // CommandLineIcon,
-  Squares2X2Icon,
+  Squares2X2Icon,
-  // eslint-disable-next-line sort-imports
+  // eslint-disable-next-line sort-imports
-  PuzzlePieceIcon,
+  PuzzlePieceIcon,
-  DocumentTextIcon,
+  DocumentTextIcon,
-  PaperClipIcon,
+  PaperClipIcon,
-  QuestionMarkCircleIcon,
+  QuestionMarkCircleIcon,
-} from '@heroicons/react/24/outline'
+} from '@heroicons/react/24/outline'
-import {
+import {
-  Cog8ToothIcon as Cog8ToothSolidIcon,
+  Cog8ToothIcon as Cog8ToothSolidIcon,
-  // CommandLineIcon as CommandLineSolidIcon,
+  // CommandLineIcon as CommandLineSolidIcon,
-  DocumentTextIcon as DocumentTextSolidIcon,
+  DocumentTextIcon as DocumentTextSolidIcon,
-} from '@heroicons/react/24/solid'
+} from '@heroicons/react/24/solid'
-import Link from 'next/link'
+import Link from 'next/link'
-import s from './style.module.css'
+import s from './style.module.css'
-import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
+import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
-import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
+import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
-import { getLocaleOnClient } from '@/i18n'
+import { getLocaleOnClient } from '@/i18n'
-import AppSideBar from '@/app/components/app-sidebar'
+import AppSideBar from '@/app/components/app-sidebar'
-import Divider from '@/app/components/base/divider'
+import Divider from '@/app/components/base/divider'
-import AppIcon from '@/app/components/base/app-icon'
+import AppIcon from '@/app/components/base/app-icon'
-import Loading from '@/app/components/base/loading'
+import Loading from '@/app/components/base/loading'
-import FloatPopoverContainer from '@/app/components/base/float-popover-container'
+import FloatPopoverContainer from '@/app/components/base/float-popover-container'
-import DatasetDetailContext from '@/context/dataset-detail'
+import DatasetDetailContext from '@/context/dataset-detail'
-import { DataSourceType } from '@/models/datasets'
+import { DataSourceType } from '@/models/datasets'
-import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
+import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
-import { LanguagesSupported } from '@/i18n/language'
+import { LanguagesSupported } from '@/i18n/language'
-import { useStore } from '@/app/components/app/store'
+import { useStore } from '@/app/components/app/store'
-import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
+import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
-import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
+import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
-
+
-export type IAppDetailLayoutProps = {
+export type IAppDetailLayoutProps = {
-  children: React.ReactNode
+  children: React.ReactNode
-  params: { datasetId: string }
+  params: { datasetId: string }
-}
+}
-
+
-type ILikedItemProps = {
+type ILikedItemProps = {
-  type?: 'plugin' | 'app'
+  type?: 'plugin' | 'app'
-  appStatus?: boolean
+  appStatus?: boolean
-  detail: RelatedApp
+  detail: RelatedApp
-  isMobile: boolean
+  isMobile: boolean
-}
+}
-
+
-const LikedItem = ({
+const LikedItem = ({
-  type = 'app',
+  type = 'app',
-  detail,
+  detail,
-  isMobile,
+  isMobile,
-}: ILikedItemProps) => {
+}: ILikedItemProps) => {
-  return (
+  return (
-    <Link className={classNames(s.itemWrapper, 'px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
+    <Link className={classNames(s.itemWrapper, 'px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
-      <div className={classNames(s.iconWrapper, 'mr-0')}>
+      <div className={classNames(s.iconWrapper, 'mr-0')}>
-        <AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background} />
+        <AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background} />
-        {type === 'app' && (
+        {type === 'app' && (
-          <span className='absolute bottom-[-2px] right-[-2px] w-3.5 h-3.5 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
+          <span className='absolute bottom-[-2px] right-[-2px] w-3.5 h-3.5 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
-            {detail.mode === 'advanced-chat' && (
+            {detail.mode === 'advanced-chat' && (
-              <ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
+              <ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
-            )}
+            )}
-            {detail.mode === 'agent-chat' && (
+            {detail.mode === 'agent-chat' && (
-              <CuteRobote className='w-2.5 h-2.5 text-indigo-600' />
+              <CuteRobote className='w-2.5 h-2.5 text-indigo-600' />
-            )}
+            )}
-            {detail.mode === 'chat' && (
+            {detail.mode === 'chat' && (
-              <ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
+              <ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
-            )}
+            )}
-            {detail.mode === 'completion' && (
+            {detail.mode === 'completion' && (
-              <AiText className='w-2.5 h-2.5 text-[#0E9384]' />
+              <AiText className='w-2.5 h-2.5 text-[#0E9384]' />
-            )}
+            )}
-            {detail.mode === 'workflow' && (
+            {detail.mode === 'workflow' && (
-              <Route className='w-2.5 h-2.5 text-[#f79009]' />
+              <Route className='w-2.5 h-2.5 text-[#f79009]' />
-            )}
+            )}
-          </span>
+          </span>
-        )}
+        )}
-      </div>
+      </div>
-      {!isMobile && <div className={classNames(s.appInfo, 'ml-2')}>{detail?.name || '--'}</div>}
+      {!isMobile && <div className={classNames(s.appInfo, 'ml-2')}>{detail?.name || '--'}</div>}
-    </Link>
+    </Link>
-  )
+  )
-}
+}
-
+
-const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
+const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
-  return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
+  return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
-    <g clipPath="url(#clip0_4610_6951)">
+    <g clipPath="url(#clip0_4610_6951)">
-      <path d="M10.6666 5.33325V3.33325L12.6666 1.33325L13.3332 2.66659L14.6666 3.33325L12.6666 5.33325H10.6666ZM10.6666 5.33325L7.9999 7.99988M14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325M11.3333 7.99992C11.3333 9.84087 9.84087 11.3333 7.99992 11.3333C6.15897 11.3333 4.66659 9.84087 4.66659 7.99992C4.66659 6.15897 6.15897 4.66659 7.99992 4.66659" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
+      <path d="M10.6666 5.33325V3.33325L12.6666 1.33325L13.3332 2.66659L14.6666 3.33325L12.6666 5.33325H10.6666ZM10.6666 5.33325L7.9999 7.99988M14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325M11.3333 7.99992C11.3333 9.84087 9.84087 11.3333 7.99992 11.3333C6.15897 11.3333 4.66659 9.84087 4.66659 7.99992C4.66659 6.15897 6.15897 4.66659 7.99992 4.66659" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
-    </g>
+    </g>
-    <defs>
+    <defs>
-      <clipPath id="clip0_4610_6951">
+      <clipPath id="clip0_4610_6951">
-        <rect width="16" height="16" fill="white" />
+        <rect width="16" height="16" fill="white" />
-      </clipPath>
+      </clipPath>
-    </defs>
+    </defs>
-  </svg>
+  </svg>
-}
+}
-
+
-const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => {
+const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => {
-  return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
+  return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
-    <path fillRule="evenodd" clipRule="evenodd" d="M12.7733 0.67512C12.9848 0.709447 13.1669 0.843364 13.2627 1.03504L13.83 2.16961L14.9646 2.73689C15.1563 2.83273 15.2902 3.01486 15.3245 3.22639C15.3588 3.43792 15.2894 3.65305 15.1379 3.80458L13.1379 5.80458C13.0128 5.92961 12.8433 5.99985 12.6665 5.99985H10.9426L8.47124 8.47124C8.21089 8.73159 7.78878 8.73159 7.52843 8.47124C7.26808 8.21089 7.26808 7.78878 7.52843 7.52843L9.9998 5.05707V3.33318C9.9998 3.15637 10.07 2.9868 10.1951 2.86177L12.1951 0.861774C12.3466 0.710244 12.5617 0.640794 12.7733 0.67512Z" fill="#155EEF" />
+    <path fillRule="evenodd" clipRule="evenodd" d="M12.7733 0.67512C12.9848 0.709447 13.1669 0.843364 13.2627 1.03504L13.83 2.16961L14.9646 2.73689C15.1563 2.83273 15.2902 3.01486 15.3245 3.22639C15.3588 3.43792 15.2894 3.65305 15.1379 3.80458L13.1379 5.80458C13.0128 5.92961 12.8433 5.99985 12.6665 5.99985H10.9426L8.47124 8.47124C8.21089 8.73159 7.78878 8.73159 7.52843 8.47124C7.26808 8.21089 7.26808 7.78878 7.52843 7.52843L9.9998 5.05707V3.33318C9.9998 3.15637 10.07 2.9868 10.1951 2.86177L12.1951 0.861774C12.3466 0.710244 12.5617 0.640794 12.7733 0.67512Z" fill="#155EEF" />
-    <path d="M1.99984 7.99984C1.99984 4.68613 4.68613 1.99984 7.99984 1.99984C8.36803 1.99984 8.6665 1.70136 8.6665 1.33317C8.6665 0.964981 8.36803 0.666504 7.99984 0.666504C3.94975 0.666504 0.666504 3.94975 0.666504 7.99984C0.666504 12.0499 3.94975 15.3332 7.99984 15.3332C12.0499 15.3332 15.3332 12.0499 15.3332 7.99984C15.3332 7.63165 15.0347 7.33317 14.6665 7.33317C14.2983 7.33317 13.9998 7.63165 13.9998 7.99984C13.9998 11.3135 11.3135 13.9998 7.99984 13.9998C4.68613 13.9998 1.99984 11.3135 1.99984 7.99984Z" fill="#155EEF" />
+    <path d="M1.99984 7.99984C1.99984 4.68613 4.68613 1.99984 7.99984 1.99984C8.36803 1.99984 8.6665 1.70136 8.6665 1.33317C8.6665 0.964981 8.36803 0.666504 7.99984 0.666504C3.94975 0.666504 0.666504 3.94975 0.666504 7.99984C0.666504 12.0499 3.94975 15.3332 7.99984 15.3332C12.0499 15.3332 15.3332 12.0499 15.3332 7.99984C15.3332 7.63165 15.0347 7.33317 14.6665 7.33317C14.2983 7.33317 13.9998 7.63165 13.9998 7.99984C13.9998 11.3135 11.3135 13.9998 7.99984 13.9998C4.68613 13.9998 1.99984 11.3135 1.99984 7.99984Z" fill="#155EEF" />
-    <path d="M5.33317 7.99984C5.33317 6.52708 6.52708 5.33317 7.99984 5.33317C8.36803 5.33317 8.6665 5.03469 8.6665 4.6665C8.6665 4.29831 8.36803 3.99984 7.99984 3.99984C5.7907 3.99984 3.99984 5.7907 3.99984 7.99984C3.99984 10.209 5.7907 11.9998 7.99984 11.9998C10.209 11.9998 11.9998 10.209 11.9998 7.99984C11.9998 7.63165 11.7014 7.33317 11.3332 7.33317C10.965 7.33317 10.6665 7.63165 10.6665 7.99984C10.6665 9.4726 9.4726 10.6665 7.99984 10.6665C6.52708 10.6665 5.33317 9.4726 5.33317 7.99984Z" fill="#155EEF" />
+    <path d="M5.33317 7.99984C5.33317 6.52708 6.52708 5.33317 7.99984 5.33317C8.36803 5.33317 8.6665 5.03469 8.6665 4.6665C8.6665 4.29831 8.36803 3.99984 7.99984 3.99984C5.7907 3.99984 3.99984 5.7907 3.99984 7.99984C3.99984 10.209 5.7907 11.9998 7.99984 11.9998C10.209 11.9998 11.9998 10.209 11.9998 7.99984C11.9998 7.63165 11.7014 7.33317 11.3332 7.33317C10.965 7.33317 10.6665 7.63165 10.6665 7.99984C10.6665 9.4726 9.4726 10.6665 7.99984 10.6665C6.52708 10.6665 5.33317 9.4726 5.33317 7.99984Z" fill="#155EEF" />
-  </svg>
+  </svg>
-}
+}
-
+
-const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
+const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
-  return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
+  return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
-    <path opacity="0.12" d="M1 3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7V10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1Z" fill="#155EEF" />
+    <path opacity="0.12" d="M1 3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7V10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1Z" fill="#155EEF" />
-    <path d="M6 10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7M6 10.5V4.7M6 10.5L6.05003 10.425C6.39735 9.90398 6.57101 9.64349 6.80045 9.45491C7.00357 9.28796 7.23762 9.1627 7.4892 9.0863C7.77337 9 8.08645 9 8.71259 9H9.4C9.96005 9 10.2401 9 10.454 8.89101C10.6422 8.79513 10.7951 8.64215 10.891 8.45399C11 8.24008 11 7.96005 11 7.4V3.1C11 2.53995 11 2.25992 10.891 2.04601C10.7951 1.85785 10.6422 1.70487 10.454 1.60899C10.2401 1.5 9.96005 1.5 9.4 1.5H9.2C8.07989 1.5 7.51984 1.5 7.09202 1.71799C6.71569 1.90973 6.40973 2.21569 6.21799 2.59202C6 3.01984 6 3.5799 6 4.7" stroke="#155EEF" strokeLinecap="round" strokeLinejoin="round" />
+    <path d="M6 10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7M6 10.5V4.7M6 10.5L6.05003 10.425C6.39735 9.90398 6.57101 9.64349 6.80045 9.45491C7.00357 9.28796 7.23762 9.1627 7.4892 9.0863C7.77337 9 8.08645 9 8.71259 9H9.4C9.96005 9 10.2401 9 10.454 8.89101C10.6422 8.79513 10.7951 8.64215 10.891 8.45399C11 8.24008 11 7.96005 11 7.4V3.1C11 2.53995 11 2.25992 10.891 2.04601C10.7951 1.85785 10.6422 1.70487 10.454 1.60899C10.2401 1.5 9.96005 1.5 9.4 1.5H9.2C8.07989 1.5 7.51984 1.5 7.09202 1.71799C6.71569 1.90973 6.40973 2.21569 6.21799 2.59202C6 3.01984 6 3.5799 6 4.7" stroke="#155EEF" strokeLinecap="round" strokeLinejoin="round" />
-  </svg>
+  </svg>
-}
+}
-
+
-type IExtraInfoProps = {
+type IExtraInfoProps = {
-  isMobile: boolean
+  isMobile: boolean
-  relatedApps?: RelatedAppResponse
+  relatedApps?: RelatedAppResponse
-}
+}
-
+
-const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
+const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
-  const locale = getLocaleOnClient()
+  const locale = getLocaleOnClient()
-  const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
+  const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
-  const { t } = useTranslation()
+  const { t } = useTranslation()
-
+
-  useEffect(() => {
+  useEffect(() => {
-    setShowTips(!isMobile)
+    setShowTips(!isMobile)
-  }, [isMobile, setShowTips])
+  }, [isMobile, setShowTips])
-
+
-  return <div className='w-full flex flex-col items-center'>
+  return <div className='w-full flex flex-col items-center'>
-    <Divider className='mt-5' />
+    <Divider className='mt-5' />
-    {(relatedApps?.data && relatedApps?.data?.length > 0) && (
+    {(relatedApps?.data && relatedApps?.data?.length > 0) && (
-      <>
+      <>
-        {!isMobile && <div className='w-full px-2 pb-1 pt-4 uppercase text-xs text-gray-500 font-medium'>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>}
+        {!isMobile && <div className='w-full px-2 pb-1 pt-4 uppercase text-xs text-gray-500 font-medium'>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>}
-        {isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
+        {isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
-          {relatedApps?.total || '--'}
+          {relatedApps?.total || '--'}
-          <PaperClipIcon className='h-4 w-4 text-gray-700' />
+          <PaperClipIcon className='h-4 w-4 text-gray-700' />
-        </div>}
+        </div>}
-        {relatedApps?.data?.map((item, index) => (<LikedItem key={index} isMobile={isMobile} detail={item} />))}
+        {relatedApps?.data?.map((item, index) => (<LikedItem key={index} isMobile={isMobile} detail={item} />))}
-      </>
+      </>
-    )}
+    )}
-    {!relatedApps?.data?.length && (
+    {!relatedApps?.data?.length && (
-      <FloatPopoverContainer
+      <FloatPopoverContainer
-        placement='bottom-start'
+        placement='bottom-start'
-        open={isShowTips}
+        open={isShowTips}
-        toggle={toggleTips}
+        toggle={toggleTips}
-        isMobile={isMobile}
+        isMobile={isMobile}
-        triggerElement={
+        triggerElement={
-          <div className={classNames('h-7 w-7 inline-flex justify-center items-center rounded-lg bg-transparent', isShowTips && '!bg-gray-50')}>
+          <div className={classNames('h-7 w-7 inline-flex justify-center items-center rounded-lg bg-transparent', isShowTips && '!bg-gray-50')}>
-            <QuestionMarkCircleIcon className='h-4 w-4 flex-shrink-0 text-gray-500' />
+            <QuestionMarkCircleIcon className='h-4 w-4 flex-shrink-0 text-gray-500' />
-          </div>
+          </div>
-        }
+        }
-      >
+      >
-        <div className={classNames('mt-5 p-3', isMobile && 'border-[0.5px] border-gray-200 shadow-lg rounded-lg bg-white w-[160px]')}>
+        <div className={classNames('mt-5 p-3', isMobile && 'border-[0.5px] border-gray-200 shadow-lg rounded-lg bg-white w-[160px]')}>
-          <div className='flex items-center justify-start gap-2'>
+          <div className='flex items-center justify-start gap-2'>
-            <div className={s.emptyIconDiv}>
+            <div className={s.emptyIconDiv}>
-              <Squares2X2Icon className='w-3 h-3 text-gray-500' />
+              <Squares2X2Icon className='w-3 h-3 text-gray-500' />
-            </div>
+            </div>
-            <div className={s.emptyIconDiv}>
+            <div className={s.emptyIconDiv}>
-              <PuzzlePieceIcon className='w-3 h-3 text-gray-500' />
+              <PuzzlePieceIcon className='w-3 h-3 text-gray-500' />
-            </div>
+            </div>
-          </div>
+          </div>
-          <div className='text-xs text-gray-500 mt-2'>{t('common.datasetMenus.emptyTip')}</div>
+          <div className='text-xs text-gray-500 mt-2'>{t('common.datasetMenus.emptyTip')}</div>
-          <a
+          <a
-            className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
+            className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
-            href={
+            href={
-              locale === LanguagesSupported[1]
+              locale === LanguagesSupported[1]
-                ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
+                ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
-                : 'https://docs.dify.ai/user-guide/creating-dify-apps/prompt-engineering'
+                : 'https://docs.dify.ai/user-guide/creating-dify-apps/prompt-engineering'
-            }
+            }
-            target='_blank' rel='noopener noreferrer'
+            target='_blank' rel='noopener noreferrer'
-          >
+          >
-            <BookOpenIcon className='mr-1' />
+            <BookOpenIcon className='mr-1' />
-            {t('common.datasetMenus.viewDoc')}
+            {t('common.datasetMenus.viewDoc')}
-          </a>
+          </a>
-        </div>
+        </div>
-      </FloatPopoverContainer>
+      </FloatPopoverContainer>
-    )}
+    )}
-  </div>
+  </div>
-}
+}
-
+
-const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
+const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
-  const {
+  const {
-    children,
+    children,
-    params: { datasetId },
+    params: { datasetId },
-  } = props
+  } = props
-  const pathname = usePathname()
+  const pathname = usePathname()
-  const hideSideBar = /documents\/create$/.test(pathname)
+  const hideSideBar = /documents\/create$/.test(pathname)
-  const { t } = useTranslation()
+  const { t } = useTranslation()
-
+
-  const media = useBreakpoints()
+  const media = useBreakpoints()
-  const isMobile = media === MediaType.mobile
+  const isMobile = media === MediaType.mobile
-
+
-  const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
+  const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
-    url: 'fetchDatasetDetail',
+    url: 'fetchDatasetDetail',
-    datasetId,
+    datasetId,
-  }, apiParams => fetchDatasetDetail(apiParams.datasetId))
+  }, apiParams => fetchDatasetDetail(apiParams.datasetId))
-
+
-  const { data: relatedApps } = useSWR({
+  const { data: relatedApps } = useSWR({
-    action: 'fetchDatasetRelatedApps',
+    action: 'fetchDatasetRelatedApps',
-    datasetId,
+    datasetId,
-  }, apiParams => fetchDatasetRelatedApps(apiParams.datasetId))
+  }, apiParams => fetchDatasetRelatedApps(apiParams.datasetId))
-
+
-  const navigation = [
+  const navigation = [
-    { name: t('common.datasetMenus.documents'), href: `/datasets/${datasetId}/documents`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
+    { name: t('common.datasetMenus.documents'), href: `/datasets/${datasetId}/documents`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
-    { name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
+    { name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
-    // { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
+    // { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
-    { name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
+    { name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
-  ]
+  ]
-
+
-  useEffect(() => {
+  useEffect(() => {
-    if (datasetRes)
+    if (datasetRes)
-      document.title = `${datasetRes.name || 'Dataset'} - Dify`
+      document.title = `${datasetRes.name || 'Dataset'} - Dify`
-  }, [datasetRes])
+  }, [datasetRes])
-
+
-  const { setAppSiderbarExpand } = useStore()
+  const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
-
+
-  useEffect(() => {
+  useEffect(() => {
-    const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
+    const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
-    const mode = isMobile ? 'collapse' : 'expand'
+    const mode = isMobile ? 'collapse' : 'expand'
-    setAppSiderbarExpand(isMobile ? mode : localeMode)
+    setAppSiderbarExpand(isMobile ? mode : localeMode)
-  }, [isMobile, setAppSiderbarExpand])
+  }, [isMobile, setAppSiderbarExpand])
-
+
-  if (!datasetRes && !error)
+  if (!datasetRes && !error)
-    return <Loading />
+    return <Loading />
-
+
-  return (
+  return (
-    <div className='grow flex overflow-hidden'>
+    <div className='grow flex overflow-hidden'>
-      {!hideSideBar && <AppSideBar
+      {!hideSideBar && <AppSideBar
-        title={datasetRes?.name || '--'}
+        title={datasetRes?.name || '--'}
-        icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
+        icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
-        icon_background={datasetRes?.icon_background || '#F5F5F5'}
+        icon_background={datasetRes?.icon_background || '#F5F5F5'}
-        desc={datasetRes?.description || '--'}
+        desc={datasetRes?.description || '--'}
-        navigation={navigation}
+        navigation={navigation}
-        extraInfo={mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} />}
+        extraInfo={mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} />}
-        iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
+        iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
-      />}
+      />}
-      <DatasetDetailContext.Provider value={{
+      <DatasetDetailContext.Provider value={{
-        indexingTechnique: datasetRes?.indexing_technique,
+        indexingTechnique: datasetRes?.indexing_technique,
-        dataset: datasetRes,
+        dataset: datasetRes,
-        mutateDatasetRes: () => mutateDatasetRes(),
+        mutateDatasetRes: () => mutateDatasetRes(),
-      }}>
+      }}>
-        <div className="bg-white grow overflow-hidden">{children}</div>
+        <div className="bg-white grow overflow-hidden">{children}</div>
-      </DatasetDetailContext.Provider>
+      </DatasetDetailContext.Provider>
-    </div>
+    </div>
-  )
+  )
-}
+}
-export default React.memo(DatasetDetailLayout)
+export default React.memo(DatasetDetailLayout)

+ 13 - 10
web/app/components/app-sidebar/index.tsx

@@ -1,4 +1,5 @@
-import React, { useEffect, useState } from 'react'
+import React, { useEffect } from 'react'
+import { useShallow } from 'zustand/react/shallow'
 import NavLink from './navLink'
 import NavLink from './navLink'
 import type { NavIcon } from './navLink'
 import type { NavIcon } from './navLink'
 import AppBasic from './basic'
 import AppBasic from './basic'
@@ -26,11 +27,13 @@ export type IAppDetailNavProps = {
 }
 }
 
 
 const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInfo, iconType = 'app' }: IAppDetailNavProps) => {
 const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInfo, iconType = 'app' }: IAppDetailNavProps) => {
-  const { appSidebarExpand, setAppSiderbarExpand } = useAppStore()
+  const { appSidebarExpand, setAppSiderbarExpand } = useAppStore(useShallow(state => ({
+    appSidebarExpand: state.appSidebarExpand,
+    setAppSiderbarExpand: state.setAppSiderbarExpand,
+  })))
   const media = useBreakpoints()
   const media = useBreakpoints()
   const isMobile = media === MediaType.mobile
   const isMobile = media === MediaType.mobile
-  const [modeState, setModeState] = useState(appSidebarExpand)
+  const expand = appSidebarExpand === 'expand'
-  const expand = modeState === 'expand'
 
 
   const handleToggle = (state: string) => {
   const handleToggle = (state: string) => {
     setAppSiderbarExpand(state === 'expand' ? 'collapse' : 'expand')
     setAppSiderbarExpand(state === 'expand' ? 'collapse' : 'expand')
@@ -39,9 +42,9 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
   useEffect(() => {
   useEffect(() => {
     if (appSidebarExpand) {
     if (appSidebarExpand) {
       localStorage.setItem('app-detail-collapse-or-expand', appSidebarExpand)
       localStorage.setItem('app-detail-collapse-or-expand', appSidebarExpand)
-      setModeState(appSidebarExpand)
+      setAppSiderbarExpand(appSidebarExpand)
     }
     }
-  }, [appSidebarExpand])
+  }, [appSidebarExpand, setAppSiderbarExpand])
 
 
   return (
   return (
     <div
     <div
@@ -61,7 +64,7 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
         )}
         )}
         {iconType !== 'app' && (
         {iconType !== 'app' && (
           <AppBasic
           <AppBasic
-            mode={modeState}
+            mode={appSidebarExpand}
             iconType={iconType}
             iconType={iconType}
             icon={icon}
             icon={icon}
             icon_background={icon_background}
             icon_background={icon_background}
@@ -81,10 +84,10 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
       >
       >
         {navigation.map((item, index) => {
         {navigation.map((item, index) => {
           return (
           return (
-            <NavLink key={index} mode={modeState} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
+            <NavLink key={index} mode={appSidebarExpand} iconMap={{ selected: item.selectedIcon, normal: item.icon }} name={item.name} href={item.href} />
           )
           )
         })}
         })}
-        {extraInfo && extraInfo(modeState)}
+        {extraInfo && extraInfo(appSidebarExpand)}
       </nav>
       </nav>
       {
       {
         !isMobile && (
         !isMobile && (
@@ -96,7 +99,7 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
           >
           >
             <div
             <div
               className='flex items-center justify-center w-6 h-6 text-gray-500 cursor-pointer'
               className='flex items-center justify-center w-6 h-6 text-gray-500 cursor-pointer'
-              onClick={() => handleToggle(modeState)}
+              onClick={() => handleToggle(appSidebarExpand)}
             >
             >
               {
               {
                 expand
                 expand

+ 7 - 1
web/app/components/app/configuration/debug/index.tsx

@@ -6,6 +6,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'
 import { setAutoFreeze } from 'immer'
 import { setAutoFreeze } from 'immer'
 import { useBoolean } from 'ahooks'
 import { useBoolean } from 'ahooks'
 import { useContext } from 'use-context-selector'
 import { useContext } from 'use-context-selector'
+import { useShallow } from 'zustand/react/shallow'
 import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api'
 import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api'
 import FormattingChanged from '../base/warning-mask/formatting-changed'
 import FormattingChanged from '../base/warning-mask/formatting-changed'
 import GroupName from '../base/group-name'
 import GroupName from '../base/group-name'
@@ -367,7 +368,12 @@ const Debug: FC<IDebug> = ({
     handleVisionConfigInMultipleModel()
     handleVisionConfigInMultipleModel()
   }, [multipleModelConfigs, mode])
   }, [multipleModelConfigs, mode])
 
 
-  const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal } = useAppStore()
+  const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal } = useAppStore(useShallow(state => ({
+    currentLogItem: state.currentLogItem,
+    setCurrentLogItem: state.setCurrentLogItem,
+    showPromptLogModal: state.showPromptLogModal,
+    setShowPromptLogModal: state.setShowPromptLogModal,
+  })))
   const [width, setWidth] = useState(0)
   const [width, setWidth] = useState(0)
   const ref = useRef<HTMLDivElement>(null)
   const ref = useRef<HTMLDivElement>(null)
 
 

+ 5 - 1
web/app/components/app/configuration/index.tsx

@@ -8,6 +8,7 @@ import produce from 'immer'
 import { useBoolean, useGetState } from 'ahooks'
 import { useBoolean, useGetState } from 'ahooks'
 import { clone, isEqual } from 'lodash-es'
 import { clone, isEqual } from 'lodash-es'
 import { CodeBracketIcon } from '@heroicons/react/20/solid'
 import { CodeBracketIcon } from '@heroicons/react/20/solid'
+import { useShallow } from 'zustand/react/shallow'
 import Button from '../../base/button'
 import Button from '../../base/button'
 import Loading from '../../base/loading'
 import Loading from '../../base/loading'
 import AppPublisher from '../app-publisher'
 import AppPublisher from '../app-publisher'
@@ -65,7 +66,10 @@ type PublishConfig = {
 const Configuration: FC = () => {
 const Configuration: FC = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { notify } = useContext(ToastContext)
   const { notify } = useContext(ToastContext)
-  const { appDetail, setAppSiderbarExpand } = useAppStore()
+  const { appDetail, setAppSiderbarExpand } = useAppStore(useShallow(state => ({
+    appDetail: state.appDetail,
+    setAppSiderbarExpand: state.setAppSiderbarExpand,
+  })))
   const [formattingChanged, setFormattingChanged] = useState(false)
   const [formattingChanged, setFormattingChanged] = useState(false)
   const { setShowAccountSettingModal } = useModalContext()
   const { setShowAccountSettingModal } = useModalContext()
   const [hasFetchedDetail, setHasFetchedDetail] = useState(false)
   const [hasFetchedDetail, setHasFetchedDetail] = useState(false)

+ 1 - 1
web/app/components/app/log-annotation/index.tsx

@@ -21,7 +21,7 @@ const LogAnnotation: FC<Props> = ({
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const router = useRouter()
   const router = useRouter()
-  const { appDetail } = useAppStore()
+  const appDetail = useAppStore(state => state.appDetail)
 
 
   const options = [
   const options = [
     { value: PageType.log, text: t('appLog.title') },
     { value: PageType.log, text: t('appLog.title') },

+ 9 - 1
web/app/components/base/chat/chat/index.tsx

@@ -12,6 +12,7 @@ import {
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import { debounce } from 'lodash-es'
 import { debounce } from 'lodash-es'
 import classNames from 'classnames'
 import classNames from 'classnames'
+import { useShallow } from 'zustand/react/shallow'
 import type {
 import type {
   ChatConfig,
   ChatConfig,
   ChatItem,
   ChatItem,
@@ -79,7 +80,14 @@ const Chat: FC<ChatProps> = ({
   chatAnswerContainerInner,
   chatAnswerContainerInner,
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
-  const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore()
+  const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({
+    currentLogItem: state.currentLogItem,
+    setCurrentLogItem: state.setCurrentLogItem,
+    showPromptLogModal: state.showPromptLogModal,
+    setShowPromptLogModal: state.setShowPromptLogModal,
+    showAgentLogModal: state.showAgentLogModal,
+    setShowAgentLogModal: state.setShowAgentLogModal,
+  })))
   const [width, setWidth] = useState(0)
   const [width, setWidth] = useState(0)
   const chatContainerRef = useRef<HTMLDivElement>(null)
   const chatContainerRef = useRef<HTMLDivElement>(null)
   const chatContainerInnerRef = useRef<HTMLDivElement>(null)
   const chatContainerInnerRef = useRef<HTMLDivElement>(null)

+ 1 - 1
web/app/components/develop/index.tsx

@@ -12,7 +12,7 @@ type IDevelopMainProps = {
 }
 }
 
 
 const DevelopMain = ({ appId }: IDevelopMainProps) => {
 const DevelopMain = ({ appId }: IDevelopMainProps) => {
-  const { appDetail } = useAppStore()
+  const appDetail = useAppStore(state => state.appDetail)
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   if (!appDetail) {
   if (!appDetail) {

+ 1 - 1
web/app/components/header/app-nav/index.tsx

@@ -40,7 +40,7 @@ const AppNav = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { appId } = useParams()
   const { appId } = useParams()
   const { isCurrentWorkspaceManager } = useAppContext()
   const { isCurrentWorkspaceManager } = useAppContext()
-  const { appDetail } = useAppStore()
+  const appDetail = useAppStore(state => state.appDetail)
   const [showNewAppDialog, setShowNewAppDialog] = useState(false)
   const [showNewAppDialog, setShowNewAppDialog] = useState(false)
   const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false)
   const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false)
   const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
   const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)

+ 1 - 1
web/app/components/header/nav/index.tsx

@@ -31,7 +31,7 @@ const Nav = ({
   onLoadmore,
   onLoadmore,
   isApp,
   isApp,
 }: INavProps) => {
 }: INavProps) => {
-  const { setAppDetail } = useAppStore()
+  const setAppDetail = useAppStore(state => state.setAppDetail)
   const [hovered, setHovered] = useState(false)
   const [hovered, setHovered] = useState(false)
   const segment = useSelectedLayoutSegment()
   const segment = useSelectedLayoutSegment()
   const isActived = Array.isArray(activeSegment) ? activeSegment.includes(segment!) : segment === activeSegment
   const isActived = Array.isArray(activeSegment) ? activeSegment.includes(segment!) : segment === activeSegment

+ 1 - 1
web/app/components/header/nav/nav-selector/index.tsx

@@ -35,7 +35,7 @@ const NavSelector = ({ curNav, navs, createText, isApp, onCreate, onLoadmore }:
   const { t } = useTranslation()
   const { t } = useTranslation()
   const router = useRouter()
   const router = useRouter()
   const { isCurrentWorkspaceManager } = useAppContext()
   const { isCurrentWorkspaceManager } = useAppContext()
-  const { setAppDetail } = useAppStore()
+  const setAppDetail = useAppStore(state => state.setAppDetail)
 
 
   const handleScroll = useCallback(debounce((e) => {
   const handleScroll = useCallback(debounce((e) => {
     if (typeof onLoadmore === 'function') {
     if (typeof onLoadmore === 'function') {

+ 1 - 1
web/app/components/workflow/header/index.tsx

@@ -33,7 +33,7 @@ const Header: FC = () => {
   const workflowStore = useWorkflowStore()
   const workflowStore = useWorkflowStore()
   const appDetail = useAppStore(s => s.appDetail)
   const appDetail = useAppStore(s => s.appDetail)
   const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
   const appSidebarExpand = useAppStore(s => s.appSidebarExpand)
-  const appID = useAppStore(state => state.appDetail?.id)
+  const appID = appDetail?.id
   const {
   const {
     nodesReadOnly,
     nodesReadOnly,
     getNodesReadOnly,
     getNodesReadOnly,

+ 6 - 1
web/app/components/workflow/header/view-history.tsx

@@ -5,6 +5,7 @@ import {
 import cn from 'classnames'
 import cn from 'classnames'
 import useSWR from 'swr'
 import useSWR from 'swr'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
+import { useShallow } from 'zustand/react/shallow'
 import {
 import {
   useIsChatMode,
   useIsChatMode,
   useWorkflow,
   useWorkflow,
@@ -40,7 +41,11 @@ const ViewHistory = () => {
   const [open, setOpen] = useState(false)
   const [open, setOpen] = useState(false)
   const { formatTimeFromNow } = useWorkflow()
   const { formatTimeFromNow } = useWorkflow()
   const workflowStore = useWorkflowStore()
   const workflowStore = useWorkflowStore()
-  const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore()
+  const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
+    appDetail: state.appDetail,
+    setCurrentLogItem: state.setCurrentLogItem,
+    setShowMessageLogModal: state.setShowMessageLogModal,
+  })))
   const historyWorkflowData = useStore(s => s.historyWorkflowData)
   const historyWorkflowData = useStore(s => s.historyWorkflowData)
   const { handleBackupDraft } = useWorkflowRun()
   const { handleBackupDraft } = useWorkflowRun()
   const { data: runList, isLoading: runListLoading } = useSWR((appDetail && !isChatMode && open) ? `/apps/${appDetail.id}/workflow-runs` : null, fetchWorkflowRunHistory)
   const { data: runList, isLoading: runListLoading } = useSWR((appDetail && !isChatMode && open) ? `/apps/${appDetail.id}/workflow-runs` : null, fetchWorkflowRunHistory)

+ 7 - 1
web/app/components/workflow/panel/index.tsx

@@ -4,6 +4,7 @@ import {
   useMemo,
   useMemo,
 } from 'react'
 } from 'react'
 import { useNodes } from 'reactflow'
 import { useNodes } from 'reactflow'
+import { useShallow } from 'zustand/react/shallow'
 import type { CommonNodeType } from '../types'
 import type { CommonNodeType } from '../types'
 import { Panel as NodePanel } from '../nodes'
 import { Panel as NodePanel } from '../nodes'
 import { useStore } from '../store'
 import { useStore } from '../store'
@@ -22,7 +23,12 @@ const Panel: FC = () => {
   const showInputsPanel = useStore(s => s.showInputsPanel)
   const showInputsPanel = useStore(s => s.showInputsPanel)
   const workflowRunningData = useStore(s => s.workflowRunningData)
   const workflowRunningData = useStore(s => s.workflowRunningData)
   const historyWorkflowData = useStore(s => s.historyWorkflowData)
   const historyWorkflowData = useStore(s => s.historyWorkflowData)
-  const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal } = useAppStore()
+  const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal } = useAppStore(useShallow(state => ({
+    currentLogItem: state.currentLogItem,
+    setCurrentLogItem: state.setCurrentLogItem,
+    showMessageLogModal: state.showMessageLogModal,
+    setShowMessageLogModal: state.setShowMessageLogModal,
+  })))
   const {
   const {
     showNodePanel,
     showNodePanel,
     showDebugAndPreviewPanel,
     showDebugAndPreviewPanel,

+ 1 - 1
web/app/components/workflow/run/index.tsx

@@ -25,7 +25,7 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { notify } = useContext(ToastContext)
   const { notify } = useContext(ToastContext)
   const [currentTab, setCurrentTab] = useState<string>(activeTab)
   const [currentTab, setCurrentTab] = useState<string>(activeTab)
-  const { appDetail } = useAppStore()
+  const appDetail = useAppStore(state => state.appDetail)
   const [loading, setLoading] = useState<boolean>(true)
   const [loading, setLoading] = useState<boolean>(true)
   const [runDetail, setRunDetail] = useState<WorkflowRunDetailResponse>()
   const [runDetail, setRunDetail] = useState<WorkflowRunDetailResponse>()
   const [list, setList] = useState<NodeTracing[]>([])
   const [list, setList] = useState<NodeTracing[]>([])

+ 3 - 3
web/app/components/workflow/store.ts

@@ -1,8 +1,8 @@
 import { useContext } from 'react'
 import { useContext } from 'react'
 import {
 import {
-  create,
   useStore as useZustandStore,
   useStore as useZustandStore,
 } from 'zustand'
 } from 'zustand'
+import { createStore } from 'zustand/vanilla'
 import { debounce } from 'lodash-es'
 import { debounce } from 'lodash-es'
 import type { Viewport } from 'reactflow'
 import type { Viewport } from 'reactflow'
 import type {
 import type {
@@ -70,9 +70,9 @@ type Shape = {
 }
 }
 
 
 export const createWorkflowStore = () => {
 export const createWorkflowStore = () => {
-  return create<Shape>(set => ({
+  return createStore<Shape>(set => ({
     appId: '',
     appId: '',
-    workflowData: undefined,
+    workflowRunningData: undefined,
     setWorkflowRunningData: workflowRunningData => set(() => ({ workflowRunningData })),
     setWorkflowRunningData: workflowRunningData => set(() => ({ workflowRunningData })),
     historyWorkflowData: undefined,
     historyWorkflowData: undefined,
     setHistoryWorkflowData: historyWorkflowData => set(() => ({ historyWorkflowData })),
     setHistoryWorkflowData: historyWorkflowData => set(() => ({ historyWorkflowData })),