mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-08 04:58:13 +08:00
[dev]edited chat
This commit is contained in:
parent
a4239cac87
commit
cdcd3374ad
@ -120,4 +120,6 @@ export const icons = {
|
||||
plus: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
|
||||
</svg>`,
|
||||
building: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="16" height="16" fill="currentColor"><path d="M48 0C21.5 0 0 21.5 0 48L0 464c0 26.5 21.5 48 48 48l96 0 0-80c0-26.5 21.5-48 48-48s48 21.5 48 48l0 80 96 0c26.5 0 48-21.5 48-48l0-416c0-26.5-21.5-48-48-48L48 0zM64 240c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zm112-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32c0-8.8 7.2-16 16-16zm80 16c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zM80 96l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32c0-8.8 7.2-16 16-16zm80 16c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zM272 96l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32c0-8.8 7.2-16 16-16z"/></svg>`,
|
||||
group:`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16" fill="currentColor"><path d="M96 128a128 128 0 1 1 256 0A128 128 0 1 1 96 128zM0 482.3C0 383.8 79.8 304 178.3 304l91.4 0C368.2 304 448 383.8 448 482.3c0 16.4-13.3 29.7-29.7 29.7L29.7 512C13.3 512 0 498.7 0 482.3zM609.3 512l-137.8 0c5.4-9.4 8.6-20.3 8.6-32l0-8c0-60.7-27.1-115.2-69.8-151.8c2.4-.1 4.7-.2 7.1-.2l61.4 0C567.8 320 640 392.2 640 481.3c0 17-13.8 30.7-30.7 30.7zM432 256c-31 0-59-12.6-79.3-32.9C372.4 196.5 384 163.6 384 128c0-26.8-6.6-52.1-18.3-74.3C384.3 40.1 407.2 32 432 32c61.9 0 112 50.1 112 112s-50.1 112-112 112z"/></svg>`
|
||||
};
|
||||
|
@ -254,24 +254,22 @@ export default function ChatWindow({ chatId, knowledgeBaseId }) {
|
||||
) : (
|
||||
<SafeMarkdown content={message.content} />
|
||||
)}
|
||||
{message.is_streaming && (
|
||||
<span className='streaming-indicator'>
|
||||
<span className='dot dot1'></span>
|
||||
<span className='dot dot2'></span>
|
||||
<span className='dot dot3'></span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='message-time small text-muted mt-1'>
|
||||
{message.created_at && new Date(message.created_at).toLocaleTimeString()}
|
||||
{message.is_streaming && ' · 正在生成...'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{sendStatus === 'loading' && (
|
||||
<div className='d-flex justify-content-start mb-3'>
|
||||
<div className='chat-message p-3 rounded-3 bg-light'>
|
||||
<div className='spinner-border spinner-border-sm text-secondary' role='status'>
|
||||
<span className='visually-hidden'>加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -74,6 +74,9 @@ export default function NewChat() {
|
||||
try {
|
||||
setIsNavigating(true);
|
||||
|
||||
// 打印调试信息
|
||||
console.log('选中的知识库ID:', selectedDatasetIds);
|
||||
|
||||
// 检查是否已存在包含所有选中知识库的聊天记录
|
||||
// 注意:这里的逻辑简化了,实际可能需要更复杂的匹配算法
|
||||
const existingChat = chatHistory.find((chat) => {
|
||||
@ -110,6 +113,10 @@ export default function NewChat() {
|
||||
|
||||
// 创建新的聊天记录
|
||||
const formattedIds = selectedDatasetIds.map((id) => id.replace(/-/g, ''));
|
||||
console.log('格式化后的知识库ID:', formattedIds);
|
||||
|
||||
try {
|
||||
// 尝试创建聊天记录
|
||||
const response = await dispatch(
|
||||
createChatRecord({
|
||||
dataset_id_list: formattedIds,
|
||||
@ -117,23 +124,38 @@ export default function NewChat() {
|
||||
})
|
||||
).unwrap();
|
||||
|
||||
console.log('创建聊天响应:', response);
|
||||
|
||||
if (response && response.conversation_id) {
|
||||
// 使用第一个知识库ID作为URL参数
|
||||
const primaryDatasetId = selectedDatasetIds[0];
|
||||
console.log(`创建成功,导航到 /chat/${primaryDatasetId}/${response.conversation_id}`);
|
||||
navigate(`/chat/${primaryDatasetId}/${response.conversation_id}`);
|
||||
} else {
|
||||
throw new Error('未能获取会话ID');
|
||||
throw new Error('未能获取会话ID:' + JSON.stringify(response));
|
||||
}
|
||||
} catch (apiError) {
|
||||
// 专门处理API调用错误
|
||||
console.error('API调用失败:', apiError);
|
||||
throw new Error(`API调用失败: ${apiError.message || '未知错误'}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导航或创建聊天失败:', error);
|
||||
|
||||
// 添加更详细的错误日志
|
||||
if (error.stack) {
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
// 显示更友好的错误消息
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: `创建聊天失败: ${error.message || '请重试'}`,
|
||||
type: 'danger',
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsNavigating(false);
|
||||
}
|
||||
};
|
||||
|
@ -58,6 +58,9 @@ export default function KnowledgeCard({
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<div className='text-muted d-flex align-items-center gap-1'>
|
||||
<SvgIcon className={'group'} />{department || ''} {group || 'N/A'}
|
||||
</div>
|
||||
<p className='card-text text-muted mb-3' style={descriptionStyle} title={description}>
|
||||
{description}
|
||||
</p>
|
||||
@ -95,6 +98,7 @@ export default function KnowledgeCard({
|
||||
)}
|
||||
{access === 'full' || access === 'read' ? (
|
||||
<></>
|
||||
) : (
|
||||
// <button
|
||||
// className='btn btn-outline-dark btn-sm d-flex align-items-center gap-1'
|
||||
// onClick={handleNewChat}
|
||||
@ -102,7 +106,6 @@ export default function KnowledgeCard({
|
||||
// <SvgIcon className={'chat-dot'} />
|
||||
// 新聊天
|
||||
// </button>
|
||||
) : (
|
||||
<button
|
||||
className='btn btn-outline-dark btn-sm d-flex align-items-center gap-1'
|
||||
onClick={handleRequestAccess}
|
||||
|
@ -214,6 +214,93 @@ export const switchToRealApi = async () => {
|
||||
return isServerUp;
|
||||
};
|
||||
|
||||
// Handle streaming requests
|
||||
const streamRequest = async (url, data, onChunk, onError) => {
|
||||
try {
|
||||
if (isServerDown) {
|
||||
console.log(`[MOCK MODE] STREAM ${url}`);
|
||||
// 模拟流式响应
|
||||
setTimeout(() => onChunk('{"code":200,"message":"partial","data":{"content":"这是模拟的","conversation_id":"mock-1234"}}'), 300);
|
||||
setTimeout(() => onChunk('{"code":200,"message":"partial","data":{"content":"流式","conversation_id":"mock-1234"}}'), 600);
|
||||
setTimeout(() => onChunk('{"code":200,"message":"partial","data":{"content":"响应","conversation_id":"mock-1234"}}'), 900);
|
||||
setTimeout(() => onChunk('{"code":200,"message":"partial","data":{"content":"数据","conversation_id":"mock-1234","is_end":true}}'), 1200);
|
||||
return { success: true, conversation_id: 'mock-1234' };
|
||||
}
|
||||
|
||||
// 获取认证Token
|
||||
const encryptedToken = sessionStorage.getItem('token') || '';
|
||||
let token = '';
|
||||
if (encryptedToken) {
|
||||
token = CryptoJS.AES.decrypt(encryptedToken, secretKey).toString(CryptoJS.enc.Utf8);
|
||||
}
|
||||
|
||||
// 使用fetch API进行流式请求
|
||||
const response = await fetch(`/api${url}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Token ${token}` : '',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
// 获取响应体的reader
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
let conversationId = null;
|
||||
|
||||
// 处理流式数据
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
// 解码并处理数据
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
buffer += chunk;
|
||||
|
||||
// 按行分割并处理JSON
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || ''; // 保留最后一行(可能不完整)
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
|
||||
try {
|
||||
// 检查是否为SSE格式(data: {...})
|
||||
let jsonStr = line;
|
||||
if (line.startsWith('data:')) {
|
||||
// 提取data:后面的JSON部分
|
||||
jsonStr = line.substring(5).trim();
|
||||
console.log('检测到SSE格式数据,提取JSON:', jsonStr);
|
||||
}
|
||||
|
||||
// 尝试解析JSON
|
||||
const data = JSON.parse(jsonStr);
|
||||
if (data.code === 200 && data.data && data.data.conversation_id) {
|
||||
conversationId = data.data.conversation_id;
|
||||
}
|
||||
onChunk(jsonStr);
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse JSON:', line, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, conversation_id: conversationId };
|
||||
} catch (error) {
|
||||
console.error('Streaming request failed:', error);
|
||||
if (onError) {
|
||||
onError(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 权限相关API
|
||||
export const applyPermission = (data) => {
|
||||
return post('/permissions/', data);
|
||||
@ -231,4 +318,4 @@ export const rejectPermission = (permissionId) => {
|
||||
return post(`/permissions/reject_permission/${permissionId}`);
|
||||
};
|
||||
|
||||
export { get, post, put, del, upload };
|
||||
export { get, post, put, del, upload, streamRequest };
|
||||
|
@ -104,6 +104,25 @@ const chatSlice = createSlice({
|
||||
addMessage: (state, action) => {
|
||||
state.messages.items.push(action.payload);
|
||||
},
|
||||
|
||||
// 更新消息(用于流式传输)
|
||||
updateMessage: (state, action) => {
|
||||
const { id, ...updates } = action.payload;
|
||||
const messageIndex = state.messages.items.findIndex((msg) => msg.id === id);
|
||||
|
||||
if (messageIndex !== -1) {
|
||||
// 更新现有消息
|
||||
state.messages.items[messageIndex] = {
|
||||
...state.messages.items[messageIndex],
|
||||
...updates,
|
||||
};
|
||||
|
||||
// 如果流式传输结束,更新发送消息状态
|
||||
if (updates.is_streaming === false) {
|
||||
state.sendMessage.status = 'succeeded';
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// 获取聊天列表
|
||||
@ -114,14 +133,24 @@ const chatSlice = createSlice({
|
||||
})
|
||||
.addCase(fetchChats.fulfilled, (state, action) => {
|
||||
state.list.status = 'succeeded';
|
||||
|
||||
// 检查是否是追加模式
|
||||
if (action.payload.append) {
|
||||
// 追加模式:将新结果添加到现有列表的前面
|
||||
state.list.items = [...action.payload.results, ...state.list.items];
|
||||
state.history.items = [...action.payload.results, ...state.history.items];
|
||||
} else {
|
||||
// 替换模式:使用新结果替换整个列表
|
||||
state.list.items = action.payload.results;
|
||||
state.list.total = action.payload.total;
|
||||
state.list.page = action.payload.page;
|
||||
state.list.page_size = action.payload.page_size;
|
||||
|
||||
// 同时更新新的状态结构
|
||||
state.history.status = 'succeeded';
|
||||
state.history.items = action.payload.results;
|
||||
}
|
||||
|
||||
state.history.status = 'succeeded';
|
||||
state.history.error = null;
|
||||
})
|
||||
.addCase(fetchChats.rejected, (state, action) => {
|
||||
@ -232,62 +261,19 @@ const chatSlice = createSlice({
|
||||
state.sendMessage.error = null;
|
||||
})
|
||||
.addCase(createChatRecord.fulfilled, (state, action) => {
|
||||
state.sendMessage.status = 'succeeded';
|
||||
// 添加新的消息
|
||||
state.messages.items.push({
|
||||
id: action.payload.id,
|
||||
role: 'user',
|
||||
content: action.meta.arg.question,
|
||||
created_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// 添加助手回复
|
||||
if (action.payload.role === 'assistant' && action.payload.content) {
|
||||
state.messages.items.push({
|
||||
id: action.payload.id,
|
||||
role: 'assistant',
|
||||
content: action.payload.content,
|
||||
created_at: action.payload.created_at,
|
||||
});
|
||||
}
|
||||
|
||||
// 更新聊天记录列表
|
||||
const chatExists = state.history.items.some(
|
||||
(chat) => chat.conversation_id === action.payload.conversation_id
|
||||
);
|
||||
|
||||
if (!chatExists) {
|
||||
const newChat = {
|
||||
// 更新状态以反映聊天已创建
|
||||
if (action.payload.conversation_id && !state.currentChat.data) {
|
||||
// 设置当前聊天的会话ID
|
||||
state.currentChat.data = {
|
||||
conversation_id: action.payload.conversation_id,
|
||||
last_message: action.payload.content,
|
||||
last_time: action.payload.created_at,
|
||||
datasets: [
|
||||
{
|
||||
id: action.payload.dataset_id,
|
||||
name: action.payload.dataset_name,
|
||||
},
|
||||
],
|
||||
dataset_id_list: action.payload.dataset_id_list,
|
||||
message_count: 2, // 用户问题和助手回复
|
||||
// 其他信息将由流式更新填充
|
||||
};
|
||||
|
||||
state.history.items.unshift(newChat);
|
||||
} else {
|
||||
// 更新已存在聊天的最后消息和时间
|
||||
const chatIndex = state.history.items.findIndex(
|
||||
(chat) => chat.conversation_id === action.payload.conversation_id
|
||||
);
|
||||
|
||||
if (chatIndex !== -1) {
|
||||
state.history.items[chatIndex].last_message = action.payload.content;
|
||||
state.history.items[chatIndex].last_time = action.payload.created_at;
|
||||
state.history.items[chatIndex].message_count += 2; // 新增用户问题和助手回复
|
||||
}
|
||||
}
|
||||
// 不再在这里添加消息,因为消息已经在thunk函数中添加
|
||||
})
|
||||
.addCase(createChatRecord.rejected, (state, action) => {
|
||||
state.sendMessage.status = 'failed';
|
||||
state.sendMessage.error = action.payload || '创建聊天记录失败';
|
||||
state.sendMessage.error = action.error.message;
|
||||
})
|
||||
|
||||
// 处理获取可用知识库
|
||||
@ -334,6 +320,7 @@ export const {
|
||||
resetMessages,
|
||||
resetSendMessageStatus,
|
||||
addMessage,
|
||||
updateMessage,
|
||||
} = chatSlice.actions;
|
||||
|
||||
// 导出 reducer
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { get, post, put, del } from '../../services/api';
|
||||
import { get, post, put, del, streamRequest } from '../../services/api';
|
||||
import { showNotification } from '../notification.slice';
|
||||
import { addMessage, updateMessage, setCurrentChat } from './chat.slice';
|
||||
|
||||
/**
|
||||
* 获取聊天列表
|
||||
@ -119,26 +120,224 @@ export const fetchAvailableDatasets = createAsyncThunk(
|
||||
* @param {string} params.question - 用户问题
|
||||
* @param {string} params.conversation_id - 会话ID,可选
|
||||
*/
|
||||
export const createChatRecord = createAsyncThunk('chat/createChatRecord', async (params, { rejectWithValue }) => {
|
||||
export const createChatRecord = createAsyncThunk(
|
||||
'chat/createChatRecord',
|
||||
async ({ question, conversation_id, dataset_id_list }, { dispatch, getState, rejectWithValue }) => {
|
||||
try {
|
||||
const response = await post('/chat-history/', {
|
||||
dataset_id_list: params.dataset_id_list,
|
||||
question: params.question,
|
||||
conversation_id: params.conversation_id,
|
||||
});
|
||||
// 构建请求数据
|
||||
const requestBody = {
|
||||
question,
|
||||
dataset_id_list,
|
||||
};
|
||||
|
||||
// 处理返回格式
|
||||
if (response && response.code === 200) {
|
||||
return response.data;
|
||||
// 如果存在对话 ID,添加到请求中
|
||||
if (conversation_id) {
|
||||
requestBody.conversation_id = conversation_id;
|
||||
}
|
||||
|
||||
return rejectWithValue(response.message || '创建聊天记录失败');
|
||||
console.log('准备发送聊天请求:', requestBody);
|
||||
|
||||
// 先添加用户消息到聊天窗口
|
||||
const userMessageId = Date.now().toString();
|
||||
dispatch(
|
||||
addMessage({
|
||||
id: userMessageId,
|
||||
role: 'user',
|
||||
content: question,
|
||||
created_at: new Date().toISOString(),
|
||||
})
|
||||
);
|
||||
|
||||
// 添加临时的助手消息(流式传输期间显示)
|
||||
const assistantMessageId = (Date.now() + 1).toString();
|
||||
dispatch(
|
||||
addMessage({
|
||||
id: assistantMessageId,
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
created_at: new Date().toISOString(),
|
||||
is_streaming: true,
|
||||
})
|
||||
);
|
||||
|
||||
let finalMessage = '';
|
||||
let conversationId = conversation_id;
|
||||
|
||||
// 使用流式请求函数处理
|
||||
const result = await streamRequest(
|
||||
'/chat-history/',
|
||||
requestBody,
|
||||
// 处理每个数据块
|
||||
(chunkText) => {
|
||||
try {
|
||||
const data = JSON.parse(chunkText);
|
||||
console.log('收到聊天数据块:', data);
|
||||
|
||||
if (data.code === 200) {
|
||||
// 保存会话ID (无论消息类型,只要找到会话ID就保存)
|
||||
if (data.data && data.data.conversation_id && !conversationId) {
|
||||
conversationId = data.data.conversation_id;
|
||||
console.log('获取到会话ID:', conversationId);
|
||||
}
|
||||
|
||||
// 处理各种可能的消息类型
|
||||
const messageType = data.message;
|
||||
|
||||
// 处理部分内容更新
|
||||
if ((messageType === 'partial' || messageType === '部分') && data.data) {
|
||||
// 累加内容
|
||||
if (data.data.content !== undefined) {
|
||||
finalMessage += data.data.content;
|
||||
console.log('累加内容:', finalMessage);
|
||||
|
||||
// 更新消息内容
|
||||
dispatch(
|
||||
updateMessage({
|
||||
id: assistantMessageId,
|
||||
content: finalMessage,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// 处理结束标志
|
||||
if (data.data.is_end) {
|
||||
console.log('检测到消息结束标志');
|
||||
dispatch(
|
||||
updateMessage({
|
||||
id: assistantMessageId,
|
||||
is_streaming: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
// 处理开始流式传输的消息
|
||||
else if (messageType === '开始流式传输' || messageType === 'start_streaming') {
|
||||
console.log('开始流式传输,会话ID:', data.data?.conversation_id);
|
||||
}
|
||||
// 处理完成消息
|
||||
else if (
|
||||
messageType === 'completed' ||
|
||||
messageType === '完成' ||
|
||||
messageType === 'end_streaming' ||
|
||||
messageType === '结束流式传输'
|
||||
) {
|
||||
console.log('收到完成消息');
|
||||
dispatch(
|
||||
updateMessage({
|
||||
id: assistantMessageId,
|
||||
is_streaming: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
// 其他类型的消息
|
||||
else {
|
||||
console.log('收到其他类型消息:', messageType);
|
||||
// 如果有content字段,也尝试更新
|
||||
if (data.data && data.data.content !== undefined) {
|
||||
finalMessage += data.data.content;
|
||||
dispatch(
|
||||
updateMessage({
|
||||
id: assistantMessageId,
|
||||
content: finalMessage,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('收到非成功状态码:', data.code, data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating chat record:', error);
|
||||
return rejectWithValue(error.response?.data?.message || '创建聊天记录失败');
|
||||
console.error('解析或处理JSON失败:', error, '原始数据:', chunkText);
|
||||
}
|
||||
},
|
||||
// 处理错误
|
||||
(error) => {
|
||||
console.error('流式请求错误:', error);
|
||||
dispatch(
|
||||
updateMessage({
|
||||
id: assistantMessageId,
|
||||
content: `错误: ${error.message || '请求失败'}`,
|
||||
is_streaming: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// 确保流式传输结束后标记消息已完成
|
||||
dispatch(
|
||||
updateMessage({
|
||||
id: assistantMessageId,
|
||||
is_streaming: false,
|
||||
})
|
||||
);
|
||||
|
||||
// 返回会话信息
|
||||
const chatInfo = {
|
||||
conversation_id: conversationId || result.conversation_id,
|
||||
success: true,
|
||||
};
|
||||
|
||||
// 如果聊天创建成功,添加到历史列表,并设置为当前激活聊天
|
||||
if (chatInfo.conversation_id) {
|
||||
// 获取知识库信息
|
||||
const state = getState();
|
||||
const availableDatasets = state.chat.availableDatasets.items || [];
|
||||
|
||||
// 创建一个新的聊天记录对象添加到历史列表
|
||||
const newChatEntry = {
|
||||
conversation_id: chatInfo.conversation_id,
|
||||
datasets: dataset_id_list.map((id) => {
|
||||
// 尝试查找知识库名称
|
||||
const formattedId = id.includes('-')
|
||||
? id
|
||||
: id.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5');
|
||||
const dataset = availableDatasets.find((ds) => ds.id === formattedId);
|
||||
return {
|
||||
id: formattedId,
|
||||
name: dataset?.name || '新知识库对话',
|
||||
};
|
||||
}),
|
||||
create_time: new Date().toISOString(),
|
||||
last_message: question,
|
||||
message_count: 2, // 用户问题和助手回复
|
||||
};
|
||||
|
||||
// 更新当前聊天
|
||||
dispatch({
|
||||
type: 'chat/fetchChats/fulfilled',
|
||||
payload: {
|
||||
results: [newChatEntry],
|
||||
total: 1,
|
||||
append: true, // 标记为追加,而不是替换
|
||||
},
|
||||
});
|
||||
|
||||
// 设置为当前聊天
|
||||
dispatch(
|
||||
setCurrentChat({
|
||||
conversation_id: chatInfo.conversation_id,
|
||||
datasets: newChatEntry.datasets,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return chatInfo;
|
||||
} catch (error) {
|
||||
console.error('创建聊天记录失败:', error);
|
||||
|
||||
// 显示错误通知
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: `发送失败: ${error.message || '未知错误'}`,
|
||||
type: 'danger',
|
||||
})
|
||||
);
|
||||
|
||||
return rejectWithValue(error.message || '创建聊天记录失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取会话详情
|
||||
* @param {string} conversationId - 会话ID
|
||||
|
@ -347,3 +347,42 @@
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Streaming message indicator */
|
||||
.streaming-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
|
||||
.dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #6c757d;
|
||||
border-radius: 50%;
|
||||
margin: 0 2px;
|
||||
animation: pulse 1.5s infinite ease-in-out;
|
||||
|
||||
&.dot1 {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
&.dot2 {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
&.dot3 {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user