mirror of
https://github.com/langgenius/dify.git
synced 2026-07-30 00:39:34 +08:00
651 lines
23 KiB
TypeScript
651 lines
23 KiB
TypeScript
import type { ExtraContent } from '../chat/type'
|
|
import type { Callback, ChatConfig, ChatItem, Feedback } from '../types'
|
|
import type { InstalledApp } from '@/models/explore'
|
|
import type { AppData, ConversationItem } from '@/models/share'
|
|
import type { HumanInputFilledFormData, HumanInputFormData } from '@/types/workflow'
|
|
import { toast } from '@langgenius/dify-ui/toast'
|
|
import { noop } from 'es-toolkit/function'
|
|
import { produce } from 'immer'
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import {
|
|
useConversationIdInfo,
|
|
useWebAppSidebarCollapseState,
|
|
} from '@/app/components/base/chat/storage'
|
|
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
|
import { InputVarType } from '@/app/components/workflow/types'
|
|
import { useWebAppStore } from '@/context/web-app-context'
|
|
import { useAppFavicon } from '@/hooks/use-app-favicon'
|
|
import { changeLanguage } from '@/i18n-config/client'
|
|
import {
|
|
AppSourceType,
|
|
delConversation,
|
|
pinConversation,
|
|
renameConversation,
|
|
unpinConversation,
|
|
updateFeedback,
|
|
} from '@/service/share'
|
|
import {
|
|
useInvalidateShareConversations,
|
|
useShareChatList,
|
|
useShareConversationName,
|
|
useShareConversations,
|
|
} from '@/service/use-share'
|
|
import { TransferMethod } from '@/types/app'
|
|
import { addFileInfos, sortAgentSorts } from '../../../tools/utils'
|
|
import { enrichSubmittedHumanInputFormData } from '../chat/answer/human-input-content/submitted-utils'
|
|
import {
|
|
buildChatItemTree,
|
|
getProcessedSystemVariablesFromUrlParams,
|
|
getRawInputsFromUrlParams,
|
|
getRawUserVariablesFromUrlParams,
|
|
} from '../utils'
|
|
|
|
function getFormattedChatList(messages: any[]) {
|
|
const newChatList: ChatItem[] = []
|
|
messages.forEach((item) => {
|
|
const questionFiles =
|
|
item.message_files?.filter((file: any) => file.belongs_to === 'user') || []
|
|
newChatList.push({
|
|
id: `question-${item.id}`,
|
|
content: item.query,
|
|
isAnswer: false,
|
|
message_files: getProcessedFilesFromResponse(
|
|
questionFiles.map((item: any) => ({
|
|
...item,
|
|
related_id: item.id,
|
|
upload_file_id: item.upload_file_id,
|
|
})),
|
|
),
|
|
parentMessageId: item.parent_message_id || undefined,
|
|
})
|
|
const answerFiles =
|
|
item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || []
|
|
const answerTokens = item.answer_tokens ?? 0
|
|
const messageTokens = item.message_tokens ?? 0
|
|
const latency = Number(item.provider_response_latency)
|
|
const more =
|
|
item.provider_response_latency == null || !Number.isFinite(latency)
|
|
? undefined
|
|
: {
|
|
time: '',
|
|
tokens: answerTokens + messageTokens,
|
|
latency: latency.toFixed(2),
|
|
tokens_per_second: latency > 0 ? (answerTokens / latency).toFixed(2) : undefined,
|
|
}
|
|
const humanInputFormDataList: HumanInputFormData[] = []
|
|
const humanInputFilledFormDataList: HumanInputFilledFormData[] = []
|
|
let workflowRunId = ''
|
|
item.extra_contents?.forEach((content: ExtraContent) => {
|
|
if (content.type !== 'human_input') return
|
|
|
|
const formDefinition = 'form_definition' in content ? content.form_definition : undefined
|
|
if (!content.submitted) {
|
|
if (!formDefinition) return
|
|
humanInputFormDataList.push(formDefinition)
|
|
workflowRunId = content.workflow_run_id || workflowRunId
|
|
return
|
|
}
|
|
|
|
if (!('form_submission_data' in content) || !content.form_submission_data) return
|
|
const currentFormIndex = humanInputFormDataList.findIndex(
|
|
(item) => item.node_id === content.form_submission_data.node_id,
|
|
)
|
|
const requiredFormData =
|
|
formDefinition ||
|
|
(currentFormIndex > -1 ? humanInputFormDataList[currentFormIndex] : undefined)
|
|
if (currentFormIndex > -1) humanInputFormDataList.splice(currentFormIndex, 1)
|
|
workflowRunId = content.workflow_run_id || workflowRunId
|
|
humanInputFilledFormDataList.push(
|
|
enrichSubmittedHumanInputFormData(content.form_submission_data, requiredFormData),
|
|
)
|
|
})
|
|
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,
|
|
reasoningContent: item.metadata?.reasoning,
|
|
reasoningFinished: true,
|
|
message_files: getProcessedFilesFromResponse(
|
|
answerFiles.map((item: any) => ({
|
|
...item,
|
|
related_id: item.id,
|
|
upload_file_id: item.upload_file_id,
|
|
})),
|
|
),
|
|
parentMessageId: `question-${item.id}`,
|
|
humanInputFormDataList,
|
|
humanInputFilledFormDataList,
|
|
workflow_run_id: workflowRunId,
|
|
more,
|
|
})
|
|
})
|
|
return newChatList
|
|
}
|
|
export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|
const isInstalledApp = useMemo(() => !!installedAppInfo, [installedAppInfo])
|
|
const appSourceType = isInstalledApp ? AppSourceType.installedApp : AppSourceType.webApp
|
|
const appInfo = useWebAppStore((s) => s.appInfo)
|
|
const appParams = useWebAppStore((s) => s.appParams)
|
|
const appMeta = useWebAppStore((s) => s.appMeta)
|
|
useAppFavicon({
|
|
enable: !installedAppInfo,
|
|
icon_type: appInfo?.site.icon_type,
|
|
icon: appInfo?.site.icon,
|
|
icon_background: appInfo?.site.icon_background,
|
|
icon_url: appInfo?.site.icon_url,
|
|
})
|
|
const appData = useMemo(() => {
|
|
if (isInstalledApp) {
|
|
const { id, app } = installedAppInfo!
|
|
return {
|
|
app_id: id,
|
|
site: {
|
|
title: app.name,
|
|
description: app.description,
|
|
icon_type: app.icon_type,
|
|
icon: app.icon,
|
|
icon_background: app.icon_background,
|
|
icon_url: app.icon_url,
|
|
prompt_public: false,
|
|
copyright: '',
|
|
show_workflow_steps: true,
|
|
use_icon_as_answer_icon: app.use_icon_as_answer_icon,
|
|
},
|
|
plan: 'basic',
|
|
custom_config: null,
|
|
} as AppData
|
|
}
|
|
return appInfo
|
|
}, [isInstalledApp, installedAppInfo, appInfo])
|
|
const appId = useMemo(() => appData?.app_id, [appData])
|
|
const [userId, setUserId] = useState<string>()
|
|
useEffect(() => {
|
|
getProcessedSystemVariablesFromUrlParams().then(({ user_id }) => {
|
|
setUserId(user_id)
|
|
})
|
|
}, [])
|
|
useEffect(() => {
|
|
const setLocaleFromProps = async () => {
|
|
if (appData?.site.default_language) await changeLanguage(appData.site.default_language)
|
|
}
|
|
setLocaleFromProps()
|
|
}, [appData])
|
|
const [storedSidebarCollapseState, setStoredSidebarCollapseState] =
|
|
useWebAppSidebarCollapseState()
|
|
const sidebarCollapseState = storedSidebarCollapseState === 'collapsed'
|
|
const handleSidebarCollapse = useCallback(
|
|
(state: boolean) => {
|
|
if (appId) setStoredSidebarCollapseState(state ? 'collapsed' : 'expanded')
|
|
},
|
|
[appId, setStoredSidebarCollapseState],
|
|
)
|
|
const [conversationIdInfo, setConversationIdInfo] = useConversationIdInfo()
|
|
const currentConversationId = useMemo(
|
|
() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || '',
|
|
[appId, conversationIdInfo, userId],
|
|
)
|
|
const handleConversationIdInfoChange = useCallback(
|
|
(changeConversationId: string) => {
|
|
if (appId) {
|
|
let prevValue = conversationIdInfo?.[appId || '']
|
|
if (typeof prevValue === 'string') prevValue = {}
|
|
setConversationIdInfo({
|
|
...conversationIdInfo,
|
|
[appId || '']: {
|
|
...prevValue,
|
|
[userId || 'DEFAULT']: changeConversationId,
|
|
},
|
|
})
|
|
}
|
|
},
|
|
[appId, conversationIdInfo, setConversationIdInfo, userId],
|
|
)
|
|
const [newConversationId, setNewConversationId] = useState('')
|
|
const chatShouldReloadKey = useMemo(() => {
|
|
if (currentConversationId === newConversationId) return ''
|
|
return currentConversationId
|
|
}, [currentConversationId, newConversationId])
|
|
const { data: appPinnedConversationData } = useShareConversations(
|
|
{
|
|
appSourceType,
|
|
appId,
|
|
pinned: true,
|
|
limit: 100,
|
|
},
|
|
{
|
|
enabled: !!appId,
|
|
refetchOnWindowFocus: false,
|
|
refetchOnReconnect: false,
|
|
},
|
|
)
|
|
const { data: appConversationData, isLoading: appConversationDataLoading } =
|
|
useShareConversations(
|
|
{
|
|
appSourceType,
|
|
appId,
|
|
pinned: false,
|
|
limit: 100,
|
|
},
|
|
{
|
|
enabled: !!appId,
|
|
refetchOnWindowFocus: false,
|
|
refetchOnReconnect: false,
|
|
},
|
|
)
|
|
const { data: appChatListData, isLoading: appChatListDataLoading } = useShareChatList(
|
|
{
|
|
conversationId: chatShouldReloadKey,
|
|
appSourceType,
|
|
appId,
|
|
},
|
|
{
|
|
enabled: !!chatShouldReloadKey,
|
|
refetchOnWindowFocus: false,
|
|
refetchOnReconnect: false,
|
|
},
|
|
)
|
|
const invalidateShareConversations = useInvalidateShareConversations()
|
|
const [clearChatList, setClearChatList] = useState(false)
|
|
const [isResponding, setIsResponding] = useState(false)
|
|
const appPrevChatTree = useMemo(
|
|
() =>
|
|
currentConversationId && appChatListData?.data.length
|
|
? buildChatItemTree(getFormattedChatList(appChatListData.data))
|
|
: [],
|
|
[appChatListData, currentConversationId],
|
|
)
|
|
const [showNewConversationItemInList, setShowNewConversationItemInList] = useState(false)
|
|
const pinnedConversationList = useMemo(() => {
|
|
return appPinnedConversationData?.data || []
|
|
}, [appPinnedConversationData])
|
|
const { t } = useTranslation()
|
|
const newConversationInputsRef = useRef<Record<string, any>>({})
|
|
const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any>>({})
|
|
const [initInputs, setInitInputs] = useState<Record<string, any>>({})
|
|
const [initUserVariables, setInitUserVariables] = useState<Record<string, any>>({})
|
|
const handleNewConversationInputsChange = useCallback((newInputs: Record<string, any>) => {
|
|
newConversationInputsRef.current = newInputs
|
|
// oxlint-disable-next-line eslint-react/set-state-in-effect -- This handler intentionally syncs derived input defaults when called from the reset effect below.
|
|
setNewConversationInputs(newInputs)
|
|
}, [])
|
|
const inputsForms = useMemo(() => {
|
|
return (appParams?.user_input_form || [])
|
|
.filter((item: any) => !item.external_data_tool)
|
|
.map((item: any) => {
|
|
if (item.paragraph) {
|
|
let value = initInputs[item.paragraph.variable]
|
|
if (value && item.paragraph.max_length && value.length > item.paragraph.max_length)
|
|
value = value.slice(0, item.paragraph.max_length)
|
|
return {
|
|
...item.paragraph,
|
|
default: value || item.default || item.paragraph.default,
|
|
type: 'paragraph',
|
|
}
|
|
}
|
|
if (item.number) {
|
|
const convertedNumber = Number(initInputs[item.number.variable])
|
|
return {
|
|
...item.number,
|
|
default: convertedNumber || item.default || item.number.default,
|
|
type: 'number',
|
|
}
|
|
}
|
|
if (item.checkbox) {
|
|
const preset = initInputs[item.checkbox.variable] === true
|
|
return {
|
|
...item.checkbox,
|
|
default: preset || item.default || item.checkbox.default,
|
|
type: 'checkbox',
|
|
}
|
|
}
|
|
if (item.select) {
|
|
const isInputInOptions = item.select.options.includes(initInputs[item.select.variable])
|
|
return {
|
|
...item.select,
|
|
default:
|
|
(isInputInOptions ? initInputs[item.select.variable] : undefined) ||
|
|
item.select.default,
|
|
type: 'select',
|
|
}
|
|
}
|
|
if (item['file-list']) {
|
|
return {
|
|
...item['file-list'],
|
|
type: 'file-list',
|
|
}
|
|
}
|
|
if (item.file) {
|
|
return {
|
|
...item.file,
|
|
type: 'file',
|
|
}
|
|
}
|
|
if (item.json_object) {
|
|
return {
|
|
...item.json_object,
|
|
type: 'json_object',
|
|
}
|
|
}
|
|
let value = initInputs[item['text-input'].variable]
|
|
if (value && item['text-input'].max_length && value.length > item['text-input'].max_length)
|
|
value = value.slice(0, item['text-input'].max_length)
|
|
return {
|
|
...item['text-input'],
|
|
default: value || item.default || item['text-input'].default,
|
|
type: 'text-input',
|
|
}
|
|
})
|
|
}, [initInputs, appParams])
|
|
const allInputsHidden = useMemo(() => {
|
|
return inputsForms.length > 0 && inputsForms.every((item) => item.hide === true)
|
|
}, [inputsForms])
|
|
useEffect(() => {
|
|
// init inputs from url params
|
|
;(async () => {
|
|
const inputs = await getRawInputsFromUrlParams()
|
|
const userVariables = await getRawUserVariablesFromUrlParams()
|
|
setInitInputs(inputs)
|
|
setInitUserVariables(userVariables)
|
|
})()
|
|
}, [])
|
|
useEffect(() => {
|
|
const conversationInputs: Record<string, any> = {}
|
|
inputsForms.forEach((item: any) => {
|
|
conversationInputs[item.variable] = item.default || null
|
|
})
|
|
handleNewConversationInputsChange(conversationInputs)
|
|
}, [handleNewConversationInputsChange, inputsForms])
|
|
const { data: newConversation } = useShareConversationName(
|
|
{
|
|
conversationId: newConversationId,
|
|
appSourceType,
|
|
appId,
|
|
},
|
|
{
|
|
refetchOnWindowFocus: false,
|
|
enabled: !!newConversationId,
|
|
},
|
|
)
|
|
const [originConversationList, setOriginConversationList] = useState<ConversationItem[]>([])
|
|
useEffect(() => {
|
|
if (appConversationData?.data && !appConversationDataLoading)
|
|
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Conversation query results intentionally replace the local editable list.
|
|
setOriginConversationList(appConversationData?.data)
|
|
}, [appConversationData, appConversationDataLoading])
|
|
const conversationList = useMemo(() => {
|
|
const data = originConversationList.slice()
|
|
if (showNewConversationItemInList && data[0]?.id !== '') {
|
|
data.unshift({
|
|
id: '',
|
|
name: t(($) => $['chat.newChatDefaultName'], { ns: 'share' }),
|
|
inputs: {},
|
|
introduction: '',
|
|
})
|
|
}
|
|
return data
|
|
}, [originConversationList, showNewConversationItemInList, t])
|
|
useEffect(() => {
|
|
if (newConversation) {
|
|
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Newly resolved conversation names intentionally patch the local list cache.
|
|
setOriginConversationList(
|
|
produce((draft) => {
|
|
const index = draft.findIndex((item) => item.id === newConversation.id)
|
|
if (index > -1) draft[index] = newConversation
|
|
else draft.unshift(newConversation)
|
|
}),
|
|
)
|
|
}
|
|
}, [newConversation])
|
|
const currentConversationItem = useMemo(() => {
|
|
let conversationItem = conversationList.find((item) => item.id === currentConversationId)
|
|
if (!conversationItem && pinnedConversationList.length)
|
|
conversationItem = pinnedConversationList.find((item) => item.id === currentConversationId)
|
|
return conversationItem
|
|
}, [conversationList, currentConversationId, pinnedConversationList])
|
|
const currentConversationLatestInputs = useMemo(() => {
|
|
if (!currentConversationId || !appChatListData?.data.length)
|
|
return newConversationInputsRef.current || {}
|
|
return appChatListData.data.slice().pop().inputs || {}
|
|
}, [appChatListData, currentConversationId])
|
|
const [currentConversationInputs, setCurrentConversationInputs] = useState<Record<string, any>>(
|
|
currentConversationLatestInputs || {},
|
|
)
|
|
useEffect(() => {
|
|
if (currentConversationItem)
|
|
// oxlint-disable-next-line eslint-react/set-state-in-effect -- Selected conversation changes intentionally resync the editable input snapshot.
|
|
setCurrentConversationInputs(currentConversationLatestInputs || {})
|
|
}, [currentConversationItem, currentConversationLatestInputs])
|
|
const checkInputsRequired = useCallback(
|
|
(silent?: boolean) => {
|
|
if (allInputsHidden) return true
|
|
let hasEmptyInput = ''
|
|
let fileIsUploading = false
|
|
const requiredVars = inputsForms.filter(
|
|
({ required, type }) => required && type !== InputVarType.checkbox,
|
|
)
|
|
if (requiredVars.length) {
|
|
requiredVars.forEach(({ variable, label, type }) => {
|
|
if (hasEmptyInput) return
|
|
if (fileIsUploading) return
|
|
if (!newConversationInputsRef.current[variable] && !silent)
|
|
hasEmptyInput = label as string
|
|
if (
|
|
(type === InputVarType.singleFile || type === InputVarType.multiFiles) &&
|
|
newConversationInputsRef.current[variable] &&
|
|
!silent
|
|
) {
|
|
const files = newConversationInputsRef.current[variable]
|
|
if (Array.isArray(files))
|
|
fileIsUploading = files.find(
|
|
(item) => item.transferMethod === TransferMethod.local_file && !item.uploadedId,
|
|
)
|
|
else
|
|
fileIsUploading =
|
|
files.transferMethod === TransferMethod.local_file && !files.uploadedId
|
|
}
|
|
})
|
|
}
|
|
if (hasEmptyInput) {
|
|
toast.error(
|
|
t(($) => $['errorMessage.valueOfVarRequired'], { ns: 'appDebug', key: hasEmptyInput }),
|
|
)
|
|
return false
|
|
}
|
|
if (fileIsUploading) {
|
|
toast.info(t(($) => $['errorMessage.waitForFileUpload'], { ns: 'appDebug' }))
|
|
return
|
|
}
|
|
return true
|
|
},
|
|
[inputsForms, t, allInputsHidden],
|
|
)
|
|
const handleStartChat = useCallback(
|
|
(callback: any) => {
|
|
if (checkInputsRequired()) {
|
|
setShowNewConversationItemInList(true)
|
|
callback?.()
|
|
}
|
|
},
|
|
[setShowNewConversationItemInList, checkInputsRequired],
|
|
)
|
|
const currentChatInstanceRef = useRef<{
|
|
handleStop: () => void
|
|
}>({ handleStop: noop })
|
|
const handleChangeConversation = useCallback(
|
|
(conversationId: string) => {
|
|
currentChatInstanceRef.current.handleStop()
|
|
setNewConversationId('')
|
|
handleConversationIdInfoChange(conversationId)
|
|
if (conversationId) setClearChatList(false)
|
|
},
|
|
[handleConversationIdInfoChange, setClearChatList],
|
|
)
|
|
const handleNewConversation = useCallback(async () => {
|
|
currentChatInstanceRef.current.handleStop()
|
|
setShowNewConversationItemInList(true)
|
|
handleChangeConversation('')
|
|
const conversationInputs: Record<string, any> = {}
|
|
inputsForms.forEach((item: any) => {
|
|
conversationInputs[item.variable] = item.default || null
|
|
})
|
|
handleNewConversationInputsChange(conversationInputs)
|
|
setClearChatList(true)
|
|
}, [
|
|
handleChangeConversation,
|
|
setShowNewConversationItemInList,
|
|
handleNewConversationInputsChange,
|
|
setClearChatList,
|
|
inputsForms,
|
|
])
|
|
const handleUpdateConversationList = useCallback(() => {
|
|
invalidateShareConversations()
|
|
}, [invalidateShareConversations])
|
|
const handlePinConversation = useCallback(
|
|
async (conversationId: string) => {
|
|
await pinConversation(appSourceType, appId, conversationId)
|
|
toast.success(t(($) => $['api.success'], { ns: 'common' }))
|
|
handleUpdateConversationList()
|
|
},
|
|
[appSourceType, appId, t, handleUpdateConversationList],
|
|
)
|
|
const handleUnpinConversation = useCallback(
|
|
async (conversationId: string) => {
|
|
await unpinConversation(appSourceType, appId, conversationId)
|
|
toast.success(t(($) => $['api.success'], { ns: 'common' }))
|
|
handleUpdateConversationList()
|
|
},
|
|
[appSourceType, appId, t, handleUpdateConversationList],
|
|
)
|
|
const [conversationDeleting, setConversationDeleting] = useState(false)
|
|
const handleDeleteConversation = useCallback(
|
|
async (conversationId: string, { onSuccess }: Callback) => {
|
|
if (conversationDeleting) return
|
|
try {
|
|
setConversationDeleting(true)
|
|
await delConversation(appSourceType, appId, conversationId)
|
|
toast.success(t(($) => $['api.success'], { ns: 'common' }))
|
|
onSuccess()
|
|
} finally {
|
|
setConversationDeleting(false)
|
|
}
|
|
if (conversationId === currentConversationId) handleNewConversation()
|
|
handleUpdateConversationList()
|
|
},
|
|
[
|
|
isInstalledApp,
|
|
appId,
|
|
t,
|
|
handleUpdateConversationList,
|
|
handleNewConversation,
|
|
currentConversationId,
|
|
conversationDeleting,
|
|
],
|
|
)
|
|
const [conversationRenaming, setConversationRenaming] = useState(false)
|
|
const handleRenameConversation = useCallback(
|
|
async (conversationId: string, newName: string, { onSuccess }: Callback) => {
|
|
if (conversationRenaming) return
|
|
if (!newName.trim()) {
|
|
toast.error(t(($) => $['chat.conversationNameCanNotEmpty'], { ns: 'common' }))
|
|
return
|
|
}
|
|
setConversationRenaming(true)
|
|
try {
|
|
await renameConversation(appSourceType, appId, conversationId, newName)
|
|
toast.success(t(($) => $['actionMsg.modifiedSuccessfully'], { ns: 'common' }))
|
|
setOriginConversationList(
|
|
produce((draft) => {
|
|
const index = originConversationList.findIndex((item) => item.id === conversationId)
|
|
const item = draft[index]!
|
|
draft[index] = {
|
|
...item,
|
|
name: newName,
|
|
}
|
|
}),
|
|
)
|
|
onSuccess()
|
|
} finally {
|
|
setConversationRenaming(false)
|
|
}
|
|
},
|
|
[isInstalledApp, appId, t, conversationRenaming, originConversationList],
|
|
)
|
|
const handleNewConversationCompleted = useCallback(
|
|
(newConversationId: string) => {
|
|
setNewConversationId(newConversationId)
|
|
handleConversationIdInfoChange(newConversationId)
|
|
setShowNewConversationItemInList(false)
|
|
invalidateShareConversations()
|
|
},
|
|
[handleConversationIdInfoChange, invalidateShareConversations],
|
|
)
|
|
const handleFeedback = useCallback(
|
|
async (messageId: string, feedback: Feedback) => {
|
|
await updateFeedback(
|
|
{
|
|
url: `/messages/${messageId}/feedbacks`,
|
|
body: { rating: feedback.rating, content: feedback.content },
|
|
},
|
|
appSourceType,
|
|
appId,
|
|
)
|
|
toast.success(t(($) => $['api.success'], { ns: 'common' }))
|
|
},
|
|
[appSourceType, appId, t],
|
|
)
|
|
return {
|
|
isInstalledApp,
|
|
appId,
|
|
currentConversationId,
|
|
currentConversationItem,
|
|
handleConversationIdInfoChange,
|
|
appData,
|
|
appParams: appParams || ({} as ChatConfig),
|
|
appMeta,
|
|
appPinnedConversationData,
|
|
appConversationData,
|
|
appConversationDataLoading,
|
|
appChatListData,
|
|
appChatListDataLoading,
|
|
appPrevChatTree,
|
|
pinnedConversationList,
|
|
conversationList,
|
|
setShowNewConversationItemInList,
|
|
newConversationInputs,
|
|
newConversationInputsRef,
|
|
handleNewConversationInputsChange,
|
|
inputsForms,
|
|
handleNewConversation,
|
|
handleStartChat,
|
|
handleChangeConversation,
|
|
handlePinConversation,
|
|
handleUnpinConversation,
|
|
conversationDeleting,
|
|
handleDeleteConversation,
|
|
conversationRenaming,
|
|
handleRenameConversation,
|
|
handleNewConversationCompleted,
|
|
newConversationId,
|
|
chatShouldReloadKey,
|
|
handleFeedback,
|
|
currentChatInstanceRef,
|
|
sidebarCollapseState,
|
|
handleSidebarCollapse,
|
|
clearChatList,
|
|
setClearChatList,
|
|
isResponding,
|
|
setIsResponding,
|
|
currentConversationInputs,
|
|
setCurrentConversationInputs,
|
|
allInputsHidden,
|
|
initUserVariables,
|
|
}
|
|
}
|