Compare commits

..

2 Commits

Author SHA1 Message Date
accaa47a83 fix(components): 修复组件中定时器内存泄漏问题
- 在TopLoadingBar组件中添加timeoutRef并正确清理定时器
- 在Agent页面中添加timeoutRef管理AI响应模拟定时器
- 修复BasicInformation组件中useCallback依赖数组缺失问题
- 在CreateDataset页面中传递hidden属性控制数据源显示
- 在Orchestration页面中添加intervalRef管理工作流执行进度
- 在SynthesisTask中添加testTimeoutRef管理模板测试定时器
- 确保所有组件卸载时正确清除定时器避免内存泄漏
2026-01-30 14:35:45 +08:00
98d2ef1aa5 feat(KnowledgeBase): 优化知识库文件上传功能
- 添加提交状态控制,防止重复提交
- 将分块选项中的"按章节分块"改为"按句子分块"
- 更新固定长度分块的选项值从FIXED_LENGTH_CHUNK到LENGTH_CHUNK
- 简化文件计数逻辑,直接统计选中文件数量
- 添加上传进度提示消息
- 重构文件数据结构,确保ID为字符串类型
- 添加按钮禁用状态控制,提升用户体验
- 优化消息提示的显示方式,支持更新现有消息
2026-01-30 14:29:45 +08:00
7 changed files with 117 additions and 25 deletions

View File

