+ {csvTruncated && (
+
+ 文件较大,仅预览前 {MAX_PREVIEW_ROWS} 行数据
+
+ )}
{excelData.map((sheet, index) => (
{sheet.sheetName}
@@ -541,6 +603,7 @@ const DocumentPreview: FC = ({
scroll={{ x: 'max-content' }}
size="small"
bordered
+ virtual
/>
)}
diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts
index 47de99a8..7b7900f3 100644
--- a/web/src/i18n/en.ts
+++ b/web/src/i18n/en.ts
@@ -469,6 +469,7 @@ export const en = {
download: 'Download',
view: 'View',
updated_at: 'Updated At',
+ callbackUrlInvalid: 'Please enter a valid URL',
},
model: {
searchPlaceholder: 'search model…',
diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts
index 9fa8cc0d..06a3bd74 100644
--- a/web/src/i18n/zh.ts
+++ b/web/src/i18n/zh.ts
@@ -1106,6 +1106,7 @@ export const zh = {
download: '下载',
view: '查看',
updated_at: '更新时间',
+ callbackUrlInvalid: '请输入有效的 URL',
},
model: {
searchPlaceholder: '搜索模型…',
diff --git a/web/src/views/ApplicationConfig/TestChat/index.tsx b/web/src/views/ApplicationConfig/TestChat/index.tsx
index ad7931e2..c324622d 100644
--- a/web/src/views/ApplicationConfig/TestChat/index.tsx
+++ b/web/src/views/ApplicationConfig/TestChat/index.tsx
@@ -183,7 +183,7 @@ const TestChat: FC
= ({
const handleSend = () => {
if (loading || !application || !message || !message?.trim()) return
- const files = toolbarRef.current?.getFiles() || []
+ const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
const variables = toolbarRef.current?.getVariables() || []
const { isCanSend, params } = buildVariableParams(variables)
if (!isCanSend) return
@@ -235,7 +235,7 @@ const TestChat: FC = ({
const handleWorkflowSend = () => {
if (loading || !application || !message || !message?.trim()) return
- const files = toolbarRef.current?.getFiles() || []
+ const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
const variables = toolbarRef.current?.getVariables() || []
const { isCanSend, params } = buildVariableParams(variables)
if (!isCanSend) return
diff --git a/web/src/views/ApplicationConfig/components/Chat.tsx b/web/src/views/ApplicationConfig/components/Chat.tsx
index 56e1088b..38225104 100644
--- a/web/src/views/ApplicationConfig/components/Chat.tsx
+++ b/web/src/views/ApplicationConfig/components/Chat.tsx
@@ -189,7 +189,7 @@ const Chat: FC = ({
.then(() => {
const message = msg
if (!message?.trim()) return
- const files = toolbarRef.current?.getFiles() || []
+ const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
// Validate required variables before sending
let isCanSend = true
const params: Record = {}
@@ -350,7 +350,7 @@ const Chat: FC = ({
.then(() => {
const message = msg
if (!message || message.trim() === '') return
- const files = toolbarRef.current?.getFiles() || []
+ const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
addUserMessage(message, files)
setMessage(undefined)
toolbarRef.current?.setFiles([])
diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx
index 5fcb752d..d712720f 100644
--- a/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx
+++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx
@@ -24,7 +24,7 @@ interface FeaturesConfigModalProps {
refresh: (value: FeaturesConfigForm) => void;
source?: Application['type'];
}
-
+const max_file_count = 1;
/**
* Modal for copying applications
*/
@@ -133,7 +133,7 @@ const FeaturesConfigModal = forwardRef
{t('application.maxCount')}
- {fu.max_file_count} {t('application.unix')}
+ {max_file_count} {t('application.unix')}
diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx
index 3fb05a0e..f33b313b 100644
--- a/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx
+++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/FileUploadSettingModal.tsx
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-03-05
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-03-19 15:18:20
+ * @Last Modified time: 2026-03-19 20:19:14
*/
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd';
@@ -82,28 +82,27 @@ const defaultValues: FileUpload = {
"mp3",
"wav",
"m4a",
- "ogg",
- "flac"
],
document_enabled: false,
document_max_size_mb: 100,
document_allowed_extensions: [
"pdf",
"docx",
+ "doc",
"xlsx",
+ "xls",
"txt",
"csv",
- "json"
+ "json",
+ "md",
],
video_enabled: false,
video_max_size_mb: 100,
video_allowed_extensions: [
"mp4",
"mov",
- "avi",
- "webm"
],
- max_file_count: 5,
+ max_file_count: 1,
allowed_transfer_methods: 'both'
}
@@ -168,8 +167,8 @@ const FileUploadSettingModal = forwardRef
- {t('application.maxCount')}
-
+ {/* {t('application.maxCount')}
*/}
+
diff --git a/web/src/views/Conversation/components/FileUpload.tsx b/web/src/views/Conversation/components/FileUpload.tsx
index 166b00c8..8c646bea 100644
--- a/web/src/views/Conversation/components/FileUpload.tsx
+++ b/web/src/views/Conversation/components/FileUpload.tsx
@@ -23,7 +23,7 @@
import { useState, useEffect, forwardRef, useImperativeHandle, useMemo } from 'react';
import { Upload, Progress, App, Flex } from 'antd';
import type { UploadProps, UploadFile } from 'antd';
-import type { UploadProps as RcUploadProps } from 'antd/es/upload/interface';
+import type { UploadProps as RcUploadProps, RcFile, UploadFileStatus } from 'antd/es/upload/interface';
import { useTranslation } from 'react-i18next';
import { request } from '@/utils/request'
@@ -221,17 +221,29 @@ const UploadFiles = forwardRef(({
*/
const handleCustomRequest: RcUploadProps['customRequest'] = async (options) => {
const { file, onSuccess, onError } = options;
-
- try {
- const formData = new FormData();
- formData.append('file', file);
-
- const response = await request.uploadFile(action, formData, requestConfig);
-
- onSuccess?.({data: response});
- } catch (error) {
- onError?.(error as Error);
+ if (typeof file === 'string') return;
+ const rcFile = file as RcFile;
+ const formData = new FormData();
+ formData.append('file', rcFile);
+ const fileVo: UploadFile = {
+ uid: rcFile.uid,
+ name: rcFile.name,
+ status: 'uploading' as UploadFileStatus,
+ percent: 0,
+ type: rcFile.type,
+ originFileObj: rcFile,
+ thumbUrl: URL.createObjectURL(rcFile)
}
+ onChange?.(fileVo)
+ request.uploadFile(action, formData, requestConfig)
+ .then(res => {
+ onSuccess?.({ data: res });
+ })
+ .catch((error) => {
+ onError?.(error as Error);
+ fileVo.status = 'error'
+ onChange?.(fileVo)
+ })
};
/**
diff --git a/web/src/views/Conversation/components/UploadFileListModal.tsx b/web/src/views/Conversation/components/UploadFileListModal.tsx
index ce71066d..4d2e83ee 100644
--- a/web/src/views/Conversation/components/UploadFileListModal.tsx
+++ b/web/src/views/Conversation/components/UploadFileListModal.tsx
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-06 21:09:47
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-03-18 21:10:01
+ * @Last Modified time: 2026-03-19 20:32:32
*/
/**
* Upload File List Modal Component
@@ -19,7 +19,10 @@
* @component
*/
import { forwardRef, useImperativeHandle, useState, useMemo } from 'react';
-import { Form, Input, Select, Button, Flex } from 'antd';
+import { Form, Input, Select,
+ // Button,
+ Flex
+} from 'antd';
import { useTranslation } from 'react-i18next';
import type { UploadFileListModalRef } from '../types'
@@ -105,9 +108,11 @@ const UploadFileListModal = forwardRef
-
- {(fields, { add, remove }) => (
+ {(fields,
+ // { add, remove }
+ ) => (
<>
{/* Render each file entry with type selector and URL input */}
{fields.map(({ key, name, ...restField }) => (
@@ -116,6 +121,9 @@ const UploadFileListModal = forwardRef
*/}
))}
- = {}
diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx
index 4b52b7fe..8859a8c8 100644
--- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx
+++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx
@@ -11,7 +11,7 @@ import { useNavigate, useParams, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager';
import { Button, Spin, message, Switch } from 'antd';
-import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk } from '@/api/knowledgeBase';
+import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk, getFileUrl } from '@/api/knowledgeBase';
import type { KnowledgeBaseDocumentData, RecallTestData } from '@/views/KnowledgeBase/types';
import { formatDateTime } from '@/utils/format';
import InfoPanel, { type InfoItem } from '../components/InfoPanel';
@@ -138,7 +138,7 @@ const DocumentDetails: FC = () => {
const response = await getDocumentDetail(documentId);
setDocument(response);
setInfoItems(formatDocumentInfo(response));
- const url = `${imagePath}/api/files/${response.file_id}`
+ const url = `${window.location.origin}/api/files/${response.file_id}`;
setFileUrl(url);
setParserMode(response?.parser_config?.auto_questions || 0)
// ChunkList will be called automatically in useEffect based on document.progress
diff --git a/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx b/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx
index 6ffc61e7..df7d639e 100644
--- a/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx
+++ b/web/src/views/UserMemoryDetail/components/RelationshipNetwork.tsx
@@ -191,24 +191,28 @@ const RelationshipNetwork: FC = () => {
})}>
{(selectedNode as RawCommunityNode).properties.community_id
?
-
- {(selectedNode as RawCommunityNode).properties.name}
-
-
{t('userMemory.summary')}
-
- {(selectedNode as RawCommunityNode).properties.summary}
-
-
- {t('userMemory.member_count')}
- {(selectedNode as RawCommunityNode).properties.member_count}{t('userMemory.member_count_desc')}
-
+
+ {(selectedNode as RawCommunityNode).properties.name || selectedNode.id}
+
+ {(selectedNode as RawCommunityNode).properties.summary && <>
+
{t('userMemory.summary')}
+
+ {(selectedNode as RawCommunityNode).properties.summary}
+
+ >}
+
+ {t('userMemory.member_count')}
+ {(selectedNode as RawCommunityNode).properties.member_count}{t('userMemory.member_count_desc')}
+
-
-
{t('userMemory.core_entities')}
-
- {(selectedNode as RawCommunityNode).properties.core_entities.map((entity, index) => - {entity}
)}
-
-
+ {(selectedNode as RawCommunityNode).properties.core_entities && <>
+
+ {t('userMemory.core_entities')}
+
+ {(selectedNode as RawCommunityNode).properties.core_entities?.map((entity, index) => - {entity}
)}
+
+ >}
+
: <>
{(selectedNode as Node).name &&
diff --git a/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx b/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx
index ea859dce..edb9e526 100644
--- a/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx
+++ b/web/src/views/UserMemoryDetail/pages/WorkingDetail.tsx
@@ -4,12 +4,14 @@
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-16 15:10:17
*/
-import { type FC, useEffect, useState, useMemo } from 'react'
+import { type FC, useEffect, useState, useMemo, useRef } from 'react'
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom'
import { Row, Col, Skeleton, Button, Divider, Tooltip, Flex } from 'antd'
+
+import InfiniteScroll from 'react-infinite-scroll-component'
import RbCard from '@/components/RbCard/Card'
import {
getConversations,
@@ -61,6 +63,8 @@ const WorkingDetail: FC = () => {
const { id } = useParams()
const [loading, setLoading] = useState
(false)
const [data, setData] = useState([])
+ const [hasMore, setHasMore] = useState(true)
+ const pageRef = useRef(1)
const [messagesLoading, setMessagesLoading] = useState(false)
const [messages, setMessages] = useState([])
const [detailLoading, setDetailLoading] = useState(false)
@@ -80,17 +84,30 @@ const WorkingDetail: FC = () => {
setSelected(null)
setDetail(null)
setData([])
- getConversations(id).then((res) => {
- const response = res as Conversation[]
- setData(response)
- setSelected(response[0] || null)
+ setHasMore(true)
+ pageRef.current = 1
+ getConversations(id, 1).then((res) => {
+ const response = res as { items: Conversation[], page: { hasnext: boolean } }
+ setData(response.items)
+ setSelected(response.items[0] || null)
+ setHasMore(response.page.hasnext)
})
.finally(() => {
setLoading(false)
})
}
- /* Load messages and AI insight whenever the selected conversation changes. */
+ const loadMore = () => {
+ if (!id) return
+ const nextPage = pageRef.current + 1
+ getConversations(id, nextPage).then((res) => {
+ const response = res as {items: Conversation[], page: { hasnext: boolean }}
+ setData(prev => [...prev, ...response.items])
+ pageRef.current = nextPage
+ setHasMore(response.page.hasnext)
+ })
+ }
+
useEffect(() => {
if (!id || !selected || !selected.id) return
getDetail(selected.id)
@@ -138,16 +155,16 @@ const WorkingDetail: FC = () => {
: data.length === 0
?
:(
-
-
-
-
+
+
+
+
{data.map(item => (
{
))}
-
-
+
+
{selected && <>
diff --git a/web/src/views/Workflow/components/Chat/Chat.tsx b/web/src/views/Workflow/components/Chat/Chat.tsx
index 8832cb1d..830c277c 100644
--- a/web/src/views/Workflow/components/Chat/Chat.tsx
+++ b/web/src/views/Workflow/components/Chat/Chat.tsx
@@ -151,7 +151,7 @@ const Chat = forwardRef !['uploading', 'error'].includes(item.status))
setChatList(prev => [...prev, {
role: 'user',
content: message,
diff --git a/web/src/views/Workflow/components/Editor/plugin/InitialValuePlugin.tsx b/web/src/views/Workflow/components/Editor/plugin/InitialValuePlugin.tsx
index b263120a..8fe29d19 100644
--- a/web/src/views/Workflow/components/Editor/plugin/InitialValuePlugin.tsx
+++ b/web/src/views/Workflow/components/Editor/plugin/InitialValuePlugin.tsx
@@ -18,8 +18,8 @@ const InitialValuePlugin: React.FC = ({ value, options
const isUserInputRef = useRef(false);
useEffect(() => {
- // 监听编辑器变化,标记是否为用户输入
- const removeListener = editor.registerUpdateListener(({ editorState }) => {
+ const removeListener = editor.registerUpdateListener(({ editorState, tags }) => {
+ if (tags.has('programmatic')) return;
editorState.read(() => {
const root = $getRoot();
const textContent = root.getTextContent();
@@ -107,7 +107,7 @@ const InitialValuePlugin: React.FC = ({ value, options
});
root.append(paragraph);
}
- }, { discrete: true });
+ }, { discrete: true, tag: 'programmatic' });
});
}