diff --git a/web/app/components/app/create-app-dialog/app-card/index.tsx b/web/app/components/app/create-app-dialog/app-card/index.tsx
index 7f7ede0065..82f10a2d6f 100644
--- a/web/app/components/app/create-app-dialog/app-card/index.tsx
+++ b/web/app/components/app/create-app-dialog/app-card/index.tsx
@@ -6,6 +6,11 @@ import Button from '@/app/components/base/button'
import cn from '@/utils/classnames'
import type { App } from '@/models/explore'
import AppIcon from '@/app/components/base/app-icon'
+import { useGlobalPublicStore } from '@/context/global-public-context'
+import { RiInformation2Line } from '@remixicon/react'
+import { useCallback } from 'react'
+import AppListContext from '@/context/app-list-context'
+import { useContextSelector } from 'use-context-selector'
export type AppCardProps = {
app: App
@@ -19,6 +24,14 @@ const AppCard = ({
}: AppCardProps) => {
const { t } = useTranslation()
const { app: appBasicInfo } = app
+ const { systemFeatures } = useGlobalPublicStore()
+ const isTrialApp = app.can_trial && systemFeatures.enable_trial_app
+ const setShowTryAppPanel = useContextSelector(AppListContext, ctx => ctx.setShowTryAppPanel)
+ const showTryAPPPanel = useCallback((appId: string) => {
+ return () => {
+ setShowTryAppPanel?.(true, { appId, app })
+ }
+ }, [setShowTryAppPanel, app.category])
return (
@@ -46,11 +59,17 @@ const AppCard = ({
-
-
diff --git a/web/app/components/apps/index.tsx b/web/app/components/apps/index.tsx
index 6d21800421..ba611a597b 100644
--- a/web/app/components/apps/index.tsx
+++ b/web/app/components/apps/index.tsx
@@ -3,6 +3,16 @@ import { useEducationInit } from '@/app/education-apply/hooks'
import List from './list'
import useDocumentTitle from '@/hooks/use-document-title'
import { useTranslation } from 'react-i18next'
+import AppListContext from '@/context/app-list-context'
+import { useCallback, useState } from 'react'
+import type { CurrentTryAppParams } from '@/context/explore-context'
+import TryApp from '../explore/try-app'
+import type { CreateAppModalProps } from '../explore/create-app-modal'
+import CreateAppModal from '../explore/create-app-modal'
+import { fetchAppDetail } from '@/service/explore'
+import { DSLImportMode } from '@/models/app'
+import { useImportDSL } from '@/hooks/use-import-dsl'
+import DSLConfirmModal from '../app/create-from-dsl-modal/dsl-confirm-modal'
const Apps = () => {
const { t } = useTranslation()
@@ -10,10 +20,122 @@ const Apps = () => {
useDocumentTitle(t('common.menus.apps'))
useEducationInit()
+ const [currentTryAppParams, setCurrentTryAppParams] = useState(undefined)
+ const currApp = currentTryAppParams?.app
+ const [isShowTryAppPanel, setIsShowTryAppPanel] = useState(false)
+ const hideTryAppPanel = useCallback(() => {
+ setIsShowTryAppPanel(false)
+ }, [])
+ const setShowTryAppPanel = (showTryAppPanel: boolean, params?: CurrentTryAppParams) => {
+ if (showTryAppPanel)
+ setCurrentTryAppParams(params)
+ else
+ setCurrentTryAppParams(undefined)
+ setIsShowTryAppPanel(showTryAppPanel)
+ }
+ const [isShowCreateModal, setIsShowCreateModal] = useState(false)
+
+ const handleShowFromTryApp = useCallback(() => {
+ setIsShowCreateModal(true)
+ }, [])
+
+ const [controlRefreshList, setControlRefreshList] = useState(0)
+ const [controlHideCreateFromTemplatePanel, setControlHideCreateFromTemplatePanel] = useState(0)
+ const onSuccess = useCallback(() => {
+ setControlRefreshList(prev => prev + 1)
+ setControlHideCreateFromTemplatePanel(prev => prev + 1)
+ }, [])
+
+ const [showDSLConfirmModal, setShowDSLConfirmModal] = useState(false)
+
+ const {
+ handleImportDSL,
+ handleImportDSLConfirm,
+ versions,
+ isFetching,
+ } = useImportDSL()
+
+ const onConfirmDSL = useCallback(async () => {
+ await handleImportDSLConfirm({
+ onSuccess,
+ })
+ }, [handleImportDSLConfirm, onSuccess])
+
+ const onCreate: CreateAppModalProps['onConfirm'] = async ({
+ name,
+ icon_type,
+ icon,
+ icon_background,
+ description,
+ }) => {
+ hideTryAppPanel()
+
+ const { export_data } = await fetchAppDetail(
+ currApp?.app.id as string,
+ )
+ const payload = {
+ mode: DSLImportMode.YAML_CONTENT,
+ yaml_content: export_data,
+ name,
+ icon_type,
+ icon,
+ icon_background,
+ description,
+ }
+ await handleImportDSL(payload, {
+ onSuccess: () => {
+ setIsShowCreateModal(false)
+ },
+ onPending: () => {
+ setShowDSLConfirmModal(true)
+ },
+ })
+ }
+
return (
-
-
-
+
+
+
+ {isShowTryAppPanel && (
+
+ )}
+
+ {
+ showDSLConfirmModal && (
+ setShowDSLConfirmModal(false)}
+ onConfirm={onConfirmDSL}
+ confirmDisabled={isFetching}
+ />
+ )
+ }
+
+ {isShowCreateModal && (
+ setIsShowCreateModal(false)}
+ />
+ )}
+
+
)
}
diff --git a/web/app/components/apps/list.tsx b/web/app/components/apps/list.tsx
index 30083f16cb..c1e6bc6be7 100644
--- a/web/app/components/apps/list.tsx
+++ b/web/app/components/apps/list.tsx
@@ -1,5 +1,6 @@
'use client'
+import type { FC } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
useRouter,
@@ -67,7 +68,12 @@ const getKey = (
return null
}
-const List = () => {
+type Props = {
+ controlRefreshList: number
+}
+const List: FC = ({
+ controlRefreshList,
+}) => {
const { t } = useTranslation()
const { systemFeatures } = useGlobalPublicStore()
const router = useRouter()
@@ -142,6 +148,10 @@ const List = () => {
return () => window.clearInterval(timer)
}, [workflowIds.join(','), mutate, refreshOnlineUsers])
+ useEffect(() => {
+ if (controlRefreshList > 0)
+ mutate()
+ }, [controlRefreshList])
const anchorRef = useRef(null)
const options = [
diff --git a/web/app/components/apps/new-app-card.tsx b/web/app/components/apps/new-app-card.tsx
index 7a10bc8527..8d051e9ecf 100644
--- a/web/app/components/apps/new-app-card.tsx
+++ b/web/app/components/apps/new-app-card.tsx
@@ -1,6 +1,6 @@
'use client'
-import React, { useMemo, useState } from 'react'
+import React, { useEffect, useMemo, useState } from 'react'
import {
useRouter,
useSearchParams,
@@ -11,6 +11,8 @@ import { useProviderContext } from '@/context/provider-context'
import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
import cn from '@/utils/classnames'
import dynamic from 'next/dynamic'
+import AppListContext from '@/context/app-list-context'
+import { useContextSelector } from 'use-context-selector'
const CreateAppModal = dynamic(() => import('@/app/components/app/create-app-modal'), {
ssr: false,
@@ -52,6 +54,12 @@ const CreateAppCard = ({
return undefined
}, [dslUrl])
+ const controlHideCreateFromTemplatePanel = useContextSelector(AppListContext, ctx => ctx.controlHideCreateFromTemplatePanel)
+ useEffect(() => {
+ if (controlHideCreateFromTemplatePanel > 0)
+ setShowNewAppTemplateDialog(false)
+ }, [controlHideCreateFromTemplatePanel])
+
return (
= ({
data={workflowProcess}
item={item}
hideProcessDetail={hideProcessDetail}
- readonly={hideProcessDetail && appData ? !appData.site.show_workflow_steps : undefined}
+ readonly={hideProcessDetail && appData ? !appData.site?.show_workflow_steps : undefined}
/>
)
}
diff --git a/web/app/components/base/chat/chat/index.tsx b/web/app/components/base/chat/chat/index.tsx
index 4afc434ee9..61ed1a3a20 100644
--- a/web/app/components/base/chat/chat/index.tsx
+++ b/web/app/components/base/chat/chat/index.tsx
@@ -316,7 +316,7 @@ const Chat: FC
= ({
{
!noChatInput && (
{
return null
if (!collapsed && inputsForms.length > 0 && !allInputsHidden)
return null
+ if (!appData?.site)
+ return null
if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) {
return (
@@ -226,7 +228,7 @@ const ChatWrapper = () => {
)
- }, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState, allInputsHidden])
+ }, [appData?.site, chatList, collapsed, currentConversationId, inputsForms.length, respondingState, allInputsHidden])
const answerIcon = isDify()
?
diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx
index 698f212693..500860e740 100644
--- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx
+++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx
@@ -72,8 +72,12 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
const isInstalledApp = false // just can be webapp and try app
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
const isTryApp = appSourceType === AppSourceType.tryApp
- const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR('appInfo', isTryApp ? () => fetchTryAppInfo(tryAppId) : fetchAppInfo)
- const appId = useMemo(() => isTryApp ? tryAppId : appInfo?.app_id, [appInfo])
+ const { data: appInfo, isLoading: appInfoLoading, error: appInfoError } = useSWR('appInfo', () => {
+ return isTryApp ? () => fetchTryAppInfo(tryAppId!) : fetchAppInfo
+ })
+ const appId = useMemo(() => {
+ return isTryApp ? tryAppId : (appInfo as any)?.app_id
+ }, [appInfo])
const [userId, setUserId] = useState()
const [conversationId, setConversationId] = useState()
@@ -116,9 +120,16 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
const [conversationIdInfo, setConversationIdInfo] = useLocalStorageState>>(CONVERSATION_ID_INFO, {
defaultValue: {},
})
+ const removeConversationIdInfo = useCallback((appId: string) => {
+ setConversationIdInfo((prev) => {
+ const newInfo = { ...prev }
+ delete newInfo[appId]
+ return newInfo
+ })
+ }, [setConversationIdInfo])
const allowResetChat = !conversationId
- const currentConversationId = useMemo(() => isTryApp ? '' : conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || conversationId || '',
- [isTryApp, appId, conversationIdInfo, userId, conversationId])
+ const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || conversationId || '',
+ [appId, conversationIdInfo, userId, conversationId])
const handleConversationIdInfoChange = useCallback((changeConversationId: string) => {
if (appId) {
let prevValue = conversationIdInfo?.[appId || '']
@@ -146,7 +157,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
const { data: appMeta } = useSWR(isTryApp ? null : ['appMeta', appSourceType, appId], () => fetchAppMeta(appSourceType, appId))
const { data: appPinnedConversationData } = useSWR(isTryApp ? null : ['appConversationData', appSourceType, appId, true], () => fetchConversations(appSourceType, appId, undefined, true, 100))
const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(isTryApp ? null : ['appConversationData', appSourceType, appId, false], () => fetchConversations(appSourceType, appId, undefined, false, 100))
- const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, appSourceType, appId] : null, () => fetchChatList(chatShouldReloadKey, appSourceType, appId))
+ const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR((chatShouldReloadKey && !isTryApp) ? ['appChatList', chatShouldReloadKey, appSourceType, appId] : null, () => fetchChatList(chatShouldReloadKey, appSourceType, appId))
const [clearChatList, setClearChatList] = useState(false)
const [isResponding, setIsResponding] = useState(false)
@@ -317,7 +328,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
}, [appChatListData, currentConversationId])
const [currentConversationInputs, setCurrentConversationInputs] = useState>(currentConversationLatestInputs || {})
useEffect(() => {
- if (currentConversationItem)
+ if (currentConversationItem && !isTryApp)
setCurrentConversationInputs(currentConversationLatestInputs || {})
}, [currentConversationItem, currentConversationLatestInputs])
@@ -377,12 +388,17 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
setClearChatList(false)
}, [handleConversationIdInfoChange, setClearChatList])
const handleNewConversation = useCallback(async () => {
+ if (isTryApp) {
+ setClearChatList(true)
+ return
+ }
+
currentChatInstanceRef.current.handleStop()
setShowNewConversationItemInList(true)
handleChangeConversation('')
handleNewConversationInputsChange(await getProcessedInputsFromUrlParams())
setClearChatList(true)
- }, [handleChangeConversation, setShowNewConversationItemInList, handleNewConversationInputsChange, setClearChatList])
+ }, [isTryApp, setShowNewConversationItemInList, handleNewConversationInputsChange, setClearChatList])
const handleNewConversationCompleted = useCallback((newConversationId: string) => {
setNewConversationId(newConversationId)
@@ -406,6 +422,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
appId,
currentConversationId,
currentConversationItem,
+ removeConversationIdInfo,
handleConversationIdInfoChange,
appData: appInfo,
appParams: appParams || {} as ChatConfig,
diff --git a/web/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx b/web/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx
index d6c89864d9..c8b6c406fc 100644
--- a/web/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx
+++ b/web/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx
@@ -33,7 +33,7 @@ const ViewFormDropdown = ({ iconColor }: Props) => {
-
+
diff --git a/web/app/components/explore/app-card/index.tsx b/web/app/components/explore/app-card/index.tsx
index dda12083c3..5408fd2eb8 100644
--- a/web/app/components/explore/app-card/index.tsx
+++ b/web/app/components/explore/app-card/index.tsx
@@ -71,7 +71,7 @@ const AppCard = ({
{isExplore && (canCreate || isTrialApp) && (
{canCreate && (
onCreate()}>
diff --git a/web/app/components/explore/try-app/app/chat.tsx b/web/app/components/explore/try-app/app/chat.tsx
index 54f167391d..745cc0bc62 100644
--- a/web/app/components/explore/try-app/app/chat.tsx
+++ b/web/app/components/explore/try-app/app/chat.tsx
@@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
-import React from 'react'
+import React, { useEffect } from 'react'
import ChatWrapper from '@/app/components/base/chat/embedded-chatbot/chat-wrapper'
import { useThemeContext } from '../../../base/chat/embedded-chatbot/theme/theme-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
@@ -17,6 +17,10 @@ import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import type { TryAppInfo } from '@/service/try-app'
import AppIcon from '@/app/components/base/app-icon'
+import Tooltip from '@/app/components/base/tooltip'
+import ActionButton from '@/app/components/base/action-button'
+import { RiResetLeftLine } from '@remixicon/react'
+import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown'
type Props = {
appId: string
@@ -33,10 +37,21 @@ const TryApp: FC = ({
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const themeBuilder = useThemeContext()
- const chatData = useEmbeddedChatbot(AppSourceType.tryApp, appId)
+ const { removeConversationIdInfo, ...chatData } = useEmbeddedChatbot(AppSourceType.tryApp, appId)
+ const currentConversationId = chatData.currentConversationId
+ const inputsForms = chatData.inputsForms
+ useEffect(() => {
+ if (appId)
+ removeConversationIdInfo(appId)
+ }, [appId])
const [isHideTryNotice, {
setTrue: hideTryNotice,
}] = useBoolean(false)
+
+ const handleNewConversation = () => {
+ removeConversationIdInfo(appId)
+ chatData.handleNewConversation()
+ }
return (
= ({
/>
{appDetail.name}
+
+ {currentConversationId && (
+
+
+
+
+
+ )}
+ {currentConversationId && inputsForms.length > 0 && (
+
+ )}
+
{!isHideTryNotice && (
diff --git a/web/context/app-list-context.ts b/web/context/app-list-context.ts
new file mode 100644
index 0000000000..861c6d01e3
--- /dev/null
+++ b/web/context/app-list-context.ts
@@ -0,0 +1,19 @@
+import { createContext } from 'use-context-selector'
+import { noop } from 'lodash-es'
+import type { CurrentTryAppParams } from './explore-context'
+
+type Props = {
+ currentApp?: CurrentTryAppParams
+ isShowTryAppPanel: boolean
+ setShowTryAppPanel: (showTryAppPanel: boolean, params?: CurrentTryAppParams) => void
+ controlHideCreateFromTemplatePanel: number
+}
+
+const AppListContext = createContext
({
+ isShowTryAppPanel: false,
+ setShowTryAppPanel: noop,
+ currentApp: undefined,
+ controlHideCreateFromTemplatePanel: 0,
+})
+
+export default AppListContext