handleRowClick(log)}>
+ className={cn('cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover', currentConversation?.id !== log.id ? '' : 'bg-background-default-hover')}
+ onClick={() => {
+ setShowDrawer(true)
+ setCurrentConversation(log)
+ }}>
{!log.read_at && (
diff --git a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx
index 29b27a60ad..302fb9a3c7 100644
--- a/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx
+++ b/web/app/components/base/chat/chat-with-history/chat-wrapper.tsx
@@ -3,7 +3,6 @@ import Chat from '../chat'
import type {
ChatConfig,
ChatItem,
- ChatItemInTree,
OnSend,
} from '../types'
import { useChat } from '../chat/hooks'
@@ -149,7 +148,7 @@ const ChatWrapper = () => {
)
}, [chatList, handleNewConversationCompleted, handleSend, currentConversationId, currentConversationInputs, newConversationInputs, isInstalledApp, appId])
- const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => {
+ const doRegenerate = useCallback((chatItem: ChatItem, editedQuestion?: { message: string, files?: FileEntity[] }) => {
const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)!
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
doSend(editedQuestion ? editedQuestion.message : question.content,
diff --git a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx
index 1bb3dbf56f..5fba104d35 100644
--- a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx
+++ b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx
@@ -3,7 +3,6 @@ import Chat from '../chat'
import type {
ChatConfig,
ChatItem,
- ChatItemInTree,
OnSend,
} from '../types'
import { useChat } from '../chat/hooks'
@@ -147,7 +146,7 @@ const ChatWrapper = () => {
)
}, [currentConversationId, currentConversationInputs, newConversationInputs, chatList, handleSend, isInstalledApp, appId, handleNewConversationCompleted])
- const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => {
+ const doRegenerate = useCallback((chatItem: ChatItem, editedQuestion?: { message: string, files?: FileEntity[] }) => {
const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)!
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
doSend(editedQuestion ? editedQuestion.message : question.content,
diff --git a/web/app/components/base/chat/types.ts b/web/app/components/base/chat/types.ts
index f7f7aa4dce..5b0fe1f248 100644
--- a/web/app/components/base/chat/types.ts
+++ b/web/app/components/base/chat/types.ts
@@ -85,7 +85,7 @@ export type OnSend = {
(message: string, files: FileEntity[] | undefined, isRegenerate: boolean, lastAnswer?: ChatItem | null): void
}
-export type OnRegenerate = (chatItem: ChatItem) => void
+export type OnRegenerate = (chatItem: ChatItem, editedQuestion?: { message: string; files?: FileEntity[] }) => void
export type Callback = {
onSuccess: () => void
diff --git a/web/app/components/base/content-dialog/index.stories.tsx b/web/app/components/base/content-dialog/index.stories.tsx
index 29b3914704..67781a17a0 100644
--- a/web/app/components/base/content-dialog/index.stories.tsx
+++ b/web/app/components/base/content-dialog/index.stories.tsx
@@ -32,6 +32,7 @@ const meta = {
},
args: {
show: false,
+ children: null,
},
} satisfies Meta
@@ -92,6 +93,9 @@ const DemoWrapper = (props: Props) => {
}
export const Default: Story = {
+ args: {
+ children: null,
+ },
render: args => ,
}
@@ -99,6 +103,7 @@ export const NarrowPanel: Story = {
render: args => ,
args: {
className: 'max-w-[420px]',
+ children: null,
},
parameters: {
docs: {
diff --git a/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx b/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx
index 40bc2928c8..bd4468e82d 100644
--- a/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx
+++ b/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx
@@ -3,6 +3,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
import TimePicker from './index'
import dayjs from '../utils/dayjs'
import { isDayjsObject } from '../utils/dayjs'
+import type { TimePickerProps } from '../types'
jest.mock('react-i18next', () => ({
useTranslation: () => ({
@@ -30,9 +31,10 @@ jest.mock('./options', () => () => )
jest.mock('./header', () => () => )
describe('TimePicker', () => {
- const baseProps = {
+ const baseProps: Pick = {
onChange: jest.fn(),
onClear: jest.fn(),
+ value: undefined,
}
beforeEach(() => {
diff --git a/web/app/components/base/date-and-time-picker/utils/dayjs.ts b/web/app/components/base/date-and-time-picker/utils/dayjs.ts
index 808b50247a..4f53c766ea 100644
--- a/web/app/components/base/date-and-time-picker/utils/dayjs.ts
+++ b/web/app/components/base/date-and-time-picker/utils/dayjs.ts
@@ -150,7 +150,7 @@ export const toDayjs = (value: string | Dayjs | undefined, options: ToDayjsOptio
if (format) {
const parsedWithFormat = tzName
- ? dayjs.tz(trimmed, format, tzName, true)
+ ? dayjs(trimmed, format, true).tz(tzName, true)
: dayjs(trimmed, format, true)
if (parsedWithFormat.isValid())
return parsedWithFormat
@@ -191,7 +191,7 @@ export const toDayjs = (value: string | Dayjs | undefined, options: ToDayjsOptio
const candidateFormats = formats ?? COMMON_PARSE_FORMATS
for (const fmt of candidateFormats) {
const parsed = tzName
- ? dayjs.tz(trimmed, fmt, tzName, true)
+ ? dayjs(trimmed, fmt, true).tz(tzName, true)
: dayjs(trimmed, fmt, true)
if (parsed.isValid())
return parsed
diff --git a/web/app/components/base/dialog/index.stories.tsx b/web/app/components/base/dialog/index.stories.tsx
index 62ae7c00ce..94998c6d21 100644
--- a/web/app/components/base/dialog/index.stories.tsx
+++ b/web/app/components/base/dialog/index.stories.tsx
@@ -47,6 +47,7 @@ const meta = {
args: {
title: 'Manage API Keys',
show: false,
+ children: null,
},
} satisfies Meta
@@ -102,6 +103,7 @@ export const Default: Story = {
>
),
+ children: null,
},
}
@@ -110,6 +112,7 @@ export const WithoutFooter: Story = {
args: {
footer: undefined,
title: 'Read-only summary',
+ children: null,
},
parameters: {
docs: {
@@ -140,6 +143,7 @@ export const CustomStyling: Story = {
>
),
+ children: null,
},
parameters: {
docs: {
diff --git a/web/app/components/base/form/types.ts b/web/app/components/base/form/types.ts
index d18c166186..ce3b5ec965 100644
--- a/web/app/components/base/form/types.ts
+++ b/web/app/components/base/form/types.ts
@@ -42,7 +42,7 @@ export type FormOption = {
icon?: string
}
-export type AnyValidators = FieldValidators
+export type AnyValidators = FieldValidators
export type FormSchema = {
type: FormTypeEnum
diff --git a/web/app/components/base/markdown-blocks/think-block.tsx b/web/app/components/base/markdown-blocks/think-block.tsx
index a3b0561677..9c43578e4c 100644
--- a/web/app/components/base/markdown-blocks/think-block.tsx
+++ b/web/app/components/base/markdown-blocks/think-block.tsx
@@ -1,6 +1,7 @@
import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useChatContext } from '../chat/chat/context'
+import cn from '@/utils/classnames'
const hasEndThink = (children: any): boolean => {
if (typeof children === 'string')
@@ -40,7 +41,7 @@ const useThinkTimer = (children: any) => {
const [startTime] = useState(() => Date.now())
const [elapsedTime, setElapsedTime] = useState(0)
const [isComplete, setIsComplete] = useState(false)
- const timerRef = useRef()
+ const timerRef = useRef(null)
useEffect(() => {
if (isComplete) return
@@ -63,16 +64,26 @@ const useThinkTimer = (children: any) => {
return { elapsedTime, isComplete }
}
-const ThinkBlock = ({ children, ...props }: React.ComponentProps<'details'>) => {
+type ThinkBlockProps = React.ComponentProps<'details'> & {
+ 'data-think'?: boolean
+}
+
+const ThinkBlock = ({ children, ...props }: ThinkBlockProps) => {
const { elapsedTime, isComplete } = useThinkTimer(children)
const displayContent = removeEndThink(children)
const { t } = useTranslation()
+ const { 'data-think': isThink = false, className, open, ...rest } = props
- if (!(props['data-think'] ?? false))
+ if (!isThink)
return ({children} )
return (
-
+
)}
diff --git a/web/app/components/base/portal-to-follow-elem/index.tsx b/web/app/components/base/portal-to-follow-elem/index.tsx
index 71ee251edd..e1192fe73b 100644
--- a/web/app/components/base/portal-to-follow-elem/index.tsx
+++ b/web/app/components/base/portal-to-follow-elem/index.tsx
@@ -125,7 +125,7 @@ export const PortalToFollowElemTrigger = (
children,
asChild = false,
...props
- }: React.HTMLProps & { ref?: React.RefObject, asChild?: boolean },
+ }: React.HTMLProps & { ref?: React.RefObject, asChild?: boolean },
) => {
const context = usePortalToFollowElemContext()
const childrenRef = (children as any).props?.ref
@@ -133,12 +133,13 @@ export const PortalToFollowElemTrigger = (
// `asChild` allows the user to pass any element as the anchor
if (asChild && React.isValidElement(children)) {
+ const childProps = (children.props ?? {}) as Record
return React.cloneElement(
children,
context.getReferenceProps({
ref,
...props,
- ...children.props,
+ ...childProps,
'data-state': context.open ? 'open' : 'closed',
} as React.HTMLProps),
)
@@ -164,7 +165,7 @@ export const PortalToFollowElemContent = (
style,
...props
}: React.HTMLProps & {
- ref?: React.RefObject;
+ ref?: React.RefObject;
},
) => {
const context = usePortalToFollowElemContext()
diff --git a/web/app/components/base/prompt-editor/hooks.ts b/web/app/components/base/prompt-editor/hooks.ts
index 87119f8b49..b3d2b22236 100644
--- a/web/app/components/base/prompt-editor/hooks.ts
+++ b/web/app/components/base/prompt-editor/hooks.ts
@@ -35,7 +35,7 @@ import { DELETE_QUERY_BLOCK_COMMAND } from './plugins/query-block'
import type { CustomTextNode } from './plugins/custom-text/node'
import { registerLexicalTextEntity } from './utils'
-export type UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand) => [RefObject, boolean]
+export type UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand) => [RefObject, boolean]
export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand) => {
const ref = useRef(null)
const [editor] = useLexicalComposerContext()
@@ -110,7 +110,7 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
return [ref, isSelected]
}
-export type UseTriggerHandler = () => [RefObject, boolean, Dispatch>]
+export type UseTriggerHandler = () => [RefObject, boolean, Dispatch>]
export const useTrigger: UseTriggerHandler = () => {
const triggerRef = useRef(null)
const [open, setOpen] = useState(false)
diff --git a/web/app/components/base/prompt-editor/plugins/placeholder.tsx b/web/app/components/base/prompt-editor/plugins/placeholder.tsx
index c2c2623992..187b574cea 100644
--- a/web/app/components/base/prompt-editor/plugins/placeholder.tsx
+++ b/web/app/components/base/prompt-editor/plugins/placeholder.tsx
@@ -1,4 +1,5 @@
import { memo } from 'react'
+import type { ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import cn from '@/utils/classnames'
@@ -8,7 +9,7 @@ const Placeholder = ({
className,
}: {
compact?: boolean
- value?: string | JSX.Element
+ value?: ReactNode
className?: string
}) => {
const { t } = useTranslation()
diff --git a/web/app/components/base/voice-input/utils.ts b/web/app/components/base/voice-input/utils.ts
index 70133f459f..a8ac9eba03 100644
--- a/web/app/components/base/voice-input/utils.ts
+++ b/web/app/components/base/voice-input/utils.ts
@@ -14,13 +14,19 @@ export const convertToMp3 = (recorder: any) => {
const { channels, sampleRate } = wav
const mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128)
const result = recorder.getChannelData()
- const buffer = []
+ const buffer: BlobPart[] = []
const leftData = result.left && new Int16Array(result.left.buffer, 0, result.left.byteLength / 2)
const rightData = result.right && new Int16Array(result.right.buffer, 0, result.right.byteLength / 2)
const remaining = leftData.length + (rightData ? rightData.length : 0)
const maxSamples = 1152
+ const toArrayBuffer = (bytes: Int8Array) => {
+ const arrayBuffer = new ArrayBuffer(bytes.length)
+ new Uint8Array(arrayBuffer).set(bytes)
+ return arrayBuffer
+ }
+
for (let i = 0; i < remaining; i += maxSamples) {
const left = leftData.subarray(i, i + maxSamples)
let right = null
@@ -35,13 +41,13 @@ export const convertToMp3 = (recorder: any) => {
}
if (mp3buf.length > 0)
- buffer.push(mp3buf)
+ buffer.push(toArrayBuffer(mp3buf))
}
const enc = mp3enc.flush()
if (enc.length > 0)
- buffer.push(enc)
+ buffer.push(toArrayBuffer(enc))
return new Blob(buffer, { type: 'audio/mp3' })
}
diff --git a/web/app/components/billing/pricing/index.tsx b/web/app/components/billing/pricing/index.tsx
index 8b678ab272..ae8cb2056f 100644
--- a/web/app/components/billing/pricing/index.tsx
+++ b/web/app/components/billing/pricing/index.tsx
@@ -32,7 +32,6 @@ const Pricing: FC = ({
const [planRange, setPlanRange] = React.useState(PlanRange.monthly)
const [currentCategory, setCurrentCategory] = useState(CategoryEnum.CLOUD)
const canPay = isCurrentWorkspaceManager
-
useKeyPress(['esc'], onCancel)
const pricingPageLanguage = useGetPricingPageLanguage()
diff --git a/web/app/components/billing/pricing/plans/index.tsx b/web/app/components/billing/pricing/plans/index.tsx
index 0d6d61b690..d648613c8f 100644
--- a/web/app/components/billing/pricing/plans/index.tsx
+++ b/web/app/components/billing/pricing/plans/index.tsx
@@ -6,7 +6,7 @@ import SelfHostedPlanItem from './self-hosted-plan-item'
type PlansProps = {
plan: {
- type: BasicPlan
+ type: Plan
usage: UsagePlanInfo
total: UsagePlanInfo
}
@@ -21,6 +21,7 @@ const Plans = ({
planRange,
canPay,
}: PlansProps) => {
+ const currentPlanType: BasicPlan = plan.type === Plan.enterprise ? Plan.team : plan.type
return (
@@ -28,21 +29,21 @@ const Plans = ({
currentPlan === 'cloud' && (
<>
= ({ datasetId, batchId, documents = [], index
return doc?.data_source_type as DataSourceType
}
+ const isLegacyDataSourceInfo = (info: DataSourceInfo): info is LegacyDataSourceInfo => {
+ return info != null && typeof (info as LegacyDataSourceInfo).upload_file === 'object'
+ }
+
const getIcon = (id: string) => {
const doc = documents.find(document => document.id === id)
-
- return doc?.data_source_info.notion_page_icon
+ const info = doc?.data_source_info
+ if (info && isLegacyDataSourceInfo(info))
+ return info.notion_page_icon
+ return undefined
}
const isSourceEmbedding = (detail: IndexingStatusResponse) =>
['indexing', 'splitting', 'parsing', 'cleaning', 'waiting'].includes(detail.indexing_status || '')
diff --git a/web/app/components/datasets/create/file-uploader/index.tsx b/web/app/components/datasets/create/file-uploader/index.tsx
index e2bbad2776..75557b37c9 100644
--- a/web/app/components/datasets/create/file-uploader/index.tsx
+++ b/web/app/components/datasets/create/file-uploader/index.tsx
@@ -19,8 +19,6 @@ import { IS_CE_EDITION } from '@/config'
import { Theme } from '@/types/app'
import useTheme from '@/hooks/use-theme'
-const FILES_NUMBER_LIMIT = 20
-
type IFileUploaderProps = {
fileList: FileItem[]
titleClassName?: string
@@ -72,6 +70,7 @@ const FileUploader = ({
const fileUploadConfig = useMemo(() => fileUploadConfigResponse ?? {
file_size_limit: 15,
batch_count_limit: 5,
+ file_upload_limit: 5,
}, [fileUploadConfigResponse])
const fileListRef = useRef([])
@@ -106,6 +105,8 @@ const FileUploader = ({
return isValidType && isValidSize
}, [fileUploadConfig, notify, t, ACCEPTS])
+ type UploadResult = Awaited>
+
const fileUpload = useCallback(async (fileItem: FileItem): Promise => {
const formData = new FormData()
formData.append('file', fileItem.file)
@@ -121,10 +122,10 @@ const FileUploader = ({
data: formData,
onprogress: onProgress,
}, false, undefined, '?source=datasets')
- .then((res: File) => {
+ .then((res) => {
const completeFile = {
fileID: fileItem.fileID,
- file: res,
+ file: res as unknown as File,
progress: -1,
}
const index = fileListRef.current.findIndex(item => item.fileID === fileItem.fileID)
@@ -163,11 +164,12 @@ const FileUploader = ({
}, [fileUploadConfig, uploadBatchFiles])
const initialUpload = useCallback((files: File[]) => {
+ const filesCountLimit = fileUploadConfig.file_upload_limit
if (!files.length)
return false
- if (files.length + fileList.length > FILES_NUMBER_LIMIT && !IS_CE_EDITION) {
- notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.filesNumber', { filesNumber: FILES_NUMBER_LIMIT }) })
+ if (files.length + fileList.length > filesCountLimit && !IS_CE_EDITION) {
+ notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.filesNumber', { filesNumber: filesCountLimit }) })
return false
}
@@ -180,7 +182,7 @@ const FileUploader = ({
prepareFileList(newFiles)
fileListRef.current = newFiles
uploadMultipleFiles(preparedFiles)
- }, [prepareFileList, uploadMultipleFiles, notify, t, fileList])
+ }, [prepareFileList, uploadMultipleFiles, notify, t, fileList, fileUploadConfig])
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
@@ -255,10 +257,11 @@ const FileUploader = ({
)
let files = nested.flat()
if (notSupportBatchUpload) files = files.slice(0, 1)
+ files = files.slice(0, fileUploadConfig.batch_count_limit)
const valid = files.filter(isValid)
initialUpload(valid)
},
- [initialUpload, isValid, notSupportBatchUpload, traverseFileEntry],
+ [initialUpload, isValid, notSupportBatchUpload, traverseFileEntry, fileUploadConfig],
)
const selectHandle = () => {
if (fileUploader.current)
@@ -273,9 +276,10 @@ const FileUploader = ({
onFileListUpdate?.([...fileListRef.current])
}
const fileChangeHandle = useCallback((e: React.ChangeEvent) => {
- const files = [...(e.target.files ?? [])] as File[]
+ let files = [...(e.target.files ?? [])] as File[]
+ files = files.slice(0, fileUploadConfig.batch_count_limit)
initialUpload(files.filter(isValid))
- }, [isValid, initialUpload])
+ }, [isValid, initialUpload, fileUploadConfig])
const { theme } = useTheme()
const chartColor = useMemo(() => theme === Theme.dark ? '#5289ff' : '#296dff', [theme])
@@ -324,7 +328,8 @@ const FileUploader = ({
{t('datasetCreation.stepOne.uploader.tip', {
size: fileUploadConfig.file_size_limit,
supportTypes: supportTypesShowNames,
- batchCount: fileUploadConfig.batch_count_limit,
+ batchCount: notSupportBatchUpload ? 1 : fileUploadConfig.batch_count_limit,
+ totalCount: fileUploadConfig.file_upload_limit,
})}
{dragging && }
diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx
index da47a4664c..361378362e 100644
--- a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx
+++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx
@@ -121,6 +121,8 @@ const LocalFile = ({
return isValidType && isValidSize
}, [fileUploadConfig, notify, t, ACCEPTS])
+ type UploadResult = Awaited >
+
const fileUpload = useCallback(async (fileItem: FileItem): Promise => {
const formData = new FormData()
formData.append('file', fileItem.file)
@@ -136,10 +138,14 @@ const LocalFile = ({
data: formData,
onprogress: onProgress,
}, false, undefined, '?source=datasets')
- .then((res: File) => {
- const completeFile = {
+ .then((res: UploadResult) => {
+ const updatedFile = Object.assign({}, fileItem.file, {
+ id: res.id,
+ ...(res as Partial),
+ }) as File
+ const completeFile: FileItem = {
fileID: fileItem.fileID,
- file: res,
+ file: updatedFile,
progress: -1,
}
const index = fileListRef.current.findIndex(item => item.fileID === fileItem.fileID)
@@ -287,7 +293,7 @@ const LocalFile = ({
- {t('datasetCreation.stepOne.uploader.button')}
+ {notSupportBatchUpload ? t('datasetCreation.stepOne.uploader.buttonSingleFile') : t('datasetCreation.stepOne.uploader.button')}
{allowedExtensions.length > 0 && (
)}
@@ -296,7 +302,7 @@ const LocalFile = ({
{t('datasetCreation.stepOne.uploader.tip', {
size: fileUploadConfig.file_size_limit,
supportTypes: supportTypesShowNames,
- batchCount: fileUploadConfig.batch_count_limit,
+ batchCount: notSupportBatchUpload ? 1 : fileUploadConfig.batch_count_limit,
})}
{dragging && }
diff --git a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx
index 7e8749f0bf..96cab11c9c 100644
--- a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx
+++ b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx
@@ -38,6 +38,8 @@ const CSVUploader: FC = ({
file_size_limit: 15,
}, [fileUploadConfigResponse])
+ type UploadResult = Awaited>
+
const fileUpload = useCallback(async (fileItem: FileItem): Promise => {
fileItem.progress = 0
@@ -58,10 +60,14 @@ const CSVUploader: FC = ({
data: formData,
onprogress: onProgress,
}, false, undefined, '?source=datasets')
- .then((res: File) => {
- const completeFile = {
+ .then((res: UploadResult) => {
+ const updatedFile = Object.assign({}, fileItem.file, {
+ id: res.id,
+ ...(res as Partial),
+ }) as File
+ const completeFile: FileItem = {
fileID: fileItem.fileID,
- file: res,
+ file: updatedFile,
progress: 100,
}
updateFile(completeFile)
diff --git a/web/app/components/datasets/documents/detail/index.tsx b/web/app/components/datasets/documents/detail/index.tsx
index b4f47253fb..ddec9b6dbe 100644
--- a/web/app/components/datasets/documents/detail/index.tsx
+++ b/web/app/components/datasets/documents/detail/index.tsx
@@ -17,7 +17,7 @@ import Divider from '@/app/components/base/divider'
import Loading from '@/app/components/base/loading'
import Toast from '@/app/components/base/toast'
import { ChunkingMode } from '@/models/datasets'
-import type { FileItem } from '@/models/datasets'
+import type { DataSourceInfo, FileItem, LegacyDataSourceInfo } from '@/models/datasets'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import FloatRightContainer from '@/app/components/base/float-right-container'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
@@ -109,6 +109,18 @@ const DocumentDetail: FC = ({ datasetId, documentId }) => {
const embedding = ['queuing', 'indexing', 'paused'].includes((documentDetail?.display_status || '').toLowerCase())
+ const isLegacyDataSourceInfo = (info?: DataSourceInfo): info is LegacyDataSourceInfo => {
+ return !!info && 'upload_file' in info
+ }
+
+ const documentUploadFile = useMemo(() => {
+ if (!documentDetail?.data_source_info)
+ return undefined
+ if (isLegacyDataSourceInfo(documentDetail.data_source_info))
+ return documentDetail.data_source_info.upload_file
+ return undefined
+ }, [documentDetail?.data_source_info])
+
const invalidChunkList = useInvalid(useSegmentListKey)
const invalidChildChunkList = useInvalid(useChildSegmentListKey)
const invalidDocumentList = useInvalidDocumentList(datasetId)
@@ -153,7 +165,7 @@ const DocumentDetail: FC = ({ datasetId, documentId }) => {
void
}
+type MetadataState = {
+ documentType?: DocType | ''
+ metadata: Record
+}
+
const Metadata: FC = ({ docDetail, loading, onUpdate }) => {
const { doc_metadata = {} } = docDetail || {}
- const doc_type = docDetail?.doc_type || ''
+ const rawDocType = docDetail?.doc_type ?? ''
+ const doc_type = rawDocType === 'others' ? '' : rawDocType
const { t } = useTranslation()
const metadataMap = useMetadataMap()
@@ -143,18 +149,16 @@ const Metadata: FC = ({ docDetail, loading, onUpdate }) => {
const businessDocCategoryMap = useBusinessDocCategories()
const [editStatus, setEditStatus] = useState(!doc_type) // if no documentType, in editing status by default
// the initial values are according to the documentType
- const [metadataParams, setMetadataParams] = useState<{
- documentType?: DocType | ''
- metadata: { [key: string]: string }
- }>(
+ const [metadataParams, setMetadataParams] = useState(
doc_type
? {
- documentType: doc_type,
- metadata: doc_metadata || {},
+ documentType: doc_type as DocType,
+ metadata: (doc_metadata || {}) as Record,
}
- : { metadata: {} })
+ : { metadata: {} },
+ )
const [showDocTypes, setShowDocTypes] = useState(!doc_type) // whether show doc types
- const [tempDocType, setTempDocType] = useState('') // for remember icon click
+ const [tempDocType, setTempDocType] = useState('') // for remember icon click
const [saveLoading, setSaveLoading] = useState(false)
const { notify } = useContext(ToastContext)
@@ -165,13 +169,13 @@ const Metadata: FC = ({ docDetail, loading, onUpdate }) => {
if (docDetail?.doc_type) {
setEditStatus(false)
setShowDocTypes(false)
- setTempDocType(docDetail?.doc_type)
+ setTempDocType(doc_type as DocType | '')
setMetadataParams({
- documentType: docDetail?.doc_type,
- metadata: docDetail?.doc_metadata || {},
+ documentType: doc_type as DocType | '',
+ metadata: (docDetail?.doc_metadata || {}) as Record,
})
}
- }, [docDetail?.doc_type])
+ }, [docDetail?.doc_type, docDetail?.doc_metadata, doc_type])
// confirm doc type
const confirmDocType = () => {
@@ -179,7 +183,7 @@ const Metadata: FC = ({ docDetail, loading, onUpdate }) => {
return
setMetadataParams({
documentType: tempDocType,
- metadata: tempDocType === metadataParams.documentType ? metadataParams.metadata : {}, // change doc type, clear metadata
+ metadata: tempDocType === metadataParams.documentType ? metadataParams.metadata : {} as Record, // change doc type, clear metadata
})
setEditStatus(true)
setShowDocTypes(false)
@@ -187,7 +191,7 @@ const Metadata: FC = ({ docDetail, loading, onUpdate }) => {
// cancel doc type
const cancelDocType = () => {
- setTempDocType(metadataParams.documentType)
+ setTempDocType(metadataParams.documentType ?? '')
setEditStatus(true)
setShowDocTypes(false)
}
@@ -209,7 +213,7 @@ const Metadata: FC = ({ docDetail, loading, onUpdate }) => {
{t('datasetDocuments.metadata.docTypeChangeTitle')}
{t('datasetDocuments.metadata.docTypeSelectWarning')}
>}
-
+
{CUSTOMIZABLE_DOC_TYPES.map((type, index) => {
const currValue = tempDocType ?? documentType
return
diff --git a/web/app/components/datasets/documents/detail/settings/document-settings.tsx b/web/app/components/datasets/documents/detail/settings/document-settings.tsx
index 048645c9cf..3bcb8ef3aa 100644
--- a/web/app/components/datasets/documents/detail/settings/document-settings.tsx
+++ b/web/app/components/datasets/documents/detail/settings/document-settings.tsx
@@ -4,7 +4,17 @@ import { useBoolean } from 'ahooks'
import { useContext } from 'use-context-selector'
import { useRouter } from 'next/navigation'
import DatasetDetailContext from '@/context/dataset-detail'
-import type { CrawlOptions, CustomFile, DataSourceType } from '@/models/datasets'
+import type {
+ CrawlOptions,
+ CustomFile,
+ DataSourceInfo,
+ DataSourceType,
+ LegacyDataSourceInfo,
+ LocalFileInfo,
+ OnlineDocumentInfo,
+ WebsiteCrawlInfo,
+} from '@/models/datasets'
+import type { DataSourceProvider } from '@/models/common'
import Loading from '@/app/components/base/loading'
import StepTwo from '@/app/components/datasets/create/step-two'
import AccountSetting from '@/app/components/header/account-setting'
@@ -42,15 +52,78 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
params: { metadata: 'without' },
})
+ const dataSourceInfo = documentDetail?.data_source_info
+
+ const isLegacyDataSourceInfo = (info: DataSourceInfo | undefined): info is LegacyDataSourceInfo => {
+ return !!info && 'upload_file' in info
+ }
+ const isWebsiteCrawlInfo = (info: DataSourceInfo | undefined): info is WebsiteCrawlInfo => {
+ return !!info && 'source_url' in info && 'title' in info
+ }
+ const isOnlineDocumentInfo = (info: DataSourceInfo | undefined): info is OnlineDocumentInfo => {
+ return !!info && 'page' in info
+ }
+ const isLocalFileInfo = (info: DataSourceInfo | undefined): info is LocalFileInfo => {
+ return !!info && 'related_id' in info && 'transfer_method' in info
+ }
+ const legacyInfo = isLegacyDataSourceInfo(dataSourceInfo) ? dataSourceInfo : undefined
+ const websiteInfo = isWebsiteCrawlInfo(dataSourceInfo) ? dataSourceInfo : undefined
+ const onlineDocumentInfo = isOnlineDocumentInfo(dataSourceInfo) ? dataSourceInfo : undefined
+ const localFileInfo = isLocalFileInfo(dataSourceInfo) ? dataSourceInfo : undefined
+
const currentPage = useMemo(() => {
- return {
- workspace_id: documentDetail?.data_source_info.notion_workspace_id,
- page_id: documentDetail?.data_source_info.notion_page_id,
- page_name: documentDetail?.name,
- page_icon: documentDetail?.data_source_info.notion_page_icon,
- type: documentDetail?.data_source_type,
+ if (legacyInfo) {
+ return {
+ workspace_id: legacyInfo.notion_workspace_id ?? '',
+ page_id: legacyInfo.notion_page_id ?? '',
+ page_name: documentDetail?.name,
+ page_icon: legacyInfo.notion_page_icon,
+ type: documentDetail?.data_source_type,
+ }
}
- }, [documentDetail])
+ if (onlineDocumentInfo) {
+ return {
+ workspace_id: onlineDocumentInfo.workspace_id,
+ page_id: onlineDocumentInfo.page.page_id,
+ page_name: onlineDocumentInfo.page.page_name,
+ page_icon: onlineDocumentInfo.page.page_icon,
+ type: onlineDocumentInfo.page.type,
+ }
+ }
+ return undefined
+ }, [documentDetail?.data_source_type, documentDetail?.name, legacyInfo, onlineDocumentInfo])
+
+ const files = useMemo(() => {
+ if (legacyInfo?.upload_file)
+ return [legacyInfo.upload_file as CustomFile]
+ if (localFileInfo) {
+ const { related_id, name, extension } = localFileInfo
+ return [{
+ id: related_id,
+ name,
+ extension,
+ } as unknown as CustomFile]
+ }
+ return []
+ }, [legacyInfo?.upload_file, localFileInfo])
+
+ const websitePages = useMemo(() => {
+ if (!websiteInfo)
+ return []
+ return [{
+ title: websiteInfo.title,
+ source_url: websiteInfo.source_url,
+ content: websiteInfo.content,
+ description: websiteInfo.description,
+ }]
+ }, [websiteInfo])
+
+ const crawlOptions = (dataSourceInfo && typeof dataSourceInfo === 'object' && 'includes' in dataSourceInfo && 'excludes' in dataSourceInfo)
+ ? dataSourceInfo as unknown as CrawlOptions
+ : undefined
+
+ const websiteCrawlProvider = (websiteInfo?.provider ?? legacyInfo?.provider) as DataSourceProvider | undefined
+ const websiteCrawlJobId = websiteInfo?.job_id ?? legacyInfo?.job_id
if (error)
return
@@ -65,22 +138,16 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
onSetting={showSetAPIKey}
datasetId={datasetId}
dataSourceType={documentDetail.data_source_type as DataSourceType}
- notionPages={[currentPage as unknown as NotionPage]}
- websitePages={[
- {
- title: documentDetail.name,
- source_url: documentDetail.data_source_info?.url,
- content: '',
- description: '',
- },
- ]}
- websiteCrawlProvider={documentDetail.data_source_info?.provider}
- websiteCrawlJobId={documentDetail.data_source_info?.job_id}
- crawlOptions={documentDetail.data_source_info as unknown as CrawlOptions}
+ notionPages={currentPage ? [currentPage as unknown as NotionPage] : []}
+ notionCredentialId={legacyInfo?.credential_id || onlineDocumentInfo?.credential_id || ''}
+ websitePages={websitePages}
+ websiteCrawlProvider={websiteCrawlProvider}
+ websiteCrawlJobId={websiteCrawlJobId || ''}
+ crawlOptions={crawlOptions}
indexingType={indexingTechnique}
isSetting
documentDetail={documentDetail}
- files={[documentDetail.data_source_info.upload_file as CustomFile]}
+ files={files}
onSave={saveHandler}
onCancel={cancelHandler}
/>
diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx
index d28959a509..58e96fde69 100644
--- a/web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx
@@ -5,6 +5,7 @@ import type {
Model,
ModelItem,
} from '../declarations'
+import type { ModelFeatureEnum } from '../declarations'
import { useCurrentProviderAndModel } from '../hooks'
import ModelTrigger from './model-trigger'
import EmptyTrigger from './empty-trigger'
@@ -24,7 +25,7 @@ type ModelSelectorProps = {
popupClassName?: string
onSelect?: (model: DefaultModel) => void
readonly?: boolean
- scopeFeatures?: string[]
+ scopeFeatures?: ModelFeatureEnum[]
deprecatedClassName?: string
showDeprecatedWarnIcon?: boolean
}
diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx
index ff32b438ed..b43fcd6301 100644
--- a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx
@@ -22,7 +22,7 @@ type PopupProps = {
defaultModel?: DefaultModel
modelList: Model[]
onSelect: (provider: string, model: ModelItem) => void
- scopeFeatures?: string[]
+ scopeFeatures?: ModelFeatureEnum[]
onHide: () => void
}
const Popup: FC = ({
diff --git a/web/app/components/plugins/install-plugin/utils.ts b/web/app/components/plugins/install-plugin/utils.ts
index f19a7fd287..79c6d7b031 100644
--- a/web/app/components/plugins/install-plugin/utils.ts
+++ b/web/app/components/plugins/install-plugin/utils.ts
@@ -5,15 +5,17 @@ import { isEmpty } from 'lodash-es'
export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaration): Plugin => {
return {
plugin_id: pluginManifest.plugin_unique_identifier,
- type: pluginManifest.category,
+ type: pluginManifest.category as Plugin['type'],
category: pluginManifest.category,
name: pluginManifest.name,
version: pluginManifest.version,
latest_version: '',
latest_package_identifier: '',
org: pluginManifest.author,
+ author: pluginManifest.author,
label: pluginManifest.label,
brief: pluginManifest.description,
+ description: pluginManifest.description,
icon: pluginManifest.icon,
verified: pluginManifest.verified,
introduction: '',
@@ -22,14 +24,17 @@ export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaratio
endpoint: {
settings: [],
},
- tags: [],
+ tags: pluginManifest.tags.map(tag => ({ name: tag })),
+ badges: [],
+ verification: { authorized_category: 'langgenius' },
+ from: 'package',
}
}
export const pluginManifestInMarketToPluginProps = (pluginManifest: PluginManifestInMarket): Plugin => {
return {
plugin_id: pluginManifest.plugin_unique_identifier,
- type: pluginManifest.category,
+ type: pluginManifest.category as Plugin['type'],
category: pluginManifest.category,
name: pluginManifest.name,
version: pluginManifest.latest_version,
@@ -38,6 +43,7 @@ export const pluginManifestInMarketToPluginProps = (pluginManifest: PluginManife
org: pluginManifest.org,
label: pluginManifest.label,
brief: pluginManifest.brief,
+ description: pluginManifest.brief,
icon: pluginManifest.icon,
verified: true,
introduction: pluginManifest.introduction,
@@ -49,6 +55,7 @@ export const pluginManifestInMarketToPluginProps = (pluginManifest: PluginManife
tags: [],
badges: pluginManifest.badges,
verification: isEmpty(pluginManifest.verification) ? { authorized_category: 'langgenius' } : pluginManifest.verification,
+ from: pluginManifest.from,
}
}
diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx
index 3041f13f2f..d4c0bc2d92 100644
--- a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx
@@ -50,7 +50,7 @@ const EndpointModal: FC = ({
// Fix: Process boolean fields to ensure they are sent as proper boolean values
const processedCredential = { ...tempCredential }
- formSchemas.forEach((field) => {
+ formSchemas.forEach((field: any) => {
if (field.type === 'boolean' && processedCredential[field.name] !== undefined) {
const value = processedCredential[field.name]
if (typeof value === 'string')
diff --git a/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx
index 873f187e8f..1393a1844f 100644
--- a/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/model-selector/index.tsx
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'
import type {
DefaultModel,
FormValue,
+ ModelFeatureEnum,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import { ModelStatusEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
@@ -57,7 +58,7 @@ const ModelParameterModal: FC = ({
const { isAPIKeySet } = useProviderContext()
const [open, setOpen] = useState(false)
const scopeArray = scope.split('&')
- const scopeFeatures = useMemo(() => {
+ const scopeFeatures = useMemo((): ModelFeatureEnum[] => {
if (scopeArray.includes('all'))
return []
return scopeArray.filter(item => ![
@@ -67,7 +68,7 @@ const ModelParameterModal: FC = ({
ModelTypeEnum.moderation,
ModelTypeEnum.speech2text,
ModelTypeEnum.tts,
- ].includes(item as ModelTypeEnum))
+ ].includes(item as ModelTypeEnum)).map(item => item as ModelFeatureEnum)
}, [scopeArray])
const { data: textGenerationList } = useModelList(ModelTypeEnum.textGeneration)
diff --git a/web/app/components/swr-initializer.tsx b/web/app/components/swr-initializer.tsx
index 1ab1567659..b7cd767c7a 100644
--- a/web/app/components/swr-initializer.tsx
+++ b/web/app/components/swr-initializer.tsx
@@ -56,10 +56,10 @@ const SwrInitializer = ({
}
const redirectUrl = resolvePostLoginRedirect(searchParams)
- if (redirectUrl)
+ if (redirectUrl) {
location.replace(redirectUrl)
- else
- router.replace(pathname)
+ return
+ }
setInit(true)
}
diff --git a/web/app/components/tools/add-tool-modal/category.tsx b/web/app/components/tools/add-tool-modal/category.tsx
index 270b4fc2bf..c1467a0ff4 100644
--- a/web/app/components/tools/add-tool-modal/category.tsx
+++ b/web/app/components/tools/add-tool-modal/category.tsx
@@ -9,6 +9,7 @@ import I18n from '@/context/i18n'
import { getLanguage } from '@/i18n-config/language'
import { useStore as useLabelStore } from '@/app/components/tools/labels/store'
import { fetchLabelList } from '@/service/tools'
+import { renderI18nObject } from '@/i18n-config'
type Props = {
value: string
@@ -55,14 +56,24 @@ const Category = ({
{t('tools.type.all')}
- {labelList.map(label => (
- onSelect(label.name)}>
-
-
+ {labelList.map((label) => {
+ const labelText = typeof label.label === 'string'
+ ? label.label
+ : (label.label ? renderI18nObject(label.label, language) : '')
+ return (
+ onSelect(label.name)}
+ >
+
+
+
+ {labelText}
- {label.label[language]}
-
- ))}
+ )
+ })}
)
}
diff --git a/web/app/components/tools/add-tool-modal/index.tsx b/web/app/components/tools/add-tool-modal/index.tsx
index e12ba3e334..392fa02f3a 100644
--- a/web/app/components/tools/add-tool-modal/index.tsx
+++ b/web/app/components/tools/add-tool-modal/index.tsx
@@ -10,6 +10,7 @@ import {
} from '@remixicon/react'
import { useMount } from 'ahooks'
import type { Collection, CustomCollectionBackend, Tool } from '../types'
+import type { CollectionType } from '../types'
import Type from './type'
import Category from './category'
import Tools from './tools'
@@ -129,7 +130,7 @@ const AddToolModal: FC = ({
const nexModelConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.agentConfig.tools.push({
provider_id: collection.id || collection.name,
- provider_type: collection.type,
+ provider_type: collection.type as CollectionType,
provider_name: collection.name,
tool_name: tool.name,
tool_label: tool.label[locale] || tool.label[locale.replaceAll('-', '_')],
diff --git a/web/app/components/tools/add-tool-modal/tools.tsx b/web/app/components/tools/add-tool-modal/tools.tsx
index 17a3df8357..20f7e6b0da 100644
--- a/web/app/components/tools/add-tool-modal/tools.tsx
+++ b/web/app/components/tools/add-tool-modal/tools.tsx
@@ -23,6 +23,14 @@ import type { Tool } from '@/app/components/tools/types'
import { CollectionType } from '@/app/components/tools/types'
import type { AgentTool } from '@/types/app'
import { MAX_TOOLS_NUM } from '@/config'
+import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
+import { renderI18nObject } from '@/i18n-config'
+
+const resolveI18nText = (value: TypeWithI18N | string | undefined, language: string): string => {
+ if (!value)
+ return ''
+ return typeof value === 'string' ? value : renderI18nObject(value, language)
+}
type ToolsProps = {
showWorkflowEmpty: boolean
@@ -53,7 +61,7 @@ const Blocks = ({
className='group mb-1 last-of-type:mb-0'
>
{list.map((tool) => {
@@ -62,7 +70,7 @@ const Blocks = ({
return ''
return tool.labels.map((name) => {
const label = labelList.find(item => item.name === name)
- return label?.label[language]
+ return resolveI18nText(label?.label, language)
}).filter(Boolean).join(', ')
})()
const added = !!addedTools?.find(v => v.provider_id === toolWithProvider.id && v.provider_type === toolWithProvider.type && v.tool_name === tool.name)
@@ -79,8 +87,8 @@ const Blocks = ({
type={BlockEnum.Tool}
toolIcon={toolWithProvider.icon}
/>
- {tool.label[language]}
- {tool.description[language]}
+ {resolveI18nText(tool.label, language)}
+ {resolveI18nText(tool.description, language)}
{tool.labels?.length > 0 && (
@@ -98,7 +106,7 @@ const Blocks = ({
type={BlockEnum.Tool}
toolIcon={toolWithProvider.icon}
/>
- {tool.label[language]}
+ {resolveI18nText(tool.label, language)}
{!needAuth && added && (
diff --git a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx
index 1a97357da5..6fba10bf81 100644
--- a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx
+++ b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx
@@ -12,7 +12,7 @@ import ConversationVariableModal from './conversation-variable-modal'
import { useChat } from './hooks'
import type { ChatWrapperRefType } from './index'
import Chat from '@/app/components/base/chat/chat'
-import type { ChatItem, ChatItemInTree, OnSend } from '@/app/components/base/chat/types'
+import type { ChatItem, OnSend } from '@/app/components/base/chat/types'
import { useFeatures } from '@/app/components/base/features/hooks'
import {
fetchSuggestedQuestions,
@@ -117,7 +117,7 @@ const ChatWrapper = (
)
}, [handleSend, workflowStore, conversationId, chatList, appDetail])
- const doRegenerate = useCallback((chatItem: ChatItemInTree, editedQuestion?: { message: string, files?: FileEntity[] }) => {
+ const doRegenerate = useCallback((chatItem: ChatItem, editedQuestion?: { message: string, files?: FileEntity[] }) => {
const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)!
const parentAnswer = chatList.find(item => item.id === question.parentMessageId)
doSend(editedQuestion ? editedQuestion.message : question.content,
diff --git a/web/app/components/workflow/utils/layout.ts b/web/app/components/workflow/utils/elk-layout.ts
similarity index 97%
rename from web/app/components/workflow/utils/layout.ts
rename to web/app/components/workflow/utils/elk-layout.ts
index b3cf3b0d88..69acbf9aff 100644
--- a/web/app/components/workflow/utils/layout.ts
+++ b/web/app/components/workflow/utils/elk-layout.ts
@@ -4,18 +4,18 @@ import { cloneDeep } from 'lodash-es'
import type {
Edge,
Node,
-} from '../types'
+} from '@/app/components/workflow/types'
import {
BlockEnum,
-} from '../types'
+} from '@/app/components/workflow/types'
import {
CUSTOM_NODE,
NODE_LAYOUT_HORIZONTAL_PADDING,
NODE_LAYOUT_VERTICAL_PADDING,
-} from '../constants'
-import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants'
-import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants'
-import type { CaseItem, IfElseNodeType } from '../nodes/if-else/types'
+} from '@/app/components/workflow/constants'
+import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
+import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
+import type { CaseItem, IfElseNodeType } from '@/app/components/workflow/nodes/if-else/types'
// Although the file name refers to Dagre, the implementation now relies on ELK's layered algorithm.
// Keep the export signatures unchanged to minimise the blast radius while we migrate the layout stack.
diff --git a/web/app/components/workflow/utils/index.ts b/web/app/components/workflow/utils/index.ts
index e9ae2d1ef0..53a423de34 100644
--- a/web/app/components/workflow/utils/index.ts
+++ b/web/app/components/workflow/utils/index.ts
@@ -1,7 +1,7 @@
export * from './node'
export * from './edge'
export * from './workflow-init'
-export * from './layout'
+export * from './elk-layout'
export * from './common'
export * from './tool'
export * from './workflow'
diff --git a/web/app/layout.tsx b/web/app/layout.tsx
index 1be802460b..c83ea7fd85 100644
--- a/web/app/layout.tsx
+++ b/web/app/layout.tsx
@@ -57,6 +57,7 @@ const LocaleLayout = async ({
[DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER,
[DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL,
[DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL]: process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL,
+ [DatasetAttr.DATA_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX]: process.env.NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX,
[DatasetAttr.NEXT_PUBLIC_ZENDESK_WIDGET_KEY]: process.env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY,
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT,
[DatasetAttr.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION]: process.env.NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION,
diff --git a/web/app/signin/normal-form.tsx b/web/app/signin/normal-form.tsx
index 920a992b4f..29e21b8ba2 100644
--- a/web/app/signin/normal-form.tsx
+++ b/web/app/signin/normal-form.tsx
@@ -135,8 +135,8 @@ const NormalForm = () => {
{!systemFeatures.branding.enabled && {t('login.joinTipStart')}{workspaceName}{t('login.joinTipEnd')} }
:
- {t('login.pageTitle')}
- {!systemFeatures.branding.enabled && {t('login.welcome')} }
+ {systemFeatures.branding.enabled ? t('login.pageTitleForE') : t('login.pageTitle')}
+ {t('login.welcome')}
}
diff --git a/web/app/signin/utils/post-login-redirect.ts b/web/app/signin/utils/post-login-redirect.ts
index 37ab122dfa..45e2c55941 100644
--- a/web/app/signin/utils/post-login-redirect.ts
+++ b/web/app/signin/utils/post-login-redirect.ts
@@ -1,4 +1,4 @@
-import { OAUTH_AUTHORIZE_PENDING_KEY, REDIRECT_URL_KEY } from '@/app/account/oauth/authorize/page'
+import { OAUTH_AUTHORIZE_PENDING_KEY, REDIRECT_URL_KEY } from '@/app/account/oauth/authorize/constants'
import dayjs from 'dayjs'
import type { ReadonlyURLSearchParams } from 'next/navigation'
diff --git a/web/config/index.ts b/web/config/index.ts
index 0e876b800e..158d9976fc 100644
--- a/web/config/index.ts
+++ b/web/config/index.ts
@@ -375,6 +375,11 @@ export const ENABLE_WEBSITE_WATERCRAWL = getBooleanConfig(
DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL,
false,
)
+export const ENABLE_SINGLE_DOLLAR_LATEX = getBooleanConfig(
+ process.env.NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX,
+ DatasetAttr.DATA_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX,
+ false,
+)
export const VALUE_SELECTOR_DELIMITER = '@@@'
diff --git a/web/context/debug-configuration.ts b/web/context/debug-configuration.ts
index dba2e7a231..1358940e39 100644
--- a/web/context/debug-configuration.ts
+++ b/web/context/debug-configuration.ts
@@ -210,6 +210,8 @@ const DebugConfigurationContext = createContext({
prompt_template: '',
prompt_variables: [],
},
+ chat_prompt_config: DEFAULT_CHAT_PROMPT_CONFIG,
+ completion_prompt_config: DEFAULT_COMPLETION_PROMPT_CONFIG,
more_like_this: null,
opening_statement: '',
suggested_questions: [],
@@ -220,6 +222,14 @@ const DebugConfigurationContext = createContext({
suggested_questions_after_answer: null,
retriever_resource: null,
annotation_reply: null,
+ external_data_tools: [],
+ system_parameters: {
+ audio_file_size_limit: 0,
+ file_size_limit: 0,
+ image_file_size_limit: 0,
+ video_file_size_limit: 0,
+ workflow_file_upload_limit: 0,
+ },
dataSets: [],
agentConfig: DEFAULT_AGENT_SETTING,
},
diff --git a/web/docker/entrypoint.sh b/web/docker/entrypoint.sh
index c12ebc0812..b32e648922 100755
--- a/web/docker/entrypoint.sh
+++ b/web/docker/entrypoint.sh
@@ -34,6 +34,7 @@ export NEXT_PUBLIC_MAX_TOOLS_NUM=${MAX_TOOLS_NUM}
export NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER=${ENABLE_WEBSITE_JINAREADER:-true}
export NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL=${ENABLE_WEBSITE_FIRECRAWL:-true}
export NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL=${ENABLE_WEBSITE_WATERCRAWL:-true}
+export NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX=${NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX:-false}
export NEXT_PUBLIC_LOOP_NODE_MAX_COUNT=${LOOP_NODE_MAX_COUNT}
export NEXT_PUBLIC_MAX_PARALLEL_LIMIT=${MAX_PARALLEL_LIMIT}
export NEXT_PUBLIC_MAX_ITERATIONS_NUM=${MAX_ITERATIONS_NUM}
diff --git a/web/i18n/de-DE/login.ts b/web/i18n/de-DE/login.ts
index a4c9165e23..4705a73087 100644
--- a/web/i18n/de-DE/login.ts
+++ b/web/i18n/de-DE/login.ts
@@ -120,6 +120,7 @@ const translation = {
noAccount: 'Haben Sie kein Konto?',
verifyMail: 'Fahren Sie mit dem Bestätigungscode fort',
},
+ pageTitleForE: 'Hey, lass uns anfangen!',
}
export default translation
diff --git a/web/i18n/en-US/dataset-creation.ts b/web/i18n/en-US/dataset-creation.ts
index 54d5a54fb4..f32639a6b4 100644
--- a/web/i18n/en-US/dataset-creation.ts
+++ b/web/i18n/en-US/dataset-creation.ts
@@ -38,7 +38,7 @@ const translation = {
button: 'Drag and drop file or folder, or',
buttonSingleFile: 'Drag and drop file, or',
browse: 'Browse',
- tip: 'Supports {{supportTypes}}. Max {{batchCount}} in a batch and {{size}} MB each.',
+ tip: 'Supports {{supportTypes}}. Max {{batchCount}} in a batch and {{size}} MB each. Max total {{totalCount}} files.',
validation: {
typeError: 'File type not supported',
size: 'File too large. Maximum is {{size}}MB',
diff --git a/web/i18n/en-US/login.ts b/web/i18n/en-US/login.ts
index 6015098022..dd923db217 100644
--- a/web/i18n/en-US/login.ts
+++ b/web/i18n/en-US/login.ts
@@ -1,5 +1,6 @@
const translation = {
pageTitle: 'Log in to Dify',
+ pageTitleForE: 'Hey, let\'s get started!',
welcome: '👋 Welcome! Please log in to get started.',
email: 'Email address',
emailPlaceholder: 'Your email',
diff --git a/web/i18n/es-ES/login.ts b/web/i18n/es-ES/login.ts
index ba8ad292cc..cbc223e7da 100644
--- a/web/i18n/es-ES/login.ts
+++ b/web/i18n/es-ES/login.ts
@@ -120,6 +120,7 @@ const translation = {
welcome: '👋 ¡Bienvenido! Por favor, completa los detalles para comenzar.',
verifyMail: 'Continuar con el código de verificación',
},
+ pageTitleForE: '¡Hola, vamos a empezar!',
}
export default translation
diff --git a/web/i18n/fa-IR/login.ts b/web/i18n/fa-IR/login.ts
index b57687cf5d..83382f3c9d 100644
--- a/web/i18n/fa-IR/login.ts
+++ b/web/i18n/fa-IR/login.ts
@@ -120,6 +120,7 @@ const translation = {
noAccount: 'حساب کاربری ندارید؟',
verifyMail: 'ادامه با کد تأیید',
},
+ pageTitleForE: 'هی، بیا شروع کنیم!',
}
export default translation
diff --git a/web/i18n/fr-FR/login.ts b/web/i18n/fr-FR/login.ts
index deae8e3ff4..3abb6fba2a 100644
--- a/web/i18n/fr-FR/login.ts
+++ b/web/i18n/fr-FR/login.ts
@@ -120,6 +120,7 @@ const translation = {
verifyMail: 'Continuez avec le code de vérification',
createAccount: 'Créez votre compte',
},
+ pageTitleForE: 'Hé, commençons !',
}
export default translation
diff --git a/web/i18n/hi-IN/login.ts b/web/i18n/hi-IN/login.ts
index fee51208c7..27b7df9849 100644
--- a/web/i18n/hi-IN/login.ts
+++ b/web/i18n/hi-IN/login.ts
@@ -125,6 +125,7 @@ const translation = {
welcome: '👋 स्वागत है! कृपया शुरू करने के लिए विवरण भरें।',
haveAccount: 'क्या आपका पहले से एक खाता है?',
},
+ pageTitleForE: 'अरे, चलो शुरू करें!',
}
export default translation
diff --git a/web/i18n/id-ID/login.ts b/web/i18n/id-ID/login.ts
index 41c7e04ec4..1590aa81a2 100644
--- a/web/i18n/id-ID/login.ts
+++ b/web/i18n/id-ID/login.ts
@@ -120,6 +120,7 @@ const translation = {
noAccount: 'Tidak punya akun?',
welcome: '👋 Selamat datang! Silakan isi detail untuk memulai.',
},
+ pageTitleForE: 'Hei, ayo kita mulai!',
}
export default translation
diff --git a/web/i18n/it-IT/login.ts b/web/i18n/it-IT/login.ts
index 5d6b040daf..e19baca6a3 100644
--- a/web/i18n/it-IT/login.ts
+++ b/web/i18n/it-IT/login.ts
@@ -130,6 +130,7 @@ const translation = {
signUp: 'Iscriviti',
welcome: '👋 Benvenuto! Per favore compila i dettagli per iniziare.',
},
+ pageTitleForE: 'Ehi, cominciamo!',
}
export default translation
diff --git a/web/i18n/ja-JP/login.ts b/web/i18n/ja-JP/login.ts
index d1e9a9e0e2..7069315c9d 100644
--- a/web/i18n/ja-JP/login.ts
+++ b/web/i18n/ja-JP/login.ts
@@ -1,5 +1,6 @@
const translation = {
pageTitle: 'Dify にログイン',
+ pageTitleForE: 'はじめましょう!',
welcome: '👋 ようこそ!まずはログインしてご利用ください。',
email: 'メールアドレス',
emailPlaceholder: 'メールアドレスを入力してください',
diff --git a/web/i18n/ko-KR/login.ts b/web/i18n/ko-KR/login.ts
index 8cde21472c..6d3d47a602 100644
--- a/web/i18n/ko-KR/login.ts
+++ b/web/i18n/ko-KR/login.ts
@@ -120,6 +120,7 @@ const translation = {
noAccount: '계정이 없으신가요?',
welcome: '👋 환영합니다! 시작하려면 세부 정보를 입력해 주세요.',
},
+ pageTitleForE: '이봐, 시작하자!',
}
export default translation
diff --git a/web/i18n/pl-PL/login.ts b/web/i18n/pl-PL/login.ts
index 394fe6c402..34519cd2b3 100644
--- a/web/i18n/pl-PL/login.ts
+++ b/web/i18n/pl-PL/login.ts
@@ -125,6 +125,7 @@ const translation = {
haveAccount: 'Masz już konto?',
welcome: '👋 Witaj! Proszę wypełnić szczegóły, aby rozpocząć.',
},
+ pageTitleForE: 'Hej, zaczynajmy!',
}
export default translation
diff --git a/web/i18n/pt-BR/login.ts b/web/i18n/pt-BR/login.ts
index 200e7bf30c..4fa9f36146 100644
--- a/web/i18n/pt-BR/login.ts
+++ b/web/i18n/pt-BR/login.ts
@@ -120,6 +120,7 @@ const translation = {
signUp: 'Inscreva-se',
welcome: '👋 Bem-vindo! Por favor, preencha os detalhes para começar.',
},
+ pageTitleForE: 'Ei, vamos começar!',
}
export default translation
diff --git a/web/i18n/ro-RO/login.ts b/web/i18n/ro-RO/login.ts
index 34cd4a5ffd..f676b812cb 100644
--- a/web/i18n/ro-RO/login.ts
+++ b/web/i18n/ro-RO/login.ts
@@ -120,6 +120,7 @@ const translation = {
createAccount: 'Creează-ți contul',
welcome: '👋 Buna! Te rugăm să completezi detaliile pentru a începe.',
},
+ pageTitleForE: 'Hei, hai să începem!',
}
export default translation
diff --git a/web/i18n/ru-RU/login.ts b/web/i18n/ru-RU/login.ts
index bfb2860b57..f864bdb845 100644
--- a/web/i18n/ru-RU/login.ts
+++ b/web/i18n/ru-RU/login.ts
@@ -120,6 +120,7 @@ const translation = {
verifyMail: 'Продолжите с кодом проверки',
welcome: '👋 Добро пожаловать! Пожалуйста, заполните данные, чтобы начать.',
},
+ pageTitleForE: 'Привет, давай начнем!',
}
export default translation
diff --git a/web/i18n/sl-SI/login.ts b/web/i18n/sl-SI/login.ts
index 4e5b12689d..81f280666b 100644
--- a/web/i18n/sl-SI/login.ts
+++ b/web/i18n/sl-SI/login.ts
@@ -120,6 +120,7 @@ const translation = {
noAccount: 'Nimate računa?',
welcome: '👋 Dobrodošli! Prosimo, izpolnite podatke, da začnete.',
},
+ pageTitleForE: 'Hej, začnimo!',
}
export default translation
diff --git a/web/i18n/th-TH/login.ts b/web/i18n/th-TH/login.ts
index 732af8a875..517eee95a2 100644
--- a/web/i18n/th-TH/login.ts
+++ b/web/i18n/th-TH/login.ts
@@ -120,6 +120,7 @@ const translation = {
verifyMail: 'โปรดดำเนินการต่อด้วยรหัสการตรวจสอบ',
haveAccount: 'มีบัญชีอยู่แล้วใช่ไหม?',
},
+ pageTitleForE: 'เฮ้ เรามาเริ่มกันเถอะ!',
}
export default translation
diff --git a/web/i18n/tr-TR/login.ts b/web/i18n/tr-TR/login.ts
index b8bd6d74af..d6ada5f950 100644
--- a/web/i18n/tr-TR/login.ts
+++ b/web/i18n/tr-TR/login.ts
@@ -120,6 +120,7 @@ const translation = {
haveAccount: 'Zaten bir hesabınız var mı?',
welcome: '👋 Hoş geldiniz! Başlamak için lütfen detayları doldurun.',
},
+ pageTitleForE: 'Hey, haydi başlayalım!',
}
export default translation
diff --git a/web/i18n/uk-UA/login.ts b/web/i18n/uk-UA/login.ts
index 1fa4d414f7..1a1a6d7068 100644
--- a/web/i18n/uk-UA/login.ts
+++ b/web/i18n/uk-UA/login.ts
@@ -120,6 +120,7 @@ const translation = {
noAccount: 'Не маєте облікового запису?',
welcome: '👋 Ласкаво просимо! Будь ласка, заповніть деталі, щоб почати.',
},
+ pageTitleForE: 'Гей, давай почнемо!',
}
export default translation
diff --git a/web/i18n/vi-VN/login.ts b/web/i18n/vi-VN/login.ts
index 6d877fffef..dec7eddee2 100644
--- a/web/i18n/vi-VN/login.ts
+++ b/web/i18n/vi-VN/login.ts
@@ -120,6 +120,7 @@ const translation = {
verifyMail: 'Tiếp tục với mã xác minh',
welcome: '👋 Chào mừng! Vui lòng điền vào các chi tiết để bắt đầu.',
},
+ pageTitleForE: 'Này, hãy bắt đầu nào!',
}
export default translation
diff --git a/web/i18n/zh-Hans/dataset-creation.ts b/web/i18n/zh-Hans/dataset-creation.ts
index 5b1ff2435c..f780269914 100644
--- a/web/i18n/zh-Hans/dataset-creation.ts
+++ b/web/i18n/zh-Hans/dataset-creation.ts
@@ -38,7 +38,7 @@ const translation = {
button: '拖拽文件或文件夹至此,或者',
buttonSingleFile: '拖拽文件至此,或者',
browse: '选择文件',
- tip: '已支持 {{supportTypes}},每批最多 {{batchCount}} 个文件,每个文件不超过 {{size}} MB。',
+ tip: '已支持 {{supportTypes}},每批最多 {{batchCount}} 个文件,每个文件不超过 {{size}} MB ,总数不超过 {{totalCount}} 个文件。',
validation: {
typeError: '文件类型不支持',
size: '文件太大了,不能超过 {{size}}MB',
diff --git a/web/i18n/zh-Hans/login.ts b/web/i18n/zh-Hans/login.ts
index 82c6b355f9..13a75eaaaa 100644
--- a/web/i18n/zh-Hans/login.ts
+++ b/web/i18n/zh-Hans/login.ts
@@ -1,5 +1,6 @@
const translation = {
pageTitle: '登录 Dify',
+ pageTitleForE: '嗨,近来可好',
welcome: '👋 欢迎!请登录以开始使用。',
email: '邮箱',
emailPlaceholder: '输入邮箱地址',
diff --git a/web/i18n/zh-Hant/login.ts b/web/i18n/zh-Hant/login.ts
index 0e7608140f..56150a0ed3 100644
--- a/web/i18n/zh-Hant/login.ts
+++ b/web/i18n/zh-Hant/login.ts
@@ -1,5 +1,6 @@
const translation = {
pageTitle: '嗨,近來可好',
+ pageTitleForE: '嗨,近來可好',
welcome: '👋 歡迎來到 Dify, 登入以繼續',
email: '郵箱',
emailPlaceholder: '輸入郵箱地址',
diff --git a/web/models/common.ts b/web/models/common.ts
index aa6372e36f..d83ae5fb98 100644
--- a/web/models/common.ts
+++ b/web/models/common.ts
@@ -236,6 +236,7 @@ export type FileUploadConfigResponse = {
audio_file_size_limit?: number // default is 50MB
video_file_size_limit?: number // default is 100MB
workflow_file_upload_limit?: number // default is 10
+ file_upload_limit: number // default is 5
}
export type InvitationResult = {
diff --git a/web/models/datasets.ts b/web/models/datasets.ts
index aeeb5c161a..39313d68a3 100644
--- a/web/models/datasets.ts
+++ b/web/models/datasets.ts
@@ -344,6 +344,8 @@ export type WebsiteCrawlInfo = {
description: string
source_url: string
title: string
+ provider?: string
+ job_id?: string
}
export type OnlineDocumentInfo = {
diff --git a/web/models/debug.ts b/web/models/debug.ts
index 630c48a970..90f79cbf8d 100644
--- a/web/models/debug.ts
+++ b/web/models/debug.ts
@@ -9,6 +9,7 @@ import type {
MetadataFilteringModeEnum,
} from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import type { ModelConfig as NodeModelConfig } from '@/app/components/workflow/types'
+import type { ExternalDataTool } from '@/models/common'
export type Inputs = Record
export enum PromptMode {
@@ -133,6 +134,8 @@ export type ModelConfig = {
model_id: string
mode: ModelModeType
configs: PromptConfig
+ chat_prompt_config?: ChatPromptConfig | null
+ completion_prompt_config?: CompletionPromptConfig | null
opening_statement: string | null
more_like_this: MoreLikeThisConfig | null
suggested_questions: string[] | null
@@ -143,6 +146,14 @@ export type ModelConfig = {
retriever_resource: RetrieverResourceConfig | null
sensitive_word_avoidance: ModerationConfig | null
annotation_reply: AnnotationReplyConfig | null
+ external_data_tools?: ExternalDataTool[] | null
+ system_parameters: {
+ audio_file_size_limit: number
+ file_size_limit: number
+ image_file_size_limit: number
+ video_file_size_limit: number
+ workflow_file_upload_limit: number
+ }
dataSets: any[]
agentConfig: AgentConfig
}
diff --git a/web/package.json b/web/package.json
index abc0914469..88927ca5c4 100644
--- a/web/package.json
+++ b/web/package.json
@@ -22,7 +22,7 @@
"dev": "cross-env NODE_OPTIONS='--inspect' next dev --turbopack",
"build": "next build",
"build:docker": "next build && node scripts/optimize-standalone.js",
- "start": "cp -r .next/static .next/standalone/.next/static && cp -r public .next/standalone/public && cross-env PORT=$npm_config_port HOSTNAME=$npm_config_host node .next/standalone/server.js",
+ "start": "node ./scripts/copy-and-start.mjs",
"lint": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache",
"lint:fix": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix",
"lint:quiet": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --quiet",
@@ -144,7 +144,7 @@
"@babel/core": "^7.28.4",
"@chromatic-com/storybook": "^4.1.1",
"@eslint-react/eslint-plugin": "^1.53.1",
- "@happy-dom/jest-environment": "^20.0.7",
+ "@happy-dom/jest-environment": "^20.0.8",
"@mdx-js/loader": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@next/bundle-analyzer": "15.5.4",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index f05c225cab..1422f071c6 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -356,8 +356,8 @@ importers:
specifier: ^1.53.1
version: 1.53.1(eslint@9.38.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3)
'@happy-dom/jest-environment':
- specifier: ^20.0.7
- version: 20.0.7(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)
+ specifier: ^20.0.8
+ version: 20.0.8(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)
'@mdx-js/loader':
specifier: ^3.1.1
version: 3.1.1(webpack@5.102.1(esbuild@0.25.0)(uglify-js@3.19.3))
@@ -688,6 +688,10 @@ packages:
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.27.1':
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'}
@@ -705,6 +709,11 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1':
resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==}
engines: {node: '>=6.9.0'}
@@ -1230,6 +1239,10 @@ packages:
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
engines: {node: '>=6.9.0'}
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+ engines: {node: '>=6.9.0'}
+
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
@@ -1747,8 +1760,8 @@ packages:
'@formatjs/intl-localematcher@0.5.10':
resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
- '@happy-dom/jest-environment@20.0.7':
- resolution: {integrity: sha512-f7cvUghxPIUS8L21uSNab1GYXPr6+7FvltpsWyzrSzhSbjhDWr5Ixcy5bv2DqaQEhAKIQ7SYBYD5n4+SSHwfig==}
+ '@happy-dom/jest-environment@20.0.8':
+ resolution: {integrity: sha512-e8/c1EW+vUF7MFTZZtPbWrD3rStPnx3X8M4pAaOU++x+1lsXr/bsdoLoHs6bQ2kEZyPRhate3sC6MnpVD/O/9A==}
engines: {node: '>=20.0.0'}
peerDependencies:
'@jest/environment': '>=25.0.0'
@@ -3525,8 +3538,8 @@ packages:
'@types/node@18.15.0':
resolution: {integrity: sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==}
- '@types/node@20.19.22':
- resolution: {integrity: sha512-hRnu+5qggKDSyWHlnmThnUqg62l29Aj/6vcYgUaSFL9oc7DVjeWEQN3PRgdSc6F8d9QRMWkf36CLMch1Do/+RQ==}
+ '@types/node@20.19.23':
+ resolution: {integrity: sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==}
'@types/papaparse@5.3.16':
resolution: {integrity: sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg==}
@@ -5575,8 +5588,8 @@ packages:
hachure-fill@0.5.2:
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
- happy-dom@20.0.7:
- resolution: {integrity: sha512-CywLfzmYxP5OYpuAG0usFY0CpxJtwYR+w8Mms5J8W29Y2Pzf6rbfQS2M523tRZTb0oLA+URopPtnAQX2fupHZQ==}
+ happy-dom@20.0.8:
+ resolution: {integrity: sha512-TlYaNQNtzsZ97rNMBAm8U+e2cUQXNithgfCizkDgc11lgmN4j9CKMhO3FPGKWQYPwwkFcPpoXYF/CqEPLgzfOg==}
engines: {node: '>=20.0.0'}
has-flag@4.0.0:
@@ -5803,6 +5816,7 @@ packages:
intersection-observer@0.12.2:
resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
+ deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.
is-alphabetical@1.0.4:
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
@@ -6354,6 +6368,9 @@ packages:
magic-string@0.30.19:
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
magicast@0.3.5:
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
@@ -8932,6 +8949,8 @@ snapshots:
'@babel/helper-validator-identifier@7.27.1': {}
+ '@babel/helper-validator-identifier@7.28.5': {}
+
'@babel/helper-validator-option@7.27.1': {}
'@babel/helper-wrap-function@7.28.3':
@@ -8951,6 +8970,10 @@ snapshots:
dependencies:
'@babel/types': 7.28.4
+ '@babel/parser@7.28.5':
+ dependencies:
+ '@babel/types': 7.28.5
+
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
@@ -9607,6 +9630,11 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
+ '@babel/types@7.28.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
'@bcoe/v8-coverage@0.2.3': {}
'@braintree/sanitize-url@7.1.1': {}
@@ -10099,12 +10127,12 @@ snapshots:
dependencies:
tslib: 2.8.1
- '@happy-dom/jest-environment@20.0.7(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)':
+ '@happy-dom/jest-environment@20.0.8(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)':
dependencies:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
- happy-dom: 20.0.7
+ happy-dom: 20.0.8
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -12091,7 +12119,7 @@ snapshots:
'@types/node@18.15.0': {}
- '@types/node@20.19.22':
+ '@types/node@20.19.23':
dependencies:
undici-types: 6.21.0
@@ -12292,7 +12320,7 @@ snapshots:
'@vue/compiler-core@3.5.17':
dependencies:
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.5
'@vue/shared': 3.5.17
entities: 4.5.0
estree-walker: 2.0.2
@@ -12318,13 +12346,13 @@ snapshots:
'@vue/compiler-sfc@3.5.17':
dependencies:
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.5
'@vue/compiler-core': 3.5.17
'@vue/compiler-dom': 3.5.17
'@vue/compiler-ssr': 3.5.17
'@vue/shared': 3.5.17
estree-walker: 2.0.2
- magic-string: 0.30.19
+ magic-string: 0.30.21
postcss: 8.5.6
source-map-js: 1.2.1
@@ -14504,9 +14532,9 @@ snapshots:
hachure-fill@0.5.2: {}
- happy-dom@20.0.7:
+ happy-dom@20.0.8:
dependencies:
- '@types/node': 20.19.22
+ '@types/node': 20.19.23
'@types/whatwg-mimetype': 3.0.2
whatwg-mimetype: 3.0.0
@@ -15518,6 +15546,10 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
magicast@0.3.5:
dependencies:
'@babel/parser': 7.28.4
diff --git a/web/scripts/copy-and-start.mjs b/web/scripts/copy-and-start.mjs
new file mode 100644
index 0000000000..b23ce636a4
--- /dev/null
+++ b/web/scripts/copy-and-start.mjs
@@ -0,0 +1,115 @@
+#!/usr/bin/env node
+/**
+ * This script copies static files to the target directory and starts the server.
+ * It is intended to be used as a replacement for `next start`.
+ */
+
+import { cp, mkdir, stat } from 'node:fs/promises'
+import { spawn } from 'node:child_process'
+import path from 'node:path'
+
+// Configuration for directories to copy
+const DIRS_TO_COPY = [
+ {
+ src: path.join('.next', 'static'),
+ dest: path.join('.next', 'standalone', '.next', 'static'),
+ },
+ {
+ src: 'public',
+ dest: path.join('.next', 'standalone', 'public'),
+ },
+]
+
+// Path to the server script
+const SERVER_SCRIPT_PATH = path.join('.next', 'standalone', 'server.js')
+
+// Function to check if a path exists
+const pathExists = async (path) => {
+ try {
+ console.debug(`Checking if path exists: ${path}`)
+ await stat(path)
+ console.debug(`Path exists: ${path}`)
+ return true
+ }
+ catch (err) {
+ if (err.code === 'ENOENT') {
+ console.warn(`Path does not exist: ${path}`)
+ return false
+ }
+ throw err
+ }
+}
+
+// Function to recursively copy directories
+const copyDir = async (src, dest) => {
+ console.debug(`Copying directory from ${src} to ${dest}`)
+ await cp(src, dest, { recursive: true })
+ console.info(`Successfully copied ${src} to ${dest}`)
+}
+
+// Process each directory copy operation
+const copyAllDirs = async () => {
+ console.debug('Starting directory copy operations')
+ for (const { src, dest } of DIRS_TO_COPY) {
+ try {
+ // Instead of pre-creating destination directory, we ensure parent directory exists
+ const destParent = path.dirname(dest)
+ console.debug(`Ensuring destination parent directory exists: ${destParent}`)
+ await mkdir(destParent, { recursive: true })
+ if (await pathExists(src)) {
+ await copyDir(src, dest)
+ }
+ else {
+ console.error(`Error: ${src} directory does not exist. This is a required build artifact.`)
+ process.exit(1)
+ }
+ }
+ catch (err) {
+ console.error(`Error processing ${src}:`, err.message)
+ process.exit(1)
+ }
+ }
+ console.debug('Finished directory copy operations')
+}
+
+// Run copy operations and start server
+const main = async () => {
+ console.debug('Starting copy-and-start script')
+ await copyAllDirs()
+
+ // Start server
+ const port = process.env.npm_config_port || process.env.PORT || '3000'
+ const host = process.env.npm_config_host || process.env.HOSTNAME || '0.0.0.0'
+
+ console.info(`Starting server on ${host}:${port}`)
+ console.debug(`Server script path: ${SERVER_SCRIPT_PATH}`)
+ console.debug(`Environment variables - PORT: ${port}, HOSTNAME: ${host}`)
+
+ const server = spawn(
+ process.execPath,
+ [SERVER_SCRIPT_PATH],
+ {
+ env: {
+ ...process.env,
+ PORT: port,
+ HOSTNAME: host,
+ },
+ stdio: 'inherit',
+ },
+ )
+
+ server.on('error', (err) => {
+ console.error('Failed to start server:', err)
+ process.exit(1)
+ })
+
+ server.on('exit', (code) => {
+ console.debug(`Server exited with code: ${code}`)
+ process.exit(code || 0)
+ })
+}
+
+main().catch((err) => {
+ console.error('Unexpected error:', err)
+ process.exit(1)
+})
diff --git a/web/types/app.ts b/web/types/app.ts
index abc5b34ca5..591bbf5e31 100644
--- a/web/types/app.ts
+++ b/web/types/app.ts
@@ -8,6 +8,7 @@ import type {
} from '@/models/datasets'
import type { UploadFileSetting } from '@/app/components/workflow/types'
import type { AccessMode } from '@/models/access-control'
+import type { ExternalDataTool } from '@/models/common'
export enum Theme {
light = 'light',
@@ -206,12 +207,12 @@ export type ModelConfig = {
suggested_questions?: string[]
pre_prompt: string
prompt_type: PromptMode
- chat_prompt_config: ChatPromptConfig | {}
- completion_prompt_config: CompletionPromptConfig | {}
+ chat_prompt_config?: ChatPromptConfig | null
+ completion_prompt_config?: CompletionPromptConfig | null
user_input_form: UserInputFormItem[]
dataset_query_variable?: string
more_like_this: {
- enabled?: boolean
+ enabled: boolean
}
suggested_questions_after_answer: {
enabled: boolean
@@ -237,12 +238,20 @@ export type ModelConfig = {
strategy?: AgentStrategy
tools: ToolItem[]
}
+ external_data_tools?: ExternalDataTool[]
model: Model
dataset_configs: DatasetConfigs
file_upload?: {
image: VisionSettings
} & UploadFileSetting
files?: VisionFile[]
+ system_parameters: {
+ audio_file_size_limit: number
+ file_size_limit: number
+ image_file_size_limit: number
+ video_file_size_limit: number
+ workflow_file_upload_limit: number
+ }
created_at?: number
updated_at?: number
}
@@ -360,6 +369,7 @@ export type App = {
updated_at: number
updated_by?: string
}
+ deleted_tools?: Array<{ id: string; tool_name: string }>
/** access control */
access_mode: AccessMode
max_active_requests?: number | null
diff --git a/web/types/feature.ts b/web/types/feature.ts
index 56fe0c0484..05421f53c3 100644
--- a/web/types/feature.ts
+++ b/web/types/feature.ts
@@ -122,6 +122,7 @@ export enum DatasetAttr {
DATA_PUBLIC_ENABLE_WEBSITE_JINAREADER = 'data-public-enable-website-jinareader',
DATA_PUBLIC_ENABLE_WEBSITE_FIRECRAWL = 'data-public-enable-website-firecrawl',
DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL = 'data-public-enable-website-watercrawl',
+ DATA_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX = 'data-public-enable-single-dollar-latex',
NEXT_PUBLIC_ZENDESK_WIDGET_KEY = 'next-public-zendesk-widget-key',
NEXT_PUBLIC_ZENDESK_FIELD_ID_ENVIRONMENT = 'next-public-zendesk-field-id-environment',
NEXT_PUBLIC_ZENDESK_FIELD_ID_VERSION = 'next-public-zendesk-field-id-version',
|