更新README.md,临时解决文档模式渲染超时问题
This commit is contained in:
parent
7086f2d11d
commit
e9ea71697f
11
README.md
11
README.md
|
@ -41,6 +41,17 @@ rm -rf /ragflow/web/dist
|
||||||
docker cp dist ragflow-server:/ragflow/web/
|
docker cp dist ragflow-server:/ragflow/web/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Agent功能恢复
|
||||||
|
|
||||||
|
由于在我的应用场景中,不需要Agent功能,故隐藏了Agent按钮的入口,如需恢复Agent功能,可修改`web\src\layouts\components\header\index.tsx`,对以下内容取消注释:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
{ path: '/flow', name: t('flow'), icon: GraphIcon },
|
||||||
|
```
|
||||||
|
|
||||||
|
同时可将排列样式进行重置,以还原原本的样式布局,修改`web\src\layouts\components\header\index.less`文件,替换为ragflow原始样式:`https://github.com/infiniflow/ragflow/blob/main/web/src/layouts/components/header/index.less`
|
||||||
|
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] 用户批量注册可视化后台管理
|
- [ ] 用户批量注册可视化后台管理
|
||||||
|
|
|
@ -34,4 +34,4 @@ const App: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
|
@ -12,7 +12,7 @@ export const aiAssistantConfig = {
|
||||||
// 修改为正确的 API 端点格式
|
// 修改为正确的 API 端点格式
|
||||||
chatEndpoint: "/api/v1/chats", // 创建聊天会话的端点
|
chatEndpoint: "/api/v1/chats", // 创建聊天会话的端点
|
||||||
completionEndpoint: "/api/v1/chats/{chat_id}/completions", // 聊天完成的端点
|
completionEndpoint: "/api/v1/chats/{chat_id}/completions", // 聊天完成的端点
|
||||||
timeout: 30000, // 请求超时时间(ms)
|
timeout: 300000, // 请求超时时间(ms)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 默认系统提示词
|
// 默认系统提示词
|
||||||
|
|
|
@ -314,6 +314,25 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
|
|
||||||
setIsAiLoading(true);
|
setIsAiLoading(true);
|
||||||
|
|
||||||
|
// 保存初始光标位置和内容,用于动态更新
|
||||||
|
const initialCursorPos = cursorPosition;
|
||||||
|
const initialContent = content;
|
||||||
|
let beforeCursor = '';
|
||||||
|
let afterCursor = '';
|
||||||
|
|
||||||
|
// 确定插入位置
|
||||||
|
if (initialCursorPos !== null && showCursorIndicator) {
|
||||||
|
beforeCursor = initialContent.substring(0, initialCursorPos);
|
||||||
|
afterCursor = initialContent.substring(initialCursorPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个可以取消的请求控制器
|
||||||
|
const controller = new AbortController();
|
||||||
|
// 设置超时定时器
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
controller.abort();
|
||||||
|
}, aiAssistantConfig.api.timeout || 30000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authorization = localStorage.getItem('Authorization');
|
const authorization = localStorage.getItem('Authorization');
|
||||||
if (!authorization) {
|
if (!authorization) {
|
||||||
|
@ -321,33 +340,42 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
setIsAiLoading(false);
|
setIsAiLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成一个随机的会话ID
|
// 生成一个随机的会话ID
|
||||||
const conversationId = Math.random().toString(36).substring(2) + Date.now().toString(36);
|
const conversationId = Math.random().toString(36).substring(2) + Date.now().toString(36);
|
||||||
|
|
||||||
// 创建新会话
|
// 创建新会话
|
||||||
const createSessionResponse = await axios.post(
|
try {
|
||||||
'v1/conversation/set',
|
const createSessionResponse = await axios.post(
|
||||||
{
|
'v1/conversation/set',
|
||||||
dialog_id: dialogId,
|
{
|
||||||
name: "文档撰写对话",
|
dialog_id: dialogId,
|
||||||
is_new: true,
|
name: "文档撰写对话",
|
||||||
conversation_id: conversationId,
|
is_new: true,
|
||||||
message: [
|
conversation_id: conversationId,
|
||||||
{
|
message: [
|
||||||
role: "assistant",
|
{
|
||||||
content: "新对话"
|
role: "assistant",
|
||||||
}
|
content: "新对话"
|
||||||
]
|
}
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
headers: {
|
{
|
||||||
'authorization': authorization
|
headers: {
|
||||||
|
'authorization': authorization
|
||||||
|
},
|
||||||
|
signal: controller.signal
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
);
|
|
||||||
|
|
||||||
if (!createSessionResponse.data?.data?.id) {
|
if (!createSessionResponse.data?.data?.id) {
|
||||||
message.error('创建会话失败');
|
message.error('创建会话失败');
|
||||||
|
setIsAiLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建会话失败:', error);
|
||||||
|
message.error('创建会话失败,请重试');
|
||||||
setIsAiLoading(false);
|
setIsAiLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -357,80 +385,99 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
|
|
||||||
console.log('发送问题到 AI 助手:', aiQuestion);
|
console.log('发送问题到 AI 助手:', aiQuestion);
|
||||||
|
|
||||||
const response = await axios.post(
|
let lastContent = ''; // 上一次的累积内容
|
||||||
'/v1/conversation/completion',
|
|
||||||
{
|
|
||||||
conversation_id: conversationId,
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
content: combinedQuestion
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timeout: aiAssistantConfig.api.timeout,
|
|
||||||
headers: {
|
|
||||||
'authorization': authorization
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// 修改响应处理逻辑,实现在光标位置插入内容
|
|
||||||
if (response.data) {
|
|
||||||
try {
|
try {
|
||||||
const lines = response.data.split('\n').filter((line: string) => line.trim());
|
const response = await axios.post(
|
||||||
let accumulatedContent = ''; // 累积的内容
|
'/v1/conversation/completion',
|
||||||
|
{
|
||||||
|
conversation_id: conversationId,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: combinedQuestion
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: aiAssistantConfig.api.timeout,
|
||||||
|
headers: {
|
||||||
|
'authorization': authorization
|
||||||
|
},
|
||||||
|
signal: controller.signal
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 处理每一行数据
|
// 修改响应处理逻辑,实现在光标位置动态插入内容
|
||||||
lines.forEach((line: string, index: number) => {
|
if (response.data) {
|
||||||
setTimeout(() => {
|
const lines = response.data.split('\n').filter((line: string) => line.trim());
|
||||||
|
|
||||||
|
// 直接处理每一行数据,不使用嵌套的 Promise
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
try {
|
try {
|
||||||
const jsonStr = line.replace('data:', '').trim();
|
const jsonStr = lines[i].replace('data:', '').trim();
|
||||||
const jsonData = JSON.parse(jsonStr);
|
const jsonData = JSON.parse(jsonStr);
|
||||||
|
|
||||||
if (jsonData.code === 0 && jsonData.data?.answer) {
|
if (jsonData.code === 0 && jsonData.data?.answer) {
|
||||||
const answer = jsonData.data.answer;
|
const answer = jsonData.data.answer;
|
||||||
|
|
||||||
// 过滤掉 think 标签内容
|
// 过滤掉 think 标签内容
|
||||||
const cleanedAnswer = answer.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
const cleanedAnswer = answer.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
||||||
// 检查是否还有未闭合的 think 标签
|
// 检查是否还有未闭合的 think 标签
|
||||||
const hasUnclosedThink = cleanedAnswer.includes('<think>') &&
|
const hasUnclosedThink = cleanedAnswer.includes('<think>') &&
|
||||||
(!cleanedAnswer.includes('</think>') ||
|
(!cleanedAnswer.includes('</think>') ||
|
||||||
cleanedAnswer.indexOf('<think>') > cleanedAnswer.lastIndexOf('</think>'));
|
cleanedAnswer.indexOf('<think>') > cleanedAnswer.lastIndexOf('</think>'));
|
||||||
|
|
||||||
if (cleanedAnswer && !hasUnclosedThink) {
|
if (cleanedAnswer && !hasUnclosedThink) {
|
||||||
// 更新累积内容
|
// 计算新增的内容部分
|
||||||
accumulatedContent = cleanedAnswer;
|
const newContent = cleanedAnswer;
|
||||||
// 在光标位置插入内容
|
const incrementalContent = newContent.substring(lastContent.length);
|
||||||
if (cursorPosition !== null && showCursorIndicator) {
|
|
||||||
const beforeCursor = content.substring(0, cursorPosition);
|
// 只有当有新增内容时才更新编辑器
|
||||||
const afterCursor = content.substring(cursorPosition);
|
if (incrementalContent) {
|
||||||
|
// 更新上一次的内容记录
|
||||||
|
lastContent = newContent;
|
||||||
|
|
||||||
// 更新编辑器内容,保持光标位置
|
// 动态更新编辑器内容
|
||||||
setContent(beforeCursor + accumulatedContent + afterCursor);
|
if (initialCursorPos !== null && showCursorIndicator) {
|
||||||
|
// 在光标位置动态插入内容
|
||||||
// 更新光标位置到插入内容之后
|
setContent(beforeCursor + newContent + afterCursor);
|
||||||
const newPosition = cursorPosition + accumulatedContent.length;
|
|
||||||
setCursorPosition(newPosition);
|
// 更新光标位置到插入内容之后
|
||||||
|
const newPosition = initialCursorPos + newContent.length;
|
||||||
// 尝试重新设置光标位置
|
setCursorPosition(newPosition);
|
||||||
setTimeout(() => {
|
} else {
|
||||||
if (textAreaRef.current) {
|
// 如果没有光标位置,则追加到末尾
|
||||||
textAreaRef.current.focus();
|
setContent(initialContent + newContent);
|
||||||
}
|
}
|
||||||
}, 50);
|
|
||||||
} else {
|
|
||||||
// 如果没有光标位置,则追加到末尾
|
|
||||||
setContent(prev => prev + accumulatedContent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (parseErr) {
|
} catch (parseErr) {
|
||||||
console.error('解析单行数据失败:', parseErr);
|
console.error('解析单行数据失败:', parseErr);
|
||||||
|
// 继续处理下一行,不中断整个流程
|
||||||
}
|
}
|
||||||
}, index * 100); // 每100毫秒处理一行
|
|
||||||
});
|
// 添加一个小延迟,让UI有时间更新
|
||||||
// 在处理完所有响应后,删除临时会话
|
if (i < lines.length - 1) {
|
||||||
setTimeout(async () => {
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('获取 AI 回答失败:', error);
|
||||||
|
// 检查是否是超时错误
|
||||||
|
if (error.code === 'ECONNABORTED' || error.name === 'AbortError') {
|
||||||
|
message.error('AI 助手响应超时,请稍后重试');
|
||||||
|
} else {
|
||||||
|
message.error('获取 AI 回答失败,请重试');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// 清除超时定时器
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在处理完所有响应后,删除临时会话
|
||||||
try {
|
try {
|
||||||
await axios.post('/v1/conversation/rm', {
|
await axios.post('/v1/conversation/rm', {
|
||||||
conversation_ids: [conversationId],
|
conversation_ids: [conversationId],
|
||||||
|
@ -441,21 +488,29 @@ const handleAiQuestionSubmit = async (e: React.KeyboardEvent<HTMLTextAreaElement
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('临时会话已删除:', conversationId);
|
console.log('临时会话已删除:', conversationId);
|
||||||
|
|
||||||
|
// 处理完成后,重新设置光标焦点
|
||||||
|
if (textAreaRef.current) {
|
||||||
|
textAreaRef.current.focus();
|
||||||
|
}
|
||||||
} catch (rmErr) {
|
} catch (rmErr) {
|
||||||
console.error('删除临时会话失败:', rmErr);
|
console.error('删除临时会话失败:', rmErr);
|
||||||
|
// 删除会话失败不影响主流程
|
||||||
}
|
}
|
||||||
}, lines.length * 100 + 500);
|
} catch (err) {
|
||||||
|
console.error('AI 助手处理失败:', err);
|
||||||
} catch (err) {
|
message.error('AI 助手处理失败,请重试');
|
||||||
console.error('解析响应数据失败:', err);
|
} finally {
|
||||||
message.error('获取 AI 回答失败');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
finally {
|
|
||||||
setIsAiLoading(false);
|
setIsAiLoading(false);
|
||||||
|
// 清空问题输入框
|
||||||
|
setAiQuestion('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ... existing code ...
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ height: 'auto', padding: 24, overflow: 'hidden'}}>
|
<Layout style={{ height: 'auto', padding: 24, overflow: 'hidden'}}>
|
||||||
<Sider
|
<Sider
|
||||||
|
|
Loading…
Reference in New Issue