| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  | /* eslint-disable @typescript-eslint/no-use-before-define */ | 
					
						
							|  |  |  | 'use client' | 
					
						
							|  |  |  | import type { FC } from 'react' | 
					
						
							|  |  |  | import React, { useEffect, useRef, useState } from 'react' | 
					
						
							|  |  |  | import cn from 'classnames' | 
					
						
							|  |  |  | import { useTranslation } from 'react-i18next' | 
					
						
							|  |  |  | import { useContext } from 'use-context-selector' | 
					
						
							|  |  |  | import produce from 'immer' | 
					
						
							|  |  |  | import { useBoolean, useGetState } from 'ahooks' | 
					
						
							|  |  |  | import { checkOrSetAccessToken } from '../utils' | 
					
						
							|  |  |  | import AppUnavailable from '../../base/app-unavailable' | 
					
						
							|  |  |  | import useConversation from './hooks/use-conversation' | 
					
						
							|  |  |  | import s from './style.module.css' | 
					
						
							|  |  |  | import { ToastContext } from '@/app/components/base/toast' | 
					
						
							|  |  |  | import ConfigScene from '@/app/components/share/chatbot/config-scence' | 
					
						
							|  |  |  | import Header from '@/app/components/share/header' | 
					
						
							| 
									
										
										
										
											2023-08-18 10:39:05 +08:00
										 |  |  | import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, fetchSuggestedQuestions, sendChatMessage, stopChatMessageResponding, updateFeedback } from '@/service/share' | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  | import type { ConversationItem, SiteInfo } from '@/models/share' | 
					
						
							|  |  |  | import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug' | 
					
						
							| 
									
										
										
										
											2023-08-18 10:39:05 +08:00
										 |  |  | import type { Feedbacktype, IChatItem } from '@/app/components/app/chat/type' | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  | import Chat from '@/app/components/app/chat' | 
					
						
							|  |  |  | import { changeLanguage } from '@/i18n/i18next-config' | 
					
						
							|  |  |  | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | 
					
						
							|  |  |  | import Loading from '@/app/components/base/loading' | 
					
						
							|  |  |  | import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel' | 
					
						
							|  |  |  | import { userInputsFormToPromptVariables } from '@/utils/model-config' | 
					
						
							|  |  |  | import type { InstalledApp } from '@/models/explore' | 
					
						
							| 
									
										
										
										
											2023-08-18 10:39:05 +08:00
										 |  |  | import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export type IMainProps = { | 
					
						
							|  |  |  |   isInstalledApp?: boolean | 
					
						
							|  |  |  |   installedAppInfo?: InstalledApp | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const Main: FC<IMainProps> = ({ | 
					
						
							|  |  |  |   isInstalledApp = false, | 
					
						
							|  |  |  |   installedAppInfo, | 
					
						
							|  |  |  | }) => { | 
					
						
							|  |  |  |   const { t } = useTranslation() | 
					
						
							|  |  |  |   const media = useBreakpoints() | 
					
						
							|  |  |  |   const isMobile = media === MediaType.mobile | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |   * app info | 
					
						
							|  |  |  |   */ | 
					
						
							|  |  |  |   const [appUnavailable, setAppUnavailable] = useState<boolean>(false) | 
					
						
							|  |  |  |   const [isUnknwonReason, setIsUnknwonReason] = useState<boolean>(false) | 
					
						
							|  |  |  |   const [appId, setAppId] = useState<string>('') | 
					
						
							|  |  |  |   const [isPublicVersion, setIsPublicVersion] = useState<boolean>(true) | 
					
						
							|  |  |  |   const [siteInfo, setSiteInfo] = useState<SiteInfo | null>() | 
					
						
							|  |  |  |   const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null) | 
					
						
							|  |  |  |   const [inited, setInited] = useState<boolean>(false) | 
					
						
							|  |  |  |   const [plan, setPlan] = useState<string>('basic') // basic/plus/pro
 | 
					
						
							|  |  |  |   // Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
 | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     if (siteInfo?.title) { | 
					
						
							|  |  |  |       if (plan !== 'basic') | 
					
						
							|  |  |  |         document.title = `${siteInfo.title}` | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         document.title = `${siteInfo.title} - Powered by Dify` | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }, [siteInfo?.title, plan]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |   * conversation info | 
					
						
							|  |  |  |   */ | 
					
						
							|  |  |  |   const [allConversationList, setAllConversationList] = useState<ConversationItem[]>([]) | 
					
						
							|  |  |  |   const [isClearConversationList, { setTrue: clearConversationListTrue, setFalse: clearConversationListFalse }] = useBoolean(false) | 
					
						
							|  |  |  |   const [isClearPinnedConversationList, { setTrue: clearPinnedConversationListTrue, setFalse: clearPinnedConversationListFalse }] = useBoolean(false) | 
					
						
							|  |  |  |   const { | 
					
						
							|  |  |  |     conversationList, | 
					
						
							|  |  |  |     setConversationList, | 
					
						
							|  |  |  |     pinnedConversationList, | 
					
						
							|  |  |  |     setPinnedConversationList, | 
					
						
							|  |  |  |     currConversationId, | 
					
						
							|  |  |  |     setCurrConversationId, | 
					
						
							|  |  |  |     getConversationIdFromStorage, | 
					
						
							|  |  |  |     isNewConversation, | 
					
						
							|  |  |  |     currConversationInfo, | 
					
						
							|  |  |  |     currInputs, | 
					
						
							|  |  |  |     newConversationInputs, | 
					
						
							|  |  |  |     // existConversationInputs,
 | 
					
						
							|  |  |  |     resetNewConversationInputs, | 
					
						
							|  |  |  |     setCurrInputs, | 
					
						
							|  |  |  |     setNewConversationInfo, | 
					
						
							|  |  |  |     setExistConversationInfo, | 
					
						
							|  |  |  |   } = useConversation() | 
					
						
							|  |  |  |   const [hasMore, setHasMore] = useState<boolean>(true) | 
					
						
							|  |  |  |   const [hasPinnedMore, setHasPinnedMore] = useState<boolean>(true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const onMoreLoaded = ({ data: conversations, has_more }: any) => { | 
					
						
							|  |  |  |     setHasMore(has_more) | 
					
						
							|  |  |  |     if (isClearConversationList) { | 
					
						
							|  |  |  |       setConversationList(conversations) | 
					
						
							|  |  |  |       clearConversationListFalse() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							|  |  |  |       setConversationList([...conversationList, ...conversations]) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const onPinnedMoreLoaded = ({ data: conversations, has_more }: any) => { | 
					
						
							|  |  |  |     setHasPinnedMore(has_more) | 
					
						
							|  |  |  |     if (isClearPinnedConversationList) { | 
					
						
							|  |  |  |       setPinnedConversationList(conversations) | 
					
						
							|  |  |  |       clearPinnedConversationListFalse() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							|  |  |  |       setPinnedConversationList([...pinnedConversationList, ...conversations]) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const noticeUpdateList = () => { | 
					
						
							|  |  |  |     setHasMore(true) | 
					
						
							|  |  |  |     clearConversationListTrue() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setHasPinnedMore(true) | 
					
						
							|  |  |  |     clearPinnedConversationListTrue() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setControlUpdateConversationList(Date.now()) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null) | 
					
						
							|  |  |  |   const [speechToTextConfig, setSpeechToTextConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false) | 
					
						
							|  |  |  |   const [isChatStarted, { setTrue: setChatStarted, setFalse: setChatNotStarted }] = useBoolean(false) | 
					
						
							|  |  |  |   const handleStartChat = (inputs: Record<string, any>) => { | 
					
						
							|  |  |  |     createNewChat() | 
					
						
							|  |  |  |     setConversationIdChangeBecauseOfNew(true) | 
					
						
							|  |  |  |     setCurrInputs(inputs) | 
					
						
							|  |  |  |     setChatStarted() | 
					
						
							|  |  |  |     // parse variables in introduction
 | 
					
						
							|  |  |  |     setChatList(generateNewChatListWithOpenstatement('', inputs)) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const hasSetInputs = (() => { | 
					
						
							|  |  |  |     if (!isNewConversation) | 
					
						
							|  |  |  |       return true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return isChatStarted | 
					
						
							|  |  |  |   })() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // const conversationName = currConversationInfo?.name || t('share.chat.newChatDefaultName') as string
 | 
					
						
							|  |  |  |   const conversationIntroduction = currConversationInfo?.introduction || '' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleConversationSwitch = () => { | 
					
						
							|  |  |  |     if (!inited) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     if (!appId) { | 
					
						
							|  |  |  |       // wait for appId
 | 
					
						
							|  |  |  |       setTimeout(handleConversationSwitch, 100) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // update inputs of current conversation
 | 
					
						
							|  |  |  |     let notSyncToStateIntroduction = '' | 
					
						
							|  |  |  |     let notSyncToStateInputs: Record<string, any> | undefined | null = {} | 
					
						
							|  |  |  |     if (!isNewConversation) { | 
					
						
							|  |  |  |       const item = allConversationList.find(item => item.id === currConversationId) | 
					
						
							|  |  |  |       notSyncToStateInputs = item?.inputs || {} | 
					
						
							|  |  |  |       setCurrInputs(notSyncToStateInputs) | 
					
						
							|  |  |  |       notSyncToStateIntroduction = item?.introduction || '' | 
					
						
							|  |  |  |       setExistConversationInfo({ | 
					
						
							|  |  |  |         name: item?.name || '', | 
					
						
							|  |  |  |         introduction: notSyncToStateIntroduction, | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							|  |  |  |       notSyncToStateInputs = newConversationInputs | 
					
						
							|  |  |  |       setCurrInputs(notSyncToStateInputs) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // update chat list of current conversation
 | 
					
						
							|  |  |  |     if (!isNewConversation && !conversationIdChangeBecauseOfNew && !isResponsing) { | 
					
						
							|  |  |  |       fetchChatList(currConversationId, isInstalledApp, installedAppInfo?.id).then((res: any) => { | 
					
						
							|  |  |  |         const { data } = res | 
					
						
							|  |  |  |         const newChatList: IChatItem[] = generateNewChatListWithOpenstatement(notSyncToStateIntroduction, notSyncToStateInputs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         data.forEach((item: any) => { | 
					
						
							|  |  |  |           newChatList.push({ | 
					
						
							|  |  |  |             id: `question-${item.id}`, | 
					
						
							|  |  |  |             content: item.query, | 
					
						
							|  |  |  |             isAnswer: false, | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |           newChatList.push({ | 
					
						
							|  |  |  |             id: item.id, | 
					
						
							|  |  |  |             content: item.answer, | 
					
						
							|  |  |  |             feedback: item.feedback, | 
					
						
							|  |  |  |             isAnswer: true, | 
					
						
							| 
									
										
										
										
											2023-09-09 19:17:12 +08:00
										 |  |  |             citation: item.retriever_resources, | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |           }) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         setChatList(newChatList) | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (isNewConversation && isChatStarted) | 
					
						
							|  |  |  |       setChatList(generateNewChatListWithOpenstatement()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setControlFocus(Date.now()) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   useEffect(handleConversationSwitch, [currConversationId, inited]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |   * chat info. chat is under conversation. | 
					
						
							|  |  |  |   */ | 
					
						
							|  |  |  |   const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([]) | 
					
						
							|  |  |  |   const chatListDomRef = useRef<HTMLDivElement>(null) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     // scroll to bottom
 | 
					
						
							|  |  |  |     if (chatListDomRef.current) | 
					
						
							|  |  |  |       chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight | 
					
						
							|  |  |  |   }, [chatList, currConversationId]) | 
					
						
							|  |  |  |   // user can not edit inputs if user had send message
 | 
					
						
							|  |  |  |   const canEditInputs = !chatList.some(item => item.isAnswer === false) && isNewConversation | 
					
						
							|  |  |  |   const createNewChat = async () => { | 
					
						
							|  |  |  |     // if new chat is already exist, do not create new chat
 | 
					
						
							|  |  |  |     abortController?.abort() | 
					
						
							|  |  |  |     setResponsingFalse() | 
					
						
							|  |  |  |     if (conversationList.some(item => item.id === '-1')) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setConversationList(produce(conversationList, (draft) => { | 
					
						
							|  |  |  |       draft.unshift({ | 
					
						
							|  |  |  |         id: '-1', | 
					
						
							|  |  |  |         name: t('share.chat.newChatDefaultName'), | 
					
						
							|  |  |  |         inputs: newConversationInputs, | 
					
						
							|  |  |  |         introduction: conversationIntroduction, | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     })) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // sometime introduction is not applied to state
 | 
					
						
							|  |  |  |   const generateNewChatListWithOpenstatement = (introduction?: string, inputs?: Record<string, any> | null) => { | 
					
						
							|  |  |  |     let caculatedIntroduction = introduction || conversationIntroduction || '' | 
					
						
							|  |  |  |     const caculatedPromptVariables = inputs || currInputs || null | 
					
						
							|  |  |  |     if (caculatedIntroduction && caculatedPromptVariables) | 
					
						
							|  |  |  |       caculatedIntroduction = replaceStringWithValues(caculatedIntroduction, promptConfig?.prompt_variables || [], caculatedPromptVariables) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const openstatement = { | 
					
						
							|  |  |  |       id: `${Date.now()}`, | 
					
						
							|  |  |  |       content: caculatedIntroduction, | 
					
						
							|  |  |  |       isAnswer: true, | 
					
						
							|  |  |  |       feedbackDisabled: true, | 
					
						
							|  |  |  |       isOpeningStatement: isPublicVersion, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (caculatedIntroduction) | 
					
						
							|  |  |  |       return [openstatement] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return [] | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const fetchAllConversations = () => { | 
					
						
							|  |  |  |     return fetchConversations(isInstalledApp, installedAppInfo?.id, undefined, undefined, 100) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const fetchInitData = async () => { | 
					
						
							|  |  |  |     if (!isInstalledApp) | 
					
						
							|  |  |  |       await checkOrSetAccessToken() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Promise.all([isInstalledApp | 
					
						
							|  |  |  |       ? { | 
					
						
							|  |  |  |         app_id: installedAppInfo?.id, | 
					
						
							|  |  |  |         site: { | 
					
						
							|  |  |  |           title: installedAppInfo?.app.name, | 
					
						
							|  |  |  |           prompt_public: false, | 
					
						
							|  |  |  |           copyright: '', | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         plan: 'basic', | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       : fetchAppInfo(), fetchAllConversations(), fetchAppParams(isInstalledApp, installedAppInfo?.id)]) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // init
 | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     (async () => { | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         const [appData, conversationData, appParams]: any = await fetchInitData() | 
					
						
							|  |  |  |         const { app_id: appId, site: siteInfo, plan }: any = appData | 
					
						
							|  |  |  |         setAppId(appId) | 
					
						
							|  |  |  |         setPlan(plan) | 
					
						
							|  |  |  |         const tempIsPublicVersion = siteInfo.prompt_public | 
					
						
							|  |  |  |         setIsPublicVersion(tempIsPublicVersion) | 
					
						
							|  |  |  |         const prompt_template = '' | 
					
						
							|  |  |  |         // handle current conversation id
 | 
					
						
							|  |  |  |         const { data: allConversations } = conversationData as { data: ConversationItem[]; has_more: boolean } | 
					
						
							|  |  |  |         const _conversationId = getConversationIdFromStorage(appId) | 
					
						
							|  |  |  |         const isNotNewConversation = allConversations.some(item => item.id === _conversationId) | 
					
						
							|  |  |  |         setAllConversationList(allConversations) | 
					
						
							|  |  |  |         // fetch new conversation info
 | 
					
						
							| 
									
										
										
										
											2023-09-09 19:17:12 +08:00
										 |  |  |         const { user_input_form, opening_statement: introduction, suggested_questions_after_answer, speech_to_text, retriever_resource }: any = appParams | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |         const prompt_variables = userInputsFormToPromptVariables(user_input_form) | 
					
						
							|  |  |  |         if (siteInfo.default_language) | 
					
						
							|  |  |  |           changeLanguage(siteInfo.default_language) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         setNewConversationInfo({ | 
					
						
							|  |  |  |           name: t('share.chat.newChatDefaultName'), | 
					
						
							|  |  |  |           introduction, | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         setSiteInfo(siteInfo as SiteInfo) | 
					
						
							|  |  |  |         setPromptConfig({ | 
					
						
							|  |  |  |           prompt_template, | 
					
						
							|  |  |  |           prompt_variables, | 
					
						
							|  |  |  |         } as PromptConfig) | 
					
						
							|  |  |  |         setSuggestedQuestionsAfterAnswerConfig(suggested_questions_after_answer) | 
					
						
							|  |  |  |         setSpeechToTextConfig(speech_to_text) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // setConversationList(conversations as ConversationItem[])
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (isNotNewConversation) | 
					
						
							|  |  |  |           setCurrConversationId(_conversationId, appId, false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         setInited(true) | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       catch (e: any) { | 
					
						
							|  |  |  |         if (e.status === 404) { | 
					
						
							|  |  |  |           setAppUnavailable(true) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							|  |  |  |           setIsUnknwonReason(true) | 
					
						
							|  |  |  |           setAppUnavailable(true) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     })() | 
					
						
							|  |  |  |   }, []) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) | 
					
						
							|  |  |  |   const [abortController, setAbortController] = useState<AbortController | null>(null) | 
					
						
							|  |  |  |   const { notify } = useContext(ToastContext) | 
					
						
							|  |  |  |   const logError = (message: string) => { | 
					
						
							|  |  |  |     notify({ type: 'error', message }) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const checkCanSend = () => { | 
					
						
							|  |  |  |     if (currConversationId !== '-1') | 
					
						
							|  |  |  |       return true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const prompt_variables = promptConfig?.prompt_variables | 
					
						
							|  |  |  |     const inputs = currInputs | 
					
						
							|  |  |  |     if (!inputs || !prompt_variables || prompt_variables?.length === 0) | 
					
						
							|  |  |  |       return true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-19 12:41:35 +08:00
										 |  |  |     let hasEmptyInput = '' | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |     const requiredVars = prompt_variables?.filter(({ key, name, required }) => { | 
					
						
							|  |  |  |       const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) | 
					
						
							|  |  |  |       return res | 
					
						
							|  |  |  |     }) || [] // compatible with old version
 | 
					
						
							| 
									
										
										
										
											2023-07-19 12:41:35 +08:00
										 |  |  |     requiredVars.forEach(({ key, name }) => { | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |       if (hasEmptyInput) | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!inputs?.[key]) | 
					
						
							| 
									
										
										
										
											2023-07-19 12:41:35 +08:00
										 |  |  |         hasEmptyInput = name | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |     }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (hasEmptyInput) { | 
					
						
							| 
									
										
										
										
											2023-07-19 12:41:35 +08:00
										 |  |  |       logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput })) | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |       return false | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return !hasEmptyInput | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const [controlFocus, setControlFocus] = useState(0) | 
					
						
							|  |  |  |   const [isShowSuggestion, setIsShowSuggestion] = useState(false) | 
					
						
							|  |  |  |   const doShowSuggestion = isShowSuggestion && !isResponsing | 
					
						
							|  |  |  |   const [suggestQuestions, setSuggestQuestions] = useState<string[]>([]) | 
					
						
							|  |  |  |   const [messageTaskId, setMessageTaskId] = useState('') | 
					
						
							|  |  |  |   const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false) | 
					
						
							| 
									
										
										
										
											2023-08-18 10:39:05 +08:00
										 |  |  |   const [shouldReload, setShouldReload] = useState(false) | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const handleSend = async (message: string) => { | 
					
						
							|  |  |  |     if (isResponsing) { | 
					
						
							|  |  |  |       notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const data = { | 
					
						
							|  |  |  |       inputs: currInputs, | 
					
						
							|  |  |  |       query: message, | 
					
						
							|  |  |  |       conversation_id: isNewConversation ? null : currConversationId, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // qustion
 | 
					
						
							|  |  |  |     const questionId = `question-${Date.now()}` | 
					
						
							|  |  |  |     const questionItem = { | 
					
						
							|  |  |  |       id: questionId, | 
					
						
							|  |  |  |       content: message, | 
					
						
							|  |  |  |       isAnswer: false, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const placeholderAnswerId = `answer-placeholder-${Date.now()}` | 
					
						
							|  |  |  |     const placeholderAnswerItem = { | 
					
						
							|  |  |  |       id: placeholderAnswerId, | 
					
						
							|  |  |  |       content: '', | 
					
						
							|  |  |  |       isAnswer: true, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const newList = [...getChatList(), questionItem, placeholderAnswerItem] | 
					
						
							|  |  |  |     setChatList(newList) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // answer
 | 
					
						
							| 
									
										
										
										
											2023-09-09 19:17:12 +08:00
										 |  |  |     const responseItem: IChatItem = { | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |       id: `${Date.now()}`, | 
					
						
							|  |  |  |       content: '', | 
					
						
							|  |  |  |       isAnswer: true, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let tempNewConversationId = '' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setHasStopResponded(false) | 
					
						
							|  |  |  |     setResponsingTrue() | 
					
						
							|  |  |  |     setIsShowSuggestion(false) | 
					
						
							|  |  |  |     sendChatMessage(data, { | 
					
						
							|  |  |  |       getAbortController: (abortController) => { | 
					
						
							|  |  |  |         setAbortController(abortController) | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { | 
					
						
							|  |  |  |         responseItem.content = responseItem.content + message | 
					
						
							|  |  |  |         responseItem.id = messageId | 
					
						
							|  |  |  |         if (isFirstMessage && newConversationId) | 
					
						
							|  |  |  |           tempNewConversationId = newConversationId | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         setMessageTaskId(taskId) | 
					
						
							|  |  |  |         // closesure new list is outdated.
 | 
					
						
							|  |  |  |         const newListWithAnswer = produce( | 
					
						
							|  |  |  |           getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), | 
					
						
							|  |  |  |           (draft) => { | 
					
						
							|  |  |  |             if (!draft.find(item => item.id === questionId)) | 
					
						
							|  |  |  |               draft.push({ ...questionItem }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             draft.push({ ...responseItem }) | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         setChatList(newListWithAnswer) | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       async onCompleted(hasError?: boolean) { | 
					
						
							|  |  |  |         setResponsingFalse() | 
					
						
							|  |  |  |         if (hasError) | 
					
						
							|  |  |  |           return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (getConversationIdChangeBecauseOfNew()) { | 
					
						
							|  |  |  |           const { data: allConversations }: any = await fetchAllConversations() | 
					
						
							|  |  |  |           setAllConversationList(allConversations) | 
					
						
							|  |  |  |           noticeUpdateList() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         setConversationIdChangeBecauseOfNew(false) | 
					
						
							|  |  |  |         resetNewConversationInputs() | 
					
						
							|  |  |  |         setChatNotStarted() | 
					
						
							|  |  |  |         setCurrConversationId(tempNewConversationId, appId, true) | 
					
						
							|  |  |  |         if (suggestedQuestionsAfterAnswerConfig?.enabled && !getHasStopResponded()) { | 
					
						
							|  |  |  |           const { data }: any = await fetchSuggestedQuestions(responseItem.id, isInstalledApp, installedAppInfo?.id) | 
					
						
							|  |  |  |           setSuggestQuestions(data) | 
					
						
							|  |  |  |           setIsShowSuggestion(true) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2023-08-18 10:39:05 +08:00
										 |  |  |       onError(errorMessage, errorCode) { | 
					
						
							|  |  |  |         if (['provider_not_initialize', 'completion_request_error'].includes(errorCode as string)) | 
					
						
							|  |  |  |           setShouldReload(true) | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |         setResponsingFalse() | 
					
						
							|  |  |  |         // role back placeholder answer
 | 
					
						
							|  |  |  |         setChatList(produce(getChatList(), (draft) => { | 
					
						
							|  |  |  |           draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1) | 
					
						
							|  |  |  |         })) | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }, isInstalledApp, installedAppInfo?.id) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleFeedback = async (messageId: string, feedback: Feedbacktype) => { | 
					
						
							|  |  |  |     await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, installedAppInfo?.id) | 
					
						
							|  |  |  |     const newChatList = chatList.map((item) => { | 
					
						
							|  |  |  |       if (item.id === messageId) { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           ...item, | 
					
						
							|  |  |  |           feedback, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return item | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     setChatList(newChatList) | 
					
						
							|  |  |  |     notify({ type: 'success', message: t('common.api.success') }) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 10:39:05 +08:00
										 |  |  |   const handleReload = () => { | 
					
						
							|  |  |  |     setCurrConversationId('-1', appId, false) | 
					
						
							|  |  |  |     setChatNotStarted() | 
					
						
							|  |  |  |     setShouldReload(false) | 
					
						
							|  |  |  |     createNewChat() | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-16 16:30:55 +08:00
										 |  |  |   const difyIcon = ( | 
					
						
							|  |  |  |     <div className={s.difyHeader}></div> | 
					
						
							|  |  |  |   ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |   if (appUnavailable) | 
					
						
							|  |  |  |     return <AppUnavailable isUnknwonReason={isUnknwonReason} /> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-24 23:24:33 +08:00
										 |  |  |   if (!appId || !siteInfo || !promptConfig) { | 
					
						
							|  |  |  |     return <div className='flex h-screen w-full'> | 
					
						
							|  |  |  |       <Loading type='app' /> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <div> | 
					
						
							|  |  |  |       <Header | 
					
						
							|  |  |  |         title={siteInfo.title} | 
					
						
							| 
									
										
										
										
											2023-07-16 16:30:55 +08:00
										 |  |  |         icon='' | 
					
						
							|  |  |  |         customerIcon={difyIcon} | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |         icon_background={siteInfo.icon_background} | 
					
						
							|  |  |  |         isEmbedScene={true} | 
					
						
							|  |  |  |         isMobile={isMobile} | 
					
						
							|  |  |  |       /> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       <div className={'flex bg-white overflow-hidden'}> | 
					
						
							|  |  |  |         <div className={cn( | 
					
						
							| 
									
										
										
										
											2023-08-24 22:37:46 +08:00
										 |  |  |           isInstalledApp ? s.installedApp : 'h-[calc(100vh_-_3rem)]', | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |           'flex-grow flex flex-col overflow-y-auto', | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         }> | 
					
						
							|  |  |  |           <ConfigScene | 
					
						
							|  |  |  |             // conversationName={conversationName}
 | 
					
						
							|  |  |  |             hasSetInputs={hasSetInputs} | 
					
						
							|  |  |  |             isPublicVersion={isPublicVersion} | 
					
						
							|  |  |  |             siteInfo={siteInfo} | 
					
						
							|  |  |  |             promptConfig={promptConfig} | 
					
						
							|  |  |  |             onStartChat={handleStartChat} | 
					
						
							|  |  |  |             canEditInputs={canEditInputs} | 
					
						
							|  |  |  |             savedInputs={currInputs as Record<string, any>} | 
					
						
							|  |  |  |             onInputsChange={setCurrInputs} | 
					
						
							|  |  |  |             plan={plan} | 
					
						
							|  |  |  |           ></ConfigScene> | 
					
						
							| 
									
										
										
										
											2023-08-18 10:39:05 +08:00
										 |  |  |           { | 
					
						
							|  |  |  |             shouldReload && ( | 
					
						
							|  |  |  |               <div className='flex items-center justify-between mb-5 px-4 py-2 bg-[#FEF0C7]'> | 
					
						
							|  |  |  |                 <div className='flex items-center text-xs font-medium text-[#DC6803]'> | 
					
						
							|  |  |  |                   <AlertTriangle className='mr-2 w-4 h-4' /> | 
					
						
							|  |  |  |                   {t('share.chat.temporarySystemIssue')} | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |                 <div | 
					
						
							|  |  |  |                   className='flex items-center px-3 h-7 bg-white shadow-xs rounded-md text-xs font-medium text-gray-700 cursor-pointer' | 
					
						
							|  |  |  |                   onClick={handleReload} | 
					
						
							|  |  |  |                 > | 
					
						
							|  |  |  |                   {t('share.chat.tryToSolve')} | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |               </div> | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |           { | 
					
						
							|  |  |  |             hasSetInputs && ( | 
					
						
							| 
									
										
										
										
											2023-07-17 00:14:32 +08:00
										 |  |  |               <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[76px]'), 'relative grow h-[200px] pc:w-[794px] max-w-full mobile:w-full mx-auto mb-3.5 overflow-hidden')}> | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |                 <div className='h-full overflow-y-auto' ref={chatListDomRef}> | 
					
						
							|  |  |  |                   <Chat | 
					
						
							|  |  |  |                     chatList={chatList} | 
					
						
							|  |  |  |                     onSend={handleSend} | 
					
						
							|  |  |  |                     isHideFeedbackEdit | 
					
						
							|  |  |  |                     onFeedback={handleFeedback} | 
					
						
							|  |  |  |                     isResponsing={isResponsing} | 
					
						
							|  |  |  |                     canStopResponsing={!!messageTaskId} | 
					
						
							|  |  |  |                     abortResponsing={async () => { | 
					
						
							|  |  |  |                       await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id) | 
					
						
							|  |  |  |                       setHasStopResponded(true) | 
					
						
							|  |  |  |                       setResponsingFalse() | 
					
						
							|  |  |  |                     }} | 
					
						
							|  |  |  |                     checkCanSend={checkCanSend} | 
					
						
							|  |  |  |                     controlFocus={controlFocus} | 
					
						
							|  |  |  |                     isShowSuggestion={doShowSuggestion} | 
					
						
							|  |  |  |                     suggestionList={suggestQuestions} | 
					
						
							|  |  |  |                     displayScene='web' | 
					
						
							|  |  |  |                     isShowSpeechToText={speechToTextConfig?.enabled} | 
					
						
							| 
									
										
										
										
											2023-07-16 16:30:55 +08:00
										 |  |  |                     answerIconClassName={s.difyIcon} | 
					
						
							| 
									
										
										
										
											2023-07-12 17:27:50 +08:00
										 |  |  |                   /> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |               </div>) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           {/* {isShowConfirm && ( | 
					
						
							|  |  |  |             <Confirm | 
					
						
							|  |  |  |               title={t('share.chat.deleteConversation.title')} | 
					
						
							|  |  |  |               content={t('share.chat.deleteConversation.content')} | 
					
						
							|  |  |  |               isShow={isShowConfirm} | 
					
						
							|  |  |  |               onClose={hideConfirm} | 
					
						
							|  |  |  |               onConfirm={didDelete} | 
					
						
							|  |  |  |               onCancel={hideConfirm} | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |           )} */} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | export default React.memo(Main) |