{
- selectedDatasetsMode.allEconomic && (
+ selectedDatasetsMode.allEconomic && !selectedDatasetsMode.mixtureInternalAndExternal && (
{
let errMsg = ''
if (tempDataSetConfigs.retrieval_model === RETRIEVE_TYPE.multiWay) {
- if (!tempDataSetConfigs.reranking_model?.reranking_model_name && (rerankDefaultModel && !isRerankDefaultModelValid))
+ if (tempDataSetConfigs.reranking_enable
+ && tempDataSetConfigs.reranking_mode === RerankingModeEnum.RerankingModel
+ && !isRerankDefaultModelValid
+ )
errMsg = t('appDebug.datasetConfig.rerankModelRequired')
}
if (errMsg) {
@@ -62,7 +66,9 @@ const ParamsConfig = ({
if (!isValid())
return
const config = { ...tempDataSetConfigs }
- if (config.retrieval_model === RETRIEVE_TYPE.multiWay && !config.reranking_model) {
+ if (config.retrieval_model === RETRIEVE_TYPE.multiWay
+ && config.reranking_mode === RerankingModeEnum.RerankingModel
+ && !config.reranking_model) {
config.reranking_model = {
reranking_provider_name: rerankDefaultModel?.provider?.provider,
reranking_model_name: rerankDefaultModel?.model,
diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx
index af50fc65c3..bf6c5e79c8 100644
--- a/web/app/components/app/configuration/index.tsx
+++ b/web/app/components/app/configuration/index.tsx
@@ -252,12 +252,18 @@ const Configuration: FC = () => {
}
hideSelectDataSet()
const {
- allEconomic,
+ allExternal,
+ allInternal,
+ mixtureInternalAndExternal,
mixtureHighQualityAndEconomic,
inconsistentEmbeddingModel,
} = getSelectedDatasetsMode(newDatasets)
- if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel)
+ if (
+ (allInternal && (mixtureHighQualityAndEconomic || inconsistentEmbeddingModel))
+ || mixtureInternalAndExternal
+ || allExternal
+ )
setRerankSettingModalOpen(true)
const { datasets, retrieval_model, score_threshold_enabled, ...restConfigs } = datasetConfigs
diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx
index 22585aa678..4c12cab581 100644
--- a/web/app/components/app/log/list.tsx
+++ b/web/app/components/app/log/list.tsx
@@ -36,6 +36,7 @@ import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import TextGeneration from '@/app/components/app/text-generate/item'
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
import MessageLogModal from '@/app/components/base/message-log-modal'
+import PromptLogModal from '@/app/components/base/prompt-log-modal'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useAppContext } from '@/context/app-context'
import useTimestamp from '@/hooks/use-timestamp'
@@ -168,11 +169,13 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
const { userProfile: { timezone } } = useAppContext()
const { formatTime } = useTimestamp()
const { onClose, appDetail } = useContext(DrawerContext)
- const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
+ const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, showPromptLogModal, setShowPromptLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
currentLogItem: state.currentLogItem,
setCurrentLogItem: state.setCurrentLogItem,
showMessageLogModal: state.showMessageLogModal,
setShowMessageLogModal: state.setShowMessageLogModal,
+ showPromptLogModal: state.showPromptLogModal,
+ setShowPromptLogModal: state.setShowPromptLogModal,
currentLogModalActiveTab: state.currentLogModalActiveTab,
})))
const { t } = useTranslation()
@@ -192,8 +195,8 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
conversation_id: detail.id,
limit: 10,
}
- if (allChatItems.at(-1)?.id)
- params.first_id = allChatItems.at(-1)?.id.replace('question-', '')
+ if (allChatItems[0]?.id)
+ params.first_id = allChatItems[0]?.id.replace('question-', '')
const messageRes = await fetchChatMessages({
url: `/apps/${appDetail?.id}/chat-messages`,
params,
@@ -557,6 +560,16 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
defaultTab={currentLogModalActiveTab}
/>
)}
+ {showPromptLogModal && (
+
{
+ setCurrentLogItem()
+ setShowPromptLogModal(false)
+ }}
+ />
+ )}
)
}
diff --git a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap
index 070975bfa7..7da09c4529 100644
--- a/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap
+++ b/web/app/components/base/chat/__tests__/__snapshots__/utils.spec.ts.snap
@@ -1804,6 +1804,280 @@ exports[`build chat item tree and get thread messages should get thread messages
]
`;
+exports[`build chat item tree and get thread messages should work with partial messages 1`] = `
+[
+ {
+ "children": [
+ {
+ "agent_thoughts": [
+ {
+ "chain_id": null,
+ "created_at": 1726105809,
+ "files": [],
+ "id": "1019cd79-d141-4f9f-880a-fc1441cfd802",
+ "message_id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
+ "observation": "",
+ "position": 1,
+ "thought": "Sure! My number is 54. Your turn!",
+ "tool": "",
+ "tool_input": "",
+ "tool_labels": {},
+ },
+ ],
+ "children": [
+ {
+ "children": [
+ {
+ "agent_thoughts": [
+ {
+ "chain_id": null,
+ "created_at": 1726105822,
+ "files": [],
+ "id": "0773bec7-b992-4a53-92b2-20ebaeae8798",
+ "message_id": "324bce32-c98c-435d-a66b-bac974ebb5ed",
+ "observation": "",
+ "position": 1,
+ "thought": "My number is 4729. Your turn!",
+ "tool": "",
+ "tool_input": "",
+ "tool_labels": {},
+ },
+ ],
+ "children": [],
+ "content": "My number is 4729. Your turn!",
+ "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80",
+ "feedbackDisabled": false,
+ "id": "324bce32-c98c-435d-a66b-bac974ebb5ed",
+ "input": {
+ "inputs": {},
+ "query": "3306",
+ },
+ "isAnswer": true,
+ "log": [
+ {
+ "files": [],
+ "role": "user",
+ "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
+ },
+ {
+ "files": [],
+ "role": "assistant",
+ "text": "Sure! My number is 54. Your turn!",
+ },
+ {
+ "files": [],
+ "role": "user",
+ "text": "3306",
+ },
+ {
+ "files": [],
+ "role": "assistant",
+ "text": "My number is 4729. Your turn!",
+ },
+ ],
+ "message_files": [],
+ "more": {
+ "latency": "1.30",
+ "time": "09/11/2024 09:50 PM",
+ "tokens": 66,
+ },
+ "parentMessageId": "question-324bce32-c98c-435d-a66b-bac974ebb5ed",
+ "siblingIndex": 0,
+ "workflow_run_id": null,
+ },
+ ],
+ "content": "3306",
+ "id": "question-324bce32-c98c-435d-a66b-bac974ebb5ed",
+ "isAnswer": false,
+ "message_files": [],
+ "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
+ },
+ {
+ "children": [
+ {
+ "agent_thoughts": [
+ {
+ "chain_id": null,
+ "created_at": 1726107812,
+ "files": [],
+ "id": "5ca650f3-982c-4399-8b95-9ea241c76707",
+ "message_id": "684b5396-4e91-4043-88e9-aabe48b21acc",
+ "observation": "",
+ "position": 1,
+ "thought": "My number is 4821. Your turn!",
+ "tool": "",
+ "tool_input": "",
+ "tool_labels": {},
+ },
+ ],
+ "children": [
+ {
+ "children": [
+ {
+ "agent_thoughts": [
+ {
+ "chain_id": null,
+ "created_at": 1726111024,
+ "files": [],
+ "id": "095cacab-afad-4387-a41d-1662578b8b13",
+ "message_id": "19904a7b-7494-4ed8-b72c-1d18668cea8c",
+ "observation": "",
+ "position": 1,
+ "thought": "My number is 1456. Your turn!",
+ "tool": "",
+ "tool_input": "",
+ "tool_labels": {},
+ },
+ ],
+ "children": [],
+ "content": "My number is 1456. Your turn!",
+ "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80",
+ "feedbackDisabled": false,
+ "id": "19904a7b-7494-4ed8-b72c-1d18668cea8c",
+ "input": {
+ "inputs": {},
+ "query": "1003",
+ },
+ "isAnswer": true,
+ "log": [
+ {
+ "files": [],
+ "role": "user",
+ "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
+ },
+ {
+ "files": [],
+ "role": "assistant",
+ "text": "Sure! My number is 54. Your turn!",
+ },
+ {
+ "files": [],
+ "role": "user",
+ "text": "3306",
+ },
+ {
+ "files": [],
+ "role": "assistant",
+ "text": "My number is 4821. Your turn!",
+ },
+ {
+ "files": [],
+ "role": "user",
+ "text": "1003",
+ },
+ {
+ "files": [],
+ "role": "assistant",
+ "text": "My number is 1456. Your turn!",
+ },
+ ],
+ "message_files": [],
+ "more": {
+ "latency": "1.38",
+ "time": "09/11/2024 11:17 PM",
+ "tokens": 86,
+ },
+ "parentMessageId": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c",
+ "siblingIndex": 0,
+ "workflow_run_id": null,
+ },
+ ],
+ "content": "1003",
+ "id": "question-19904a7b-7494-4ed8-b72c-1d18668cea8c",
+ "isAnswer": false,
+ "message_files": [],
+ "parentMessageId": "684b5396-4e91-4043-88e9-aabe48b21acc",
+ },
+ ],
+ "content": "My number is 4821. Your turn!",
+ "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80",
+ "feedbackDisabled": false,
+ "id": "684b5396-4e91-4043-88e9-aabe48b21acc",
+ "input": {
+ "inputs": {},
+ "query": "3306",
+ },
+ "isAnswer": true,
+ "log": [
+ {
+ "files": [],
+ "role": "user",
+ "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
+ },
+ {
+ "files": [],
+ "role": "assistant",
+ "text": "Sure! My number is 54. Your turn!",
+ },
+ {
+ "files": [],
+ "role": "user",
+ "text": "3306",
+ },
+ {
+ "files": [],
+ "role": "assistant",
+ "text": "My number is 4821. Your turn!",
+ },
+ ],
+ "message_files": [],
+ "more": {
+ "latency": "1.48",
+ "time": "09/11/2024 10:23 PM",
+ "tokens": 66,
+ },
+ "parentMessageId": "question-684b5396-4e91-4043-88e9-aabe48b21acc",
+ "siblingIndex": 1,
+ "workflow_run_id": null,
+ },
+ ],
+ "content": "3306",
+ "id": "question-684b5396-4e91-4043-88e9-aabe48b21acc",
+ "isAnswer": false,
+ "message_files": [],
+ "parentMessageId": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
+ },
+ ],
+ "content": "Sure! My number is 54. Your turn!",
+ "conversationId": "dd6c9cfd-2656-48ec-bd51-2139c1790d80",
+ "feedbackDisabled": false,
+ "id": "cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
+ "input": {
+ "inputs": {},
+ "query": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
+ },
+ "isAnswer": true,
+ "log": [
+ {
+ "files": [],
+ "role": "user",
+ "text": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
+ },
+ {
+ "files": [],
+ "role": "assistant",
+ "text": "Sure! My number is 54. Your turn!",
+ },
+ ],
+ "message_files": [],
+ "more": {
+ "latency": "1.52",
+ "time": "09/11/2024 09:50 PM",
+ "tokens": 46,
+ },
+ "parentMessageId": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
+ "siblingIndex": 0,
+ "workflow_run_id": null,
+ },
+ ],
+ "content": "Let's play a game, I say a number , and you response me with another bigger, yet randomly number. I'll start first, 38",
+ "id": "question-cd5affb0-7bc2-4a6f-be7e-25e74595c9dd",
+ "isAnswer": false,
+ "message_files": [],
+ },
+]
+`;
+
exports[`build chat item tree and get thread messages should work with real world messages 1`] = `
[
{
diff --git a/web/app/components/base/chat/__tests__/utils.spec.ts b/web/app/components/base/chat/__tests__/utils.spec.ts
index c602ac8a99..1dead1c949 100644
--- a/web/app/components/base/chat/__tests__/utils.spec.ts
+++ b/web/app/components/base/chat/__tests__/utils.spec.ts
@@ -255,4 +255,10 @@ describe('build chat item tree and get thread messages', () => {
const threadMessages6_2 = getThreadMessages(tree6, 'ff4c2b43-48a5-47ad-9dc5-08b34ddba61b')
expect(threadMessages6_2).toMatchSnapshot()
})
+
+ const partialMessages = (realWorldMessages as ChatItemInTree[]).slice(-10)
+ const tree7 = buildChatItemTree(partialMessages)
+ it('should work with partial messages', () => {
+ expect(tree7).toMatchSnapshot()
+ })
})
diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts
index 16357361cf..61dfaecffc 100644
--- a/web/app/components/base/chat/utils.ts
+++ b/web/app/components/base/chat/utils.ts
@@ -134,6 +134,12 @@ function buildChatItemTree(allMessages: IChatItem[]): ChatItemInTree[] {
}
}
+ // If no messages have parentMessageId=null (indicating a root node),
+ // then we likely have a partial chat history. In this case,
+ // use the first available message as the root node.
+ if (rootNodes.length === 0 && allMessages.length > 0)
+ rootNodes.push(map[allMessages[0]!.id]!)
+
return rootNodes
}
diff --git a/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx b/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx
index d22d6ff4ec..2a042bab40 100644
--- a/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx
+++ b/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx
@@ -1,6 +1,5 @@
import {
memo,
- useMemo,
} from 'react'
import {
RiDeleteBinLine,
@@ -35,17 +34,9 @@ const FileInAttachmentItem = ({
onRemove,
onReUpload,
}: FileInAttachmentItemProps) => {
- const { id, name, type, progress, supportFileType, base64Url, url } = file
- const ext = getFileExtension(name, type)
+ const { id, name, type, progress, supportFileType, base64Url, url, isRemote } = file
+ const ext = getFileExtension(name, type, isRemote)
const isImageFile = supportFileType === SupportUploadFileTypes.image
- const nameArr = useMemo(() => {
- const nameMatch = name.match(/(.+)\.([^.]+)$/)
-
- if (nameMatch)
- return [nameMatch[1], nameMatch[2]]
-
- return [name, '']
- }, [name])
return (
-
{nameArr[0]}
- {
- nameArr[1] && (
-
.{nameArr[1]}
- )
- }
+
{name}
{
@@ -93,7 +79,11 @@ const FileInAttachmentItem = ({
•
)
}
- {formatFileSize(file.size || 0)}
+ {
+ !!file.size && (
+ {formatFileSize(file.size)}
+ )
+ }
diff --git a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx
index 6597373020..a051b89ec1 100644
--- a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx
+++ b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx
@@ -31,8 +31,8 @@ const FileItem = ({
onRemove,
onReUpload,
}: FileItemProps) => {
- const { id, name, type, progress, url } = file
- const ext = getFileExtension(name, type)
+ const { id, name, type, progress, url, isRemote } = file
+ const ext = getFileExtension(name, type, isRemote)
const uploadError = progress === -1
return (
@@ -75,7 +75,9 @@ const FileItem = ({
>
)
}
- {formatFileSize(file.size || 0)}
+ {
+ !!file.size && formatFileSize(file.size)
+ }
{
showDownloadAction && (
diff --git a/web/app/components/base/file-uploader/hooks.ts b/web/app/components/base/file-uploader/hooks.ts
index 942e5d612a..a78c414913 100644
--- a/web/app/components/base/file-uploader/hooks.ts
+++ b/web/app/components/base/file-uploader/hooks.ts
@@ -25,7 +25,7 @@ import { TransferMethod } from '@/types/app'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import type { FileUpload } from '@/app/components/base/features/types'
import { formatFileSize } from '@/utils/format'
-import { fetchRemoteFileInfo } from '@/service/common'
+import { uploadRemoteFileInfo } from '@/service/common'
import type { FileUploadConfigResponse } from '@/models/common'
export const useFileSizeLimit = (fileUploadConfig?: FileUploadConfigResponse) => {
@@ -49,7 +49,7 @@ export const useFile = (fileConfig: FileUpload) => {
const params = useParams()
const { imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit } = useFileSizeLimit(fileConfig.fileUploadConfig)
- const checkSizeLimit = (fileType: string, fileSize: number) => {
+ const checkSizeLimit = useCallback((fileType: string, fileSize: number) => {
switch (fileType) {
case SupportUploadFileTypes.image: {
if (fileSize > imgSizeLimit) {
@@ -120,7 +120,7 @@ export const useFile = (fileConfig: FileUpload) => {
return true
}
}
- }
+ }, [audioSizeLimit, docSizeLimit, imgSizeLimit, notify, t, videoSizeLimit])
const handleAddFile = useCallback((newFile: FileEntity) => {
const {
@@ -188,6 +188,17 @@ export const useFile = (fileConfig: FileUpload) => {
}
}, [fileStore, notify, t, handleUpdateFile, params])
+ const startProgressTimer = useCallback((fileId: string) => {
+ const timer = setInterval(() => {
+ const files = fileStore.getState().files
+ const file = files.find(file => file.id === fileId)
+
+ if (file && file.progress < 80 && file.progress >= 0)
+ handleUpdateFile({ ...file, progress: file.progress + 20 })
+ else
+ clearTimeout(timer)
+ }, 200)
+ }, [fileStore, handleUpdateFile])
const handleLoadFileFromLink = useCallback((url: string) => {
const allowedFileTypes = fileConfig.allowed_file_types
@@ -197,19 +208,27 @@ export const useFile = (fileConfig: FileUpload) => {
type: '',
size: 0,
progress: 0,
- transferMethod: TransferMethod.remote_url,
+ transferMethod: TransferMethod.local_file,
supportFileType: '',
url,
+ isRemote: true,
}
handleAddFile(uploadingFile)
+ startProgressTimer(uploadingFile.id)
- fetchRemoteFileInfo(url).then((res) => {
+ uploadRemoteFileInfo(url).then((res) => {
const newFile = {
...uploadingFile,
- type: res.file_type,
- size: res.file_length,
+ type: res.mime_type,
+ size: res.size,
progress: 100,
- supportFileType: getSupportFileType(url, res.file_type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)),
+ supportFileType: getSupportFileType(res.name, res.mime_type, allowedFileTypes?.includes(SupportUploadFileTypes.custom)),
+ uploadedId: res.id,
+ url: res.url,
+ }
+ if (!isAllowedFileExtension(res.name, res.mime_type, fileConfig.allowed_file_types || [], fileConfig.allowed_file_extensions || [])) {
+ notify({ type: 'error', message: t('common.fileUploader.fileExtensionNotSupport') })
+ handleRemoveFile(uploadingFile.id)
}
if (!checkSizeLimit(newFile.supportFileType, newFile.size))
handleRemoveFile(uploadingFile.id)
@@ -219,7 +238,7 @@ export const useFile = (fileConfig: FileUpload) => {
notify({ type: 'error', message: t('common.fileUploader.pasteFileLinkInvalid') })
handleRemoveFile(uploadingFile.id)
})
- }, [checkSizeLimit, handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types])
+ }, [checkSizeLimit, handleAddFile, handleUpdateFile, notify, t, handleRemoveFile, fileConfig?.allowed_file_types, fileConfig.allowed_file_extensions, startProgressTimer])
const handleLoadFileFromLinkSuccess = useCallback(() => { }, [])
diff --git a/web/app/components/base/file-uploader/types.ts b/web/app/components/base/file-uploader/types.ts
index ac4584bb4c..285023f0af 100644
--- a/web/app/components/base/file-uploader/types.ts
+++ b/web/app/components/base/file-uploader/types.ts
@@ -29,4 +29,5 @@ export type FileEntity = {
uploadedId?: string
base64Url?: string
url?: string
+ isRemote?: boolean
}
diff --git a/web/app/components/base/file-uploader/utils.ts b/web/app/components/base/file-uploader/utils.ts
index 4c7ef0d89b..eb9199d74b 100644
--- a/web/app/components/base/file-uploader/utils.ts
+++ b/web/app/components/base/file-uploader/utils.ts
@@ -43,10 +43,13 @@ export const fileUpload: FileUpload = ({
})
}
-export const getFileExtension = (fileName: string, fileMimetype: string) => {
+export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
if (fileMimetype)
return mime.getExtension(fileMimetype) || ''
+ if (isRemote)
+ return ''
+
if (fileName) {
const fileNamePair = fileName.split('.')
const fileNamePairLength = fileNamePair.length
diff --git a/web/app/components/base/image-uploader/image-list.tsx b/web/app/components/base/image-uploader/image-list.tsx
index 8d5d1a1af5..35f6149b13 100644
--- a/web/app/components/base/image-uploader/image-list.tsx
+++ b/web/app/components/base/image-uploader/image-list.tsx
@@ -133,6 +133,7 @@ const ImageList: FC