123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823 |
- /* 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, { setAutoFreeze } from 'immer'
- import { useBoolean, useGetState } from 'ahooks'
- import { checkOrSetAccessToken } from '../utils'
- import AppUnavailable from '../../base/app-unavailable'
- import useConversation from './hooks/use-conversation'
- import { ToastContext } from '@/app/components/base/toast'
- import ConfigScene from '@/app/components/share/chatbot/config-scence'
- import Header from '@/app/components/share/header'
- import {
- fetchAppInfo,
- fetchAppMeta,
- fetchAppParams,
- fetchChatList,
- fetchConversations,
- fetchSuggestedQuestions,
- generationConversationName,
- sendChatMessage,
- stopChatMessageResponding,
- updateFeedback,
- } from '@/service/share'
- import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
- import type { AppMeta, ConversationItem, SiteInfo } from '@/models/share'
- import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
- import type { Feedbacktype, IChatItem } from '@/app/components/app/chat/type'
- 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'
- import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
- import LogoHeader from '@/app/components/base/logo/logo-embeded-chat-header'
- import LogoAvatar from '@/app/components/base/logo/logo-embeded-chat-avatar'
- import type { VisionFile, VisionSettings } from '@/types/app'
- import { Resolution, TransferMethod } from '@/types/app'
- import type { Annotation as AnnotationType } from '@/models/log'
- 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
- const [canReplaceLogo, setCanReplaceLogo] = useState<boolean>(false)
- const [customConfig, setCustomConfig] = useState<any>(null)
- const [appMeta, setAppMeta] = useState<AppMeta | null>(null)
- // 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 (canReplaceLogo)
- document.title = `${siteInfo.title}`
- else
- document.title = `${siteInfo.title} - Powered by Dify`
- }
- }, [siteInfo?.title, canReplaceLogo])
- // onData change thought (the produce obj). https://github.com/immerjs/immer/issues/576
- useEffect(() => {
- setAutoFreeze(false)
- return () => {
- setAutoFreeze(true)
- }
- }, [])
- /*
- * 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,
- getCurrConversationId,
- 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 [textToSpeechConfig, setTextToSpeechConfig] = useState<SuggestedQuestionsAfterAnswerConfig | null>(null)
- const [citationConfig, setCitationConfig] = 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 && !isResponding) {
- 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,
- message_files: item.message_files?.filter((file: any) => file.belongs_to === 'user') || [],
- })
- newChatList.push({
- id: item.id,
- content: item.answer,
- agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files),
- feedback: item.feedback,
- isAnswer: true,
- citation: item.retriever_resources,
- message_files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [],
- })
- })
- 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()
- setRespondingFalse()
- 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), fetchAppMeta(isInstalledApp, installedAppInfo?.id)])
- }
- // init
- useEffect(() => {
- (async () => {
- try {
- const [appData, conversationData, appParams, appMeta]: any = await fetchInitData()
- setAppMeta(appMeta)
- const { app_id: appId, site: siteInfo, plan, can_replace_logo, custom_config }: any = appData
- setAppId(appId)
- setPlan(plan)
- setCanReplaceLogo(can_replace_logo)
- setCustomConfig(custom_config)
- 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
- const { user_input_form, opening_statement: introduction, suggested_questions_after_answer, speech_to_text, text_to_speech, retriever_resource, file_upload, sensitive_word_avoidance }: any = appParams
- setVisionConfig({
- ...file_upload.image,
- image_file_size_limit: appParams?.system_parameters?.image_file_size_limit,
- })
- 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)
- setTextToSpeechConfig(text_to_speech)
- setCitationConfig(retriever_resource)
- // 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 [isResponding, { setTrue: setRespondingTrue, setFalse: setRespondingFalse }] = 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
- let hasEmptyInput = ''
- 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, name }) => {
- if (hasEmptyInput)
- return
- if (!inputs?.[key])
- hasEmptyInput = name
- })
- if (hasEmptyInput) {
- logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
- return false
- }
- return !hasEmptyInput
- }
- const [controlFocus, setControlFocus] = useState(0)
- const [isShowSuggestion, setIsShowSuggestion] = useState(false)
- const doShowSuggestion = isShowSuggestion && !isResponding
- const [suggestQuestions, setSuggestQuestions] = useState<string[]>([])
- const [messageTaskId, setMessageTaskId] = useState('')
- const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
- const [isRespondingConIsCurrCon, setIsRespondingConCurrCon, getIsRespondingConIsCurrCon] = useGetState(true)
- const [shouldReload, setShouldReload] = useState(false)
- const [userQuery, setUserQuery] = useState('')
- const [visionConfig, setVisionConfig] = useState<VisionSettings>({
- enabled: false,
- number_limits: 2,
- detail: Resolution.low,
- transfer_methods: [TransferMethod.local_file],
- })
- const updateCurrentQA = ({
- responseItem,
- questionId,
- placeholderAnswerId,
- questionItem,
- }: {
- responseItem: IChatItem
- questionId: string
- placeholderAnswerId: string
- questionItem: IChatItem
- }) => {
- // 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)
- }
- const handleSend = async (message: string, files?: VisionFile[]) => {
- if (isResponding) {
- notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
- return
- }
- if (files?.find(item => item.transfer_method === TransferMethod.local_file && !item.upload_file_id)) {
- notify({ type: 'info', message: t('appDebug.errorMessage.waitForImgUpload') })
- return false
- }
- const data: Record<string, any> = {
- inputs: currInputs,
- query: message,
- conversation_id: isNewConversation ? null : currConversationId,
- }
- if (visionConfig.enabled && files && files?.length > 0) {
- data.files = files.map((item) => {
- if (item.transfer_method === TransferMethod.local_file) {
- return {
- ...item,
- url: '',
- }
- }
- return item
- })
- }
- // qustion
- const questionId = `question-${Date.now()}`
- const questionItem = {
- id: questionId,
- content: message,
- isAnswer: false,
- message_files: files,
- }
- const placeholderAnswerId = `answer-placeholder-${Date.now()}`
- const placeholderAnswerItem = {
- id: placeholderAnswerId,
- content: '',
- isAnswer: true,
- }
- const newList = [...getChatList(), questionItem, placeholderAnswerItem]
- setChatList(newList)
- let isAgentMode = false
- // answer
- const responseItem: IChatItem = {
- id: `${Date.now()}`,
- content: '',
- agent_thoughts: [],
- message_files: [],
- isAnswer: true,
- }
- let hasSetResponseId = false
- const prevTempNewConversationId = getCurrConversationId() || '-1'
- let tempNewConversationId = prevTempNewConversationId
- setHasStopResponded(false)
- setRespondingTrue()
- setIsShowSuggestion(false)
- sendChatMessage(data, {
- getAbortController: (abortController) => {
- setAbortController(abortController)
- },
- onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
- if (!isAgentMode) {
- responseItem.content = responseItem.content + message
- }
- else {
- const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
- if (lastThought)
- lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
- }
- if (messageId && !hasSetResponseId) {
- responseItem.id = messageId
- hasSetResponseId = true
- }
- if (isFirstMessage && newConversationId)
- tempNewConversationId = newConversationId
- setMessageTaskId(taskId)
- // has switched to other conversation
- if (prevTempNewConversationId !== getCurrConversationId()) {
- setIsRespondingConCurrCon(false)
- return
- }
- updateCurrentQA({
- responseItem,
- questionId,
- placeholderAnswerId,
- questionItem,
- })
- },
- async onCompleted(hasError?: boolean) {
- if (hasError)
- return
- if (getConversationIdChangeBecauseOfNew()) {
- const { data: allConversations }: any = await fetchAllConversations()
- const newItem: any = await generationConversationName(isInstalledApp, installedAppInfo?.id, allConversations[0].id)
- const newAllConversations = produce(allConversations, (draft: any) => {
- draft[0].name = newItem.name
- })
- setAllConversationList(newAllConversations as any)
- 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)
- }
- setRespondingFalse()
- },
- onFile(file) {
- const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
- if (lastThought)
- lastThought.message_files = [...(lastThought as any).message_files, { ...file }]
- updateCurrentQA({
- responseItem,
- questionId,
- placeholderAnswerId,
- questionItem,
- })
- },
- onThought(thought) {
- isAgentMode = true
- const response = responseItem as any
- if (thought.message_id && !hasSetResponseId) {
- response.id = thought.message_id
- hasSetResponseId = true
- }
- // responseItem.id = thought.message_id;
- if (response.agent_thoughts.length === 0) {
- response.agent_thoughts.push(thought)
- }
- else {
- const lastThought = response.agent_thoughts[response.agent_thoughts.length - 1]
- // thought changed but still the same thought, so update.
- if (lastThought.id === thought.id) {
- thought.thought = lastThought.thought
- thought.message_files = lastThought.message_files
- responseItem.agent_thoughts![response.agent_thoughts.length - 1] = thought
- }
- else {
- responseItem.agent_thoughts!.push(thought)
- }
- }
- // has switched to other conversation
- if (prevTempNewConversationId !== getCurrConversationId()) {
- setIsRespondingConCurrCon(false)
- return false
- }
- updateCurrentQA({
- responseItem,
- questionId,
- placeholderAnswerId,
- questionItem,
- })
- },
- onMessageEnd: (messageEnd) => {
- if (messageEnd.metadata?.annotation_reply) {
- responseItem.id = messageEnd.id
- responseItem.annotation = ({
- id: messageEnd.metadata.annotation_reply.id,
- authorName: messageEnd.metadata.annotation_reply.account.name,
- } as AnnotationType)
- 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)
- return
- }
- // not support show citation
- // responseItem.citation = messageEnd.retriever_resources
- if (!isInstalledApp)
- return
- 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)
- },
- onMessageReplace: (messageReplace) => {
- if (isInstalledApp) {
- responseItem.content = messageReplace.answer
- }
- else {
- setChatList(produce(
- getChatList(),
- (draft) => {
- const current = draft.find(item => item.id === messageReplace.id)
- if (current)
- current.content = messageReplace.answer
- },
- ))
- }
- },
- onError() {
- setRespondingFalse()
- // 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') })
- }
- const handleReload = () => {
- setCurrConversationId('-1', appId, false)
- setChatNotStarted()
- setShouldReload(false)
- createNewChat()
- }
- const handleConversationIdChange = (id: string) => {
- if (id === '-1') {
- createNewChat()
- setConversationIdChangeBecauseOfNew(true)
- }
- else {
- setConversationIdChangeBecauseOfNew(false)
- }
- // trigger handleConversationSwitch
- setCurrConversationId(id, appId)
- setIsShowSuggestion(false)
- }
- const difyIcon = (
- <LogoHeader />
- )
- if (appUnavailable)
- return <AppUnavailable isUnknwonReason={isUnknwonReason} />
- if (!appId || !siteInfo || !promptConfig) {
- return <div className='flex h-screen w-full'>
- <Loading type='app' />
- </div>
- }
- return (
- <div>
- <Header
- title={siteInfo.title}
- icon=''
- customerIcon={difyIcon}
- icon_background={siteInfo.icon_background}
- isEmbedScene={true}
- isMobile={isMobile}
- onCreateNewChat={() => handleConversationIdChange('-1')}
- />
- <div className={'flex bg-white overflow-hidden'}>
- <div className={cn(
- isInstalledApp ? 'h-full' : 'h-[calc(100vh_-_3rem)]',
- '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}
- canReplaceLogo={canReplaceLogo}
- customConfig={customConfig}
- ></ConfigScene>
- {
- 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>
- )
- }
- {
- hasSetInputs && (
- <div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponding ? 'pb-[113px]' : 'pb-[76px]'), '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}
- query={userQuery}
- onQueryChange={setUserQuery}
- onSend={handleSend}
- isHideFeedbackEdit
- onFeedback={handleFeedback}
- isResponding={isResponding}
- canStopResponding={!!messageTaskId && isRespondingConIsCurrCon}
- abortResponding={async () => {
- await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id)
- setHasStopResponded(true)
- setRespondingFalse()
- }}
- checkCanSend={checkCanSend}
- controlFocus={controlFocus}
- isShowSuggestion={doShowSuggestion}
- suggestionList={suggestQuestions}
- displayScene='web'
- isShowSpeechToText={speechToTextConfig?.enabled}
- isShowTextToSpeech={textToSpeechConfig?.enabled}
- isShowCitation={citationConfig?.enabled && isInstalledApp}
- answerIcon={<LogoAvatar className='relative shrink-0' />}
- visionConfig={visionConfig}
- allToolIcons={appMeta?.tool_icons || {}}
- />
- </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)
|