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) && ( +
+ {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