|
@@ -1,40 +1,39 @@
|
|
|
+/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
|
'use client'
|
|
|
import type { FC } from 'react'
|
|
|
-import React, { useEffect, useState, useRef } 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 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 Sidebar from '@/app/components/share/chat/sidebar'
|
|
|
import ConfigSence from '@/app/components/share/chat/config-scence'
|
|
|
import Header from '@/app/components/share/header'
|
|
|
-import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, sendChatMessage, updateFeedback, fetchSuggestedQuestions } from '@/service/share'
|
|
|
+import { fetchAppInfo, fetchAppParams, fetchChatList, fetchConversations, fetchSuggestedQuestions, sendChatMessage, updateFeedback } from '@/service/share'
|
|
|
import type { ConversationItem, SiteInfo } from '@/models/share'
|
|
|
-import type { PromptConfig } from '@/models/debug'
|
|
|
+import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
|
|
|
import type { Feedbacktype, IChatItem } from '@/app/components/app/chat'
|
|
|
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 AppUnavailable from '../../base/app-unavailable'
|
|
|
import { userInputsFormToPromptVariables } from '@/utils/model-config'
|
|
|
-import { SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
|
|
|
-import { InstalledApp } from '@/models/explore'
|
|
|
-
|
|
|
-import s from './style.module.css'
|
|
|
+import type { InstalledApp } from '@/models/explore'
|
|
|
|
|
|
export type IMainProps = {
|
|
|
- isInstalledApp?: boolean,
|
|
|
- installedAppInfo? : InstalledApp
|
|
|
+ isInstalledApp?: boolean
|
|
|
+ installedAppInfo?: InstalledApp
|
|
|
}
|
|
|
|
|
|
const Main: FC<IMainProps> = ({
|
|
|
isInstalledApp = false,
|
|
|
- installedAppInfo
|
|
|
+ installedAppInfo,
|
|
|
}) => {
|
|
|
const { t } = useTranslation()
|
|
|
const media = useBreakpoints()
|
|
@@ -53,7 +52,7 @@ const Main: FC<IMainProps> = ({
|
|
|
const [plan, setPlan] = useState<string>('basic') // basic/plus/pro
|
|
|
// in mobile, show sidebar by click button
|
|
|
const [isShowSidebar, { setTrue: showSidebar, setFalse: hideSidebar }] = useBoolean(false)
|
|
|
- // Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client.
|
|
|
+ // 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')
|
|
@@ -61,7 +60,6 @@ const Main: FC<IMainProps> = ({
|
|
|
else
|
|
|
document.title = `${siteInfo.title} - Powered by Dify`
|
|
|
}
|
|
|
-
|
|
|
}, [siteInfo?.title, plan])
|
|
|
|
|
|
/*
|
|
@@ -81,7 +79,7 @@ const Main: FC<IMainProps> = ({
|
|
|
resetNewConversationInputs,
|
|
|
setCurrInputs,
|
|
|
setNewConversationInfo,
|
|
|
- setExistConversationInfo
|
|
|
+ setExistConversationInfo,
|
|
|
} = useConversation()
|
|
|
const [hasMore, setHasMore] = useState<boolean>(false)
|
|
|
const onMoreLoaded = ({ data: conversations, has_more }: any) => {
|
|
@@ -101,9 +99,9 @@ const Main: FC<IMainProps> = ({
|
|
|
setChatList(generateNewChatListWithOpenstatement('', inputs))
|
|
|
}
|
|
|
const hasSetInputs = (() => {
|
|
|
- if (!isNewConversation) {
|
|
|
+ if (!isNewConversation)
|
|
|
return true
|
|
|
- }
|
|
|
+
|
|
|
return isChatStarted
|
|
|
})()
|
|
|
|
|
@@ -111,7 +109,8 @@ const Main: FC<IMainProps> = ({
|
|
|
const conversationIntroduction = currConversationInfo?.introduction || ''
|
|
|
|
|
|
const handleConversationSwitch = () => {
|
|
|
- if (!inited) return
|
|
|
+ if (!inited)
|
|
|
+ return
|
|
|
if (!appId) {
|
|
|
// wait for appId
|
|
|
setTimeout(handleConversationSwitch, 100)
|
|
@@ -130,12 +129,13 @@ const Main: FC<IMainProps> = ({
|
|
|
name: item?.name || '',
|
|
|
introduction: notSyncToStateIntroduction,
|
|
|
})
|
|
|
- } else {
|
|
|
+ }
|
|
|
+ else {
|
|
|
notSyncToStateInputs = newConversationInputs
|
|
|
setCurrInputs(notSyncToStateInputs)
|
|
|
}
|
|
|
|
|
|
- // update chat list of current conversation
|
|
|
+ // update chat list of current conversation
|
|
|
if (!isNewConversation && !conversationIdChangeBecauseOfNew && !isResponsing) {
|
|
|
fetchChatList(currConversationId, isInstalledApp, installedAppInfo?.id).then((res: any) => {
|
|
|
const { data } = res
|
|
@@ -158,9 +158,8 @@ const Main: FC<IMainProps> = ({
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- if (isNewConversation && isChatStarted) {
|
|
|
+ if (isNewConversation && isChatStarted)
|
|
|
setChatList(generateNewChatListWithOpenstatement())
|
|
|
- }
|
|
|
|
|
|
setControlFocus(Date.now())
|
|
|
}
|
|
@@ -170,7 +169,8 @@ const Main: FC<IMainProps> = ({
|
|
|
if (id === '-1') {
|
|
|
createNewChat()
|
|
|
setConversationIdChangeBecauseOfNew(true)
|
|
|
- } else {
|
|
|
+ }
|
|
|
+ else {
|
|
|
setConversationIdChangeBecauseOfNew(false)
|
|
|
}
|
|
|
// trigger handleConversationSwitch
|
|
@@ -186,9 +186,8 @@ const Main: FC<IMainProps> = ({
|
|
|
const chatListDomRef = useRef<HTMLDivElement>(null)
|
|
|
useEffect(() => {
|
|
|
// scroll to bottom
|
|
|
- if (chatListDomRef.current) {
|
|
|
+ if (chatListDomRef.current)
|
|
|
chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight
|
|
|
- }
|
|
|
}, [chatList, currConversationId])
|
|
|
// user can not edit inputs if user had send message
|
|
|
const canEditInpus = !chatList.some(item => item.isAnswer === false) && isNewConversation
|
|
@@ -196,15 +195,15 @@ const Main: FC<IMainProps> = ({
|
|
|
// if new chat is already exist, do not create new chat
|
|
|
abortController?.abort()
|
|
|
setResponsingFalse()
|
|
|
- if (conversationList.some(item => item.id === '-1')) {
|
|
|
+ if (conversationList.some(item => item.id === '-1'))
|
|
|
return
|
|
|
- }
|
|
|
- setConversationList(produce(conversationList, draft => {
|
|
|
+
|
|
|
+ setConversationList(produce(conversationList, (draft) => {
|
|
|
draft.unshift({
|
|
|
id: '-1',
|
|
|
name: t('share.chat.newChatDefaultName'),
|
|
|
inputs: newConversationInputs,
|
|
|
- introduction: conversationIntroduction
|
|
|
+ introduction: conversationIntroduction,
|
|
|
})
|
|
|
}))
|
|
|
}
|
|
@@ -213,36 +212,37 @@ const Main: FC<IMainProps> = ({
|
|
|
const generateNewChatListWithOpenstatement = (introduction?: string, inputs?: Record<string, any> | null) => {
|
|
|
let caculatedIntroduction = introduction || conversationIntroduction || ''
|
|
|
const caculatedPromptVariables = inputs || currInputs || null
|
|
|
- if (caculatedIntroduction && caculatedPromptVariables) {
|
|
|
+ if (caculatedIntroduction && caculatedPromptVariables)
|
|
|
caculatedIntroduction = replaceStringWithValues(caculatedIntroduction, promptConfig?.prompt_variables || [], caculatedPromptVariables)
|
|
|
- }
|
|
|
+
|
|
|
// console.log(isPublicVersion)
|
|
|
const openstatement = {
|
|
|
id: `${Date.now()}`,
|
|
|
content: caculatedIntroduction,
|
|
|
isAnswer: true,
|
|
|
feedbackDisabled: true,
|
|
|
- isOpeningStatement: isPublicVersion
|
|
|
+ isOpeningStatement: isPublicVersion,
|
|
|
}
|
|
|
- if (caculatedIntroduction) {
|
|
|
+ if (caculatedIntroduction)
|
|
|
return [openstatement]
|
|
|
- }
|
|
|
+
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
const fetchInitData = () => {
|
|
|
- return Promise.all([isInstalledApp ? {
|
|
|
- app_id: installedAppInfo?.id,
|
|
|
- site: {
|
|
|
- title: installedAppInfo?.app.name,
|
|
|
- prompt_public: false,
|
|
|
- copyright: ''
|
|
|
- },
|
|
|
- plan: 'basic',
|
|
|
- }: fetchAppInfo(), fetchConversations(isInstalledApp, installedAppInfo?.id), fetchAppParams(isInstalledApp, installedAppInfo?.id)])
|
|
|
+ return Promise.all([isInstalledApp
|
|
|
+ ? {
|
|
|
+ app_id: installedAppInfo?.id,
|
|
|
+ site: {
|
|
|
+ title: installedAppInfo?.app.name,
|
|
|
+ prompt_public: false,
|
|
|
+ copyright: '',
|
|
|
+ },
|
|
|
+ plan: 'basic',
|
|
|
+ }
|
|
|
+ : fetchAppInfo(), fetchConversations(isInstalledApp, installedAppInfo?.id), fetchAppParams(isInstalledApp, installedAppInfo?.id)])
|
|
|
}
|
|
|
|
|
|
-
|
|
|
// init
|
|
|
useEffect(() => {
|
|
|
(async () => {
|
|
@@ -255,16 +255,16 @@ const Main: FC<IMainProps> = ({
|
|
|
setIsPublicVersion(tempIsPublicVersion)
|
|
|
const prompt_template = ''
|
|
|
// handle current conversation id
|
|
|
- const { data: conversations, has_more } = conversationData as { data: ConversationItem[], has_more: boolean }
|
|
|
+ const { data: conversations, has_more } = conversationData as { data: ConversationItem[]; has_more: boolean }
|
|
|
const _conversationId = getConversationIdFromStorage(appId)
|
|
|
const isNotNewConversation = conversations.some(item => item.id === _conversationId)
|
|
|
setHasMore(has_more)
|
|
|
// fetch new conversation info
|
|
|
const { user_input_form, opening_statement: introduction, suggested_questions_after_answer }: any = appParams
|
|
|
const prompt_variables = userInputsFormToPromptVariables(user_input_form)
|
|
|
- if(siteInfo.default_language) {
|
|
|
+ if (siteInfo.default_language)
|
|
|
changeLanguage(siteInfo.default_language)
|
|
|
- }
|
|
|
+
|
|
|
setNewConversationInfo({
|
|
|
name: t('share.chat.newChatDefaultName'),
|
|
|
introduction,
|
|
@@ -272,20 +272,22 @@ const Main: FC<IMainProps> = ({
|
|
|
setSiteInfo(siteInfo as SiteInfo)
|
|
|
setPromptConfig({
|
|
|
prompt_template,
|
|
|
- prompt_variables: prompt_variables,
|
|
|
+ prompt_variables,
|
|
|
} as PromptConfig)
|
|
|
setSuggestedQuestionsAfterAnswerConfig(suggested_questions_after_answer)
|
|
|
|
|
|
setConversationList(conversations as ConversationItem[])
|
|
|
|
|
|
- if (isNotNewConversation) {
|
|
|
+ if (isNotNewConversation)
|
|
|
setCurrConversationId(_conversationId, appId, false)
|
|
|
- }
|
|
|
+
|
|
|
setInited(true)
|
|
|
- } catch (e: any) {
|
|
|
+ }
|
|
|
+ catch (e: any) {
|
|
|
if (e.status === 404) {
|
|
|
setAppUnavailable(true)
|
|
|
- } else {
|
|
|
+ }
|
|
|
+ else {
|
|
|
setIsUnknwonReason(true)
|
|
|
setAppUnavailable(true)
|
|
|
}
|
|
@@ -303,21 +305,20 @@ const Main: FC<IMainProps> = ({
|
|
|
const checkCanSend = () => {
|
|
|
const prompt_variables = promptConfig?.prompt_variables
|
|
|
const inputs = currInputs
|
|
|
- if (!inputs || !prompt_variables || prompt_variables?.length === 0) {
|
|
|
+ if (!inputs || !prompt_variables || prompt_variables?.length === 0)
|
|
|
return true
|
|
|
- }
|
|
|
+
|
|
|
let hasEmptyInput = false
|
|
|
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
|
|
|
requiredVars.forEach(({ key }) => {
|
|
|
- if (hasEmptyInput) {
|
|
|
+ if (hasEmptyInput)
|
|
|
return
|
|
|
- }
|
|
|
- if (!inputs?.[key]) {
|
|
|
+
|
|
|
+ if (!inputs?.[key])
|
|
|
hasEmptyInput = true
|
|
|
- }
|
|
|
})
|
|
|
|
|
|
if (hasEmptyInput) {
|
|
@@ -378,9 +379,8 @@ const Main: FC<IMainProps> = ({
|
|
|
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId }: any) => {
|
|
|
responseItem.content = responseItem.content + message
|
|
|
responseItem.id = messageId
|
|
|
- if (isFirstMessage && newConversationId) {
|
|
|
+ if (isFirstMessage && newConversationId)
|
|
|
tempNewConversationId = newConversationId
|
|
|
- }
|
|
|
|
|
|
// closesure new list is outdated.
|
|
|
const newListWithAnswer = produce(
|
|
@@ -395,9 +395,9 @@ const Main: FC<IMainProps> = ({
|
|
|
},
|
|
|
async onCompleted(hasError?: boolean) {
|
|
|
setResponsingFalse()
|
|
|
- if (hasError) {
|
|
|
+ if (hasError)
|
|
|
return
|
|
|
- }
|
|
|
+
|
|
|
let currChatList = conversationList
|
|
|
if (getConversationIdChangeBecauseOfNew()) {
|
|
|
const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppInfo?.id)
|
|
@@ -418,7 +418,7 @@ const Main: FC<IMainProps> = ({
|
|
|
onError() {
|
|
|
setResponsingFalse()
|
|
|
// role back placeholder answer
|
|
|
- setChatList(produce(getChatList(), draft => {
|
|
|
+ setChatList(produce(getChatList(), (draft) => {
|
|
|
draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1)
|
|
|
}))
|
|
|
},
|
|
@@ -476,18 +476,20 @@ const Main: FC<IMainProps> = ({
|
|
|
onCreateNewChat={() => handleConversationIdChange('-1')}
|
|
|
/>
|
|
|
)}
|
|
|
-
|
|
|
+
|
|
|
{/* {isNewConversation ? 'new' : 'exist'}
|
|
|
{JSON.stringify(newConversationInputs ? newConversationInputs : {})}
|
|
|
{JSON.stringify(existConversationInputs ? existConversationInputs : {})} */}
|
|
|
- <div
|
|
|
+ <div
|
|
|
className={cn(
|
|
|
- "flex rounded-t-2xl bg-white overflow-hidden",
|
|
|
+ 'flex rounded-t-2xl bg-white overflow-hidden',
|
|
|
isInstalledApp && 'rounded-b-2xl',
|
|
|
)}
|
|
|
- style={isInstalledApp ? {
|
|
|
- boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)'
|
|
|
- } : {}}
|
|
|
+ style={isInstalledApp
|
|
|
+ ? {
|
|
|
+ boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)',
|
|
|
+ }
|
|
|
+ : {}}
|
|
|
>
|
|
|
{/* sidebar */}
|
|
|
{!isMobile && renderSidebar()}
|
|
@@ -504,8 +506,8 @@ const Main: FC<IMainProps> = ({
|
|
|
{/* main */}
|
|
|
<div className={cn(
|
|
|
isInstalledApp ? s.installedApp : 'h-[calc(100vh_-_3rem)]',
|
|
|
- 'flex-grow flex flex-col overflow-y-auto'
|
|
|
- )
|
|
|
+ 'flex-grow flex flex-col overflow-y-auto',
|
|
|
+ )
|
|
|
}>
|
|
|
<ConfigSence
|
|
|
conversationName={conversationName}
|
|
@@ -522,7 +524,7 @@ const Main: FC<IMainProps> = ({
|
|
|
|
|
|
{
|
|
|
hasSetInputs && (
|
|
|
- <div className={cn(doShowSuggestion ? 'pb-[140px]' : 'pb-[66px]', 'relative grow h-[200px] pc:w-[794px] max-w-full mobile:w-full mx-auto mb-3.5 overflow-hidden')}>
|
|
|
+ <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[66px]'), 'relative grow h-[200px] pc:w-[794px] max-w-full mobile:w-full mx-auto mb-3.5 overflow-hidden')}>
|
|
|
<div className='h-full overflow-y-auto' ref={chatListDomRef}>
|
|
|
<Chat
|
|
|
chatList={chatList}
|