knowledgebase_influencer/src/store/chat/chat.thunks.js

468 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createAsyncThunk } from '@reduxjs/toolkit';
import { get, post, put, del, streamRequest } from '../../services/api';
import { showNotification } from '../notification.slice';
import { addMessage, updateMessage, setActiveChat } from './chat.slice';
/**
* 获取聊天列表
* @param {Object} params - 查询参数
* @param {number} params.page - 页码
* @param {number} params.page_size - 每页数量
*/
export const fetchChats = createAsyncThunk('chat/fetchChats', async (params = {}, { rejectWithValue }) => {
try {
const response = await get('/chat-history/', { params });
// 处理返回格式
if (response && response.code === 200) {
return {
results: response.data.results,
total: response.data.total,
page: response.data.page || 1,
page_size: response.data.page_size || 10,
};
}
return { results: [], total: 0, page: 1, page_size: 10 };
} catch (error) {
console.error('Error fetching chats:', error);
return rejectWithValue(error.response?.data?.message || 'Failed to fetch chats');
}
});
/**
* 更新聊天
* 更新已经发送出去的聊天,获得新的回复,相当于编辑以往的聊天记录。暂时未使用
* @param {Object} params - 更新参数
* @param {string} params.id - 聊天ID
* @param {Object} params.data - 更新数据
*/
export const updateChat = createAsyncThunk('chat/updateChat', async ({ id, data }, { rejectWithValue }) => {
try {
const response = await put(`/chat-history/${id}/`, data);
// 处理返回格式
if (response && response.code === 200) {
return response.data.chat;
}
return response.data?.chat || {};
} catch (error) {
return rejectWithValue(error.response?.data?.message || 'Failed to update chat');
}
});
/**
* 删除聊天
* @param {string} conversationId - 聊天ID
*/
export const deleteChat = createAsyncThunk('chat/deleteChat', async (conversationId, { rejectWithValue }) => {
try {
const response = await del(`/chat-history/delete_conversation?conversation_id=${conversationId}`);
// 处理返回格式
if (response && response.code === 200) {
return conversationId;
}
return conversationId;
} catch (error) {
console.error('Error deleting chat:', error);
return rejectWithValue(error.response?.data?.message || '删除聊天失败');
}
});
/**
* 获取可用于聊天的知识库列表
*/
export const fetchAvailableDatasets = createAsyncThunk(
'chat/fetchAvailableDatasets',
async (_, { rejectWithValue }) => {
try {
const response = await get('/chat-history/available_datasets/');
if (response && response.code === 200) {
return response.data;
}
return rejectWithValue('获取可用知识库列表失败');
} catch (error) {
console.error('Error fetching available datasets:', error);
return rejectWithValue(error.response?.data?.message || '获取可用知识库列表失败');
}
}
);
/**
* 创建/继续聊天
* 创建新会话或者继续一个已有的会话根据conversation_id来更新
* @param {Object} params - 聊天参数
* @param {string[]} params.dataset_id_list - 知识库ID列表
* @param {string} params.question - 用户问题
* @param {string} params.conversation_id - 会话ID可选
*/
export const createChatRecord = createAsyncThunk(
'chat/createChatRecord',
async ({ question, conversation_id, dataset_id_list }, { dispatch, getState, rejectWithValue }) => {
try {
// 构建请求数据
const requestBody = {
question,
dataset_id_list,
conversation_id,
};
console.log('准备发送聊天请求:', requestBody);
// 先添加用户消息到聊天窗口
const userMessageId = Date.now().toString();
const userMessage = {
id: userMessageId,
role: 'user',
content: question,
created_at: new Date().toISOString(),
};
// 添加用户消息
dispatch(
addMessage({
conversationId: conversation_id,
message: userMessage,
})
);
// 添加临时的助手消息(流式传输期间显示)
const assistantMessageId = (Date.now() + 1).toString();
const assistantMessage = {
id: assistantMessageId,
role: 'assistant',
content: '',
created_at: new Date().toISOString(),
is_streaming: true,
};
// 添加助手消息
dispatch(
addMessage({
conversationId: conversation_id,
message: assistantMessage,
})
);
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);
// 获取服务器消息ID (如果存在)
const serverMessageId = data.data.id;
// 更新消息内容
dispatch(
updateMessage({
conversationId: conversationId,
messageId: assistantMessageId,
serverMessageId: serverMessageId,
updates: { content: finalMessage },
})
);
}
// 处理结束标志
if (data.data.is_end) {
console.log('检测到消息结束标志');
// 获取服务器消息ID (如果存在)
const serverMessageId = data.data.id;
dispatch(
updateMessage({
conversationId: conversationId,
messageId: assistantMessageId,
serverMessageId: serverMessageId,
updates: { 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('收到完成消息');
// 获取服务器消息ID (如果存在)
const serverMessageId = data.data?.id;
dispatch(
updateMessage({
conversationId: conversationId,
messageId: assistantMessageId,
serverMessageId: serverMessageId,
updates: { is_streaming: false },
})
);
}
// 其他类型的消息
else {
console.log('收到其他类型消息:', messageType);
// 如果有content字段也尝试更新
if (data.data && data.data.content !== undefined) {
finalMessage += data.data.content;
// 获取服务器消息ID (如果存在)
const serverMessageId = data.data.id;
dispatch(
updateMessage({
conversationId: conversationId,
messageId: assistantMessageId,
serverMessageId: serverMessageId,
updates: { content: finalMessage },
})
);
}
}
} else {
console.warn('收到非成功状态码:', data.code, data.message);
}
} catch (error) {
console.error('解析或处理JSON失败:', error, '原始数据:', chunkText);
}
},
// 处理错误
(error) => {
console.error('流式请求错误:', error);
dispatch(
updateMessage({
conversationId: conversationId,
messageId: assistantMessageId,
updates: {
content: `错误: ${error.message || '请求失败'}`,
is_streaming: false,
},
})
);
}
);
// 确保流式传输结束后标记消息已完成
dispatch(
updateMessage({
conversationId: conversationId,
messageId: assistantMessageId,
updates: { 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 existingChats = state.chat.chats.items || [];
// 检查是否已存在此会话ID的记录
const existingChat = existingChats.find((chat) => chat.conversation_id === chatInfo.conversation_id);
// 只有在不存在相同会话ID的记录时才创建新的记录
if (!existingChat) {
console.log('创建新的聊天sidebar记录:', chatInfo.conversation_id);
// 创建一个新的聊天记录对象添加到历史列表
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, // 用户问题和助手回复
messages: [userMessage, assistantMessage], // 添加消息到聊天记录
};
// 更新当前聊天
dispatch({
type: 'chat/fetchChats/fulfilled',
payload: {
results: [newChatEntry],
total: 1,
append: true, // 标记为追加,而不是替换
},
});
} else {
console.log('聊天sidebar记录已存在不再创建:', chatInfo.conversation_id);
}
// 设置为当前聊天
dispatch(setActiveChat(chatInfo.conversation_id));
}
return chatInfo;
} catch (error) {
console.error('创建聊天记录失败:', error);
// 显示错误通知
dispatch(
showNotification({
message: `发送失败: ${error.message || '未知错误'}`,
type: 'danger',
})
);
return rejectWithValue(error.message || '创建聊天记录失败');
}
}
);
/**
* 获取会话详情
* @param {string} conversationId - 会话ID
*/
export const fetchConversationDetail = createAsyncThunk(
'chat/fetchConversationDetail',
async (conversationId, { rejectWithValue, dispatch, getState }) => {
try {
const response = await get('/chat-history/conversation_detail', {
params: { conversation_id: conversationId },
});
if (response && response.code === 200) {
return response.data;
}
return rejectWithValue('获取会话详情失败');
} catch (error) {
// 明确检查是否是404错误
const is404Error = error.response && error.response.status === 404;
if (is404Error) {
console.log('会话未找到,可能是新创建的会话:', conversationId);
return null;
}
console.error('Error fetching conversation detail:', error);
return rejectWithValue(error.response?.data?.message || '获取会话详情失败');
}
}
);
/**
* 创建新会话仅获取会话ID相当于一个会话凭证不发送消息
* @param {Object} params - 参数
* @param {string[]} params.dataset_id_list - 知识库ID列表
*/
export const createConversation = createAsyncThunk(
'chat/createConversation',
async ({ dataset_id_list }, { dispatch, getState, rejectWithValue }) => {
try {
console.log('创建新会话知识库ID列表:', dataset_id_list);
const params = {
dataset_id_list: dataset_id_list,
};
const response = await post('/chat-history/create_conversation/', params);
if (response && response.code === 200) {
const conversationData = response.data;
console.log('会话创建成功:', conversationData);
// 获取知识库信息
const state = getState();
const availableDatasets = state.chat.availableDatasets.items || [];
// 创建一个新的聊天记录对象添加到历史列表
const newChatEntry = {
conversation_id: conversationData.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: '',
message_count: 0,
messages: [], // 初始化空消息数组
};
// 更新聊天历史列表
dispatch({
type: 'chat/fetchChats/fulfilled',
payload: {
results: [newChatEntry],
total: 1,
append: true, // 标记为追加,而不是替换
},
});
// 设置为当前聊天
dispatch(setActiveChat(conversationData.conversation_id));
return conversationData;
}
return rejectWithValue('创建会话失败');
} catch (error) {
console.error('创建会话失败:', error);
// 显示错误通知
dispatch(
showNotification({
message: `创建会话失败: ${error.message || '未知错误'}`,
type: 'danger',
})
);
return rejectWithValue(error.message || '创建会话失败');
}
}
);