@@ -4,6 +4,7 @@ const TopLoadingBar = () => {
const [isVisible, setIsVisible] = useState(false);
const [progress, setProgress] = useState(0);
const intervalRef = useRef(null);
const timeoutRef = useRef(null);
useEffect(() => {
// 监听全局事件
@@ -33,8 +34,13 @@ const TopLoadingBar = () => {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
// 清除旧的timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
setProgress(100);
setTimeout(() => {
timeoutRef.current = setTimeout(() => {
setIsVisible(false);
setProgress(0);
}, 300);
@@ -49,6 +55,9 @@ const TopLoadingBar = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
window.removeEventListener("loading:show", handleShow);
window.removeEventListener("loading:hide", handleHide);
};

View File

@@ -151,6 +151,15 @@ export default function AgentPage() {
const [isTyping, setIsTyping] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<any>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -174,8 +183,13 @@ export default function AgentPage() {
setInputValue("");
setIsTyping(true);
// 清理旧的 timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// 模拟AI响应
setTimeout(() => {
timeoutRef.current = setTimeout(() => {
const response = generateResponse(content);
const assistantMessage: Message = {
id: (Date.now() + 1).toString(),

View File

@@ -78,7 +78,11 @@ export default function DatasetCreate() {
onValuesChange={handleValuesChange}
layout="vertical"
>
<BasicInformation data={newDataset} setData={setNewDataset} />
<BasicInformation
data={newDataset}
setData={setNewDataset}
hidden={["dataSource"]}
/>
</Form>
</div>
<div className="flex gap-2 justify-end p-6 border-top">

View File

@@ -39,6 +39,7 @@ export default function BasicInformation({
// 获取归集任务
const fetchCollectionTasks = useCallback(async () => {
if (hidden.includes("dataSource")) return;
try {
const res = await queryTasksUsingGet({ page: 0, size: 100 });
const tasks = Array.isArray(res?.data?.content)
@@ -52,7 +53,7 @@ export default function BasicInformation({
} catch (error) {
console.error("Error fetching collection tasks:", error);
}
}, []);
}, [hidden]);
const fetchParentDatasets = useCallback(async () => {
if (hidden.includes("parentDatasetId")) return;

View File

@@ -21,15 +21,16 @@ export default function AddDataDialog({ knowledgeBase, onDataAdded }) {
const { message } = App.useApp();
const [form] = Form.useForm();
const [currentStep, setCurrentStep] = useState(0);
const [isSubmitting, setIsSubmitting] = useState(false);
const [selectedFilesMap, setSelectedFilesMap] = useState({});
// 定义分块选项
const sliceOptions = [
{ label: "默认分块", value: "DEFAULT_CHUNK" },
{ label: "按章节分块", value: "CHAPTER_CHUNK" },
{ label: "按句子分块", value: "SENTENCE_CHUNK" },
{ label: "按段落分块", value: "PARAGRAPH_CHUNK" },
{ label: "固定长度分块", value: "FIXED_LENGTH_CHUNK" },
{ label: "固定长度分块", value: "LENGTH_CHUNK" },
{ label: "自定义分隔符分块", value: "CUSTOM_SEPARATOR_CHUNK" },
];
@@ -57,12 +58,7 @@ export default function AddDataDialog({ knowledgeBase, onDataAdded }) {
];
// 获取已选择文件总数
const getSelectedFilesCount = () => {
return Object.values(selectedFilesMap).reduce(
(total, ids) => total + ids.length,
0
);
};
const getSelectedFilesCount = () => Object.keys(selectedFilesMap).length;
const handleNext = () => {
// 验证当前步骤
@@ -112,15 +108,29 @@ export default function AddDataDialog({ knowledgeBase, onDataAdded }) {
};
const handleAddData = async () => {
if (isSubmitting) {
return;
}
if (getSelectedFilesCount() === 0) {
message.warning("请至少选择一个文件");
return;
}
try {
setIsSubmitting(true);
const uploadMessageKey = "kb-add-files";
message.open({
type: "loading",
content: "正在上传,请稍候...",
key: uploadMessageKey,
duration: 0,
});
// 构造符合API要求的请求数据
const requestData = {
files: Object.values(selectedFilesMap),
files: Object.values(selectedFilesMap).map((file) => ({
id: String(file.id),
fileName: file.fileName,
})),
processType: newKB.processType,
chunkSize: Number(newKB.chunkSize), // 确保是数字类型
overlapSize: Number(newKB.overlapSize), // 确保是数字类型
@@ -132,12 +142,20 @@ export default function AddDataDialog({ knowledgeBase, onDataAdded }) {
// 先通知父组件刷新数据(确保刷新发生在重置前)
onDataAdded?.();
message.success("数据添加成功");
message.success({
content: "数据添加成功",
key: uploadMessageKey,
});
// 重置状态
setOpen(false);
} catch (error) {
message.error("数据添加失败,请重试");
message.error({
content: "数据添加失败,请重试",
key: "kb-add-files",
});
console.error("添加文件失败:", error);
} finally {
setIsSubmitting(false);
}
};
@@ -221,10 +239,12 @@ export default function AddDataDialog({ knowledgeBase, onDataAdded }) {
footer={
<div className="space-x-2">
{currentStep === 0 && (
<Button onClick={handleModalCancel}></Button>
<Button onClick={handleModalCancel} disabled={isSubmitting}>
</Button>
)}
{currentStep > 0 && (
<Button disabled={false} onClick={handlePrev}>
<Button disabled={isSubmitting} onClick={handlePrev}>
</Button>
)}
@@ -235,14 +255,20 @@ export default function AddDataDialog({ knowledgeBase, onDataAdded }) {
Object.keys(selectedFilesMap).length === 0 ||
!newKB.chunkSize ||
!newKB.overlapSize ||
!newKB.processType
!newKB.processType ||
isSubmitting
}
onClick={handleNext}
>
</Button>
) : (
<Button type="primary" onClick={handleAddData}>
<Button
type="primary"
onClick={handleAddData}
loading={isSubmitting}
disabled={isSubmitting}
>
</Button>
)}

View File

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useRef, useEffect } from "react";
import { Button, Card, Progress, Badge, Tabs } from "antd";
import {
GitBranch,
@@ -272,6 +272,19 @@ export default function OrchestrationPage() {
);
const [showWorkflowEditor, setShowWorkflowEditor] = useState(false);
// 使用 useRef 保存 interval 引用
const intervalRef = useRef<NodeJS.Timeout | null>(null);
// 组件卸载时清理 interval
useEffect(() => {
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, []);
const startNewFlow = () => {
setShowWorkflowEditor(true);
};
@@ -310,8 +323,14 @@ export default function OrchestrationPage() {
setExecutions([newExecution, ...executions]);
// 清理旧的 interval
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
// 模拟执行进度
const interval = setInterval(() => {
intervalRef.current = setInterval(() => {
setExecutions((prev) =>
prev.map((exec) => {
if (exec.id === newExecution.id) {
@@ -319,6 +338,11 @@ export default function OrchestrationPage() {
exec.progress + Math.random() * 10,
100
);
// 进度完成时清理 interval
if (newProgress >= 100 && intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return {
...exec,
progress: newProgress,
@@ -334,8 +358,6 @@ export default function OrchestrationPage() {
})
);
}, 500);
setTimeout(() => clearInterval(interval), 10000);
};
const getStatusIcon = (status: string) => {

View File

@@ -1,4 +1,4 @@
import { useState, useRef } from "react";
import { useState, useRef, useEffect } from "react";
import {
Card,
Select,
@@ -24,6 +24,7 @@ export default function InstructionTemplateCreate() {
const [templates, setTemplates] = useState<Template[]>(mockTemplates);
const [variables, setVariables] = useState<string[]>([]);
const variableInputRef = useRef<Input | null>(null);
const testTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const [form] = Form.useForm();
@@ -70,12 +71,27 @@ export default function InstructionTemplateCreate() {
setVariables(variables.filter((_, i) => i !== index));
};
// 组件卸载时清理 timeout
useEffect(() => {
return () => {
if (testTimeoutRef.current) {
clearTimeout(testTimeoutRef.current);
}
};
}, []);
// 测试模板
const handleTestTemplate = async () => {
const values = form.getFieldsValue();
if (!values.prompt || !values.testInput) return;
// 清理旧的 timeout
if (testTimeoutRef.current) {
clearTimeout(testTimeoutRef.current);
}
setIsTestingTemplate(true);
setTimeout(() => {
testTimeoutRef.current = setTimeout(() => {
form.setFieldValue(
"testOutput",
`基于输入"${values.testInput}"生成的测试结果: