knowledgebase_influencer/src/store/chat/chat.thunks.js
2025-04-15 21:51:27 -04:00

492 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, setCurrentChat } 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} chatData - 聊天数据
* @param {string} chatData.knowledge_base_id - 知识库ID
* @param {string} chatData.title - 聊天标题
*/
export const createChat = createAsyncThunk('chat/createChat', async (chatData, { rejectWithValue }) => {
try {
const response = await post('/chat-history/', chatData);
// 处理返回格式
if (response && response.code === 200) {
return response.data.chat;
}
return response.data?.chat || {};
} catch (error) {
return rejectWithValue(error.response?.data?.message || 'Failed to create chat');
}
});
/**
* 更新聊天
* @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 || '获取可用知识库列表失败');
}
}
);
/**
* 创建聊天记录
* @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();
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('解析或处理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 existingChats = state.chat.history.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, // 用户问题和助手回复
};
// 更新当前聊天
dispatch({
type: 'chat/fetchChats/fulfilled',
payload: {
results: [newChatEntry],
total: 1,
append: true, // 标记为追加,而不是替换
},
});
} else {
console.log('聊天sidebar记录已存在不再创建:', chatInfo.conversation_id);
}
// 设置为当前聊天
dispatch(
setCurrentChat({
conversation_id: chatInfo.conversation_id,
datasets: existingChat
? existingChat.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 || '新知识库对话',
};
}),
})
);
}
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 state = getState();
const createSession = state.chat.createSession || {};
const currentChat = state.chat.currentChat.data;
// 如果是刚创建成功的会话且会话ID匹配则直接返回现有会话数据
if (
createSession.status === 'succeeded' &&
createSession.sessionId === conversationId &&
currentChat?.conversation_id === conversationId
) {
console.log('使用新创建的会话数据,跳过详情请求:', conversationId);
return currentChat;
}
const response = await get('/chat-history/conversation_detail', {
params: { conversation_id: conversationId },
});
if (response && response.code === 200) {
// 如果存在消息更新Redux状态
if (response.data.messages) {
dispatch({
type: 'chat/fetchMessages/fulfilled',
payload: response.data.messages,
});
}
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,
};
// 更新聊天历史列表
dispatch({
type: 'chat/fetchChats/fulfilled',
payload: {
results: [newChatEntry],
total: 1,
append: true, // 标记为追加,而不是替换
},
});
// 设置为当前聊天
dispatch(
setCurrentChat({
conversation_id: conversationData.conversation_id,
datasets: newChatEntry.datasets,
})
);
return conversationData;
}
return rejectWithValue('创建会话失败');
} catch (error) {
console.error('创建会话失败:', error);
// 显示错误通知
dispatch(
showNotification({
message: `创建会话失败: ${error.message || '未知错误'}`,
type: 'danger',
})
);
return rejectWithValue(error.message || '创建会话失败');
}
}
);