@tencentcloud/chat-uikit-uniapp
Version:
TUIKit 是基于 IM SDK 实现的一套 UI 组件,其包含会话、聊天、群组、个人资料等功能,基于这些精心设计的 UI 组件,您可以快速构建优雅的、可靠的、可扩展的 Chat 应用。
263 lines (237 loc) • 8.7 kB
text/typescript
import TUIChatEngine, { TUIChatService, IMessageModel, TUIUserService } from '@tencentcloud/chat-uikit-engine-lite';
import { sendMessageOptions } from './info';
import { breakAiRobotPayload, ChatbotBreakMsgType, ChatbotErrorMsgType, ChatbotPlugin } from './const';
import TUIChatConfig, { FeaturesType } from '../config';
import { JSONToObject } from '../../../utils';
class AiRobotManager {
private name = 'aiRobotManager';
private static instance: AiRobotManager | null = null;
private aiRobots: Map<string, Record<string, any>> = new Map();
private streamingMessages: Map<string, IMessageModel | undefined> = new Map();
private streamingStatus: Map<string, boolean> = new Map();
private streamingListener: (options: Record<string, boolean>) => void;
private constructor() {
this.aiRobots = new Map();
this.streamingMessages = new Map();
this.streamingStatus = new Map();
this.streamingListener = () => {};
}
private getUserID = (conversationID: string) => {
const type = conversationID.startsWith(TUIChatEngine.TYPES.CONV_C2C)
? TUIChatEngine.TYPES.CONV_C2C
: TUIChatEngine.TYPES.CONV_GROUP;
return conversationID.replace(type, '');
};
private getRobotInfo = async (conversationID: string) => {
const userID = this.getUserID(conversationID);
if (this.aiRobots.has(userID)) {
return;
}
try {
const res = await TUIUserService.getUserProfile({
userIDList: [userID],
});
const { data } = res;
this.aiRobots.set(userID, data[0]);
} catch (error) {
this.aiRobots.delete(userID);
}
};
private genMsgKey = (message: IMessageModel) => {
const { sequence, random, time } = message as any;
return `${sequence}_${random}_${time}`;
};
private addThinkingMessage = (conversationID: string, messageList: IMessageModel[]) => {
if (messageList.length === 0) {
return messageList;
}
const lastMessage = messageList[messageList.length - 1];
const isOutMessage = lastMessage.flow === 'out';
const isBreakMessage = this.isBreakMessage(lastMessage);
const currentTime = parseInt(`${Date.now() / 1000}`);
const isOverTime = currentTime - lastMessage.time > 30;
if (!isOutMessage || isBreakMessage || isOverTime) {
return messageList;
}
const type: string = messageList[0].conversationType as string;
const aiRobotID = conversationID.replace(type, '');
const thinkingMessage = {
ID: `thinking-${Date.now()}`,
conversationID,
from: aiRobotID,
to: '',
type: TUIChatEngine.TYPES.MSG_CUSTOM,
conversationType: type,
flow: 'in',
avatar: this.aiRobots.get(aiRobotID)?.avatar || '',
reactionList: [],
readReceiptInfo: {},
time: currentTime,
payload: {
data: JSON.stringify({
chatbotPlugin: ChatbotPlugin,
isThinking: true,
}),
},
getMessageContent: () => ({
showName: 'thinking',
}),
} as unknown as IMessageModel;
messageList.push(thinkingMessage);
return messageList;
};
public static getInstance(): AiRobotManager {
if (!AiRobotManager.instance) {
AiRobotManager.instance = new AiRobotManager();
}
return AiRobotManager.instance;
}
public initAiRobotChat = (conversationID: string) => {
const showFeatures = [
FeaturesType.CopyMessage,
FeaturesType.DeleteMessage,
FeaturesType.ForwardMessage,
];
const hideFeatures = (Object.keys(FeaturesType) as FeaturesType[]).map((key) => {
if (!showFeatures.includes(FeaturesType[key])) {
return FeaturesType[key];
}
}) as FeaturesType[];
TUIChatConfig.hideTUIChatFeatures(hideFeatures);
TUIChatConfig.showTUIChatFeatures([FeaturesType.ClearHistory]);
TUIChatConfig.setChatType('aiRobot');
this.getRobotInfo(conversationID);
};
public isRobot = (conversationID: string) => {
const userID = this.getUserID(conversationID);
return userID.startsWith('@RBT#');
};
public isRobotMessage = (message?: IMessageModel) => {
if (!message || !message.ID || !message.from) {
return false;
}
const { type, payload } = message;
const isCustomMessage = type === TUIChatEngine.TYPES.MSG_CUSTOM;
if (isCustomMessage) {
const data = JSONToObject(payload.data);
return data.chatbotPlugin === ChatbotPlugin;
}
return false;
};
public isStreamingMessage = (message: IMessageModel) => {
if (this.isRobotMessage(message)) {
const payloadData = JSONToObject(message.payload.data);
if (Object.keys(payloadData).includes('isFinished')) {
return payloadData?.isFinished === 0 ? true : false;
}
return this.isThinkingMessage(message);
}
return false;
};
public onSteamingStatusChange = (callback: (options: Record<string, boolean>) => void) => {
this.streamingListener = callback;
};
public isThinkingMessage = (message?: IMessageModel) => {
const { payload } = message || {};
if (this.isRobotMessage(message)) {
const payloadData = JSONToObject(payload.data);
return payloadData.isThinking;
}
return false;
};
private setSteamingStatus = (conversationID: string, status: boolean) => {
if (status) {
this.streamingStatus.set(conversationID, status);
} else {
this.streamingStatus.delete(conversationID);
}
const options: Record<string, boolean> = {};
for (const [key, value] of this.streamingStatus) {
options[key] = value;
}
this.streamingListener?.(options);
};
public handleMessageList = (messageList: IMessageModel[], conversationID: string) => {
if (messageList.length === 0) {
this.setSteamingStatus(conversationID, false);
return messageList;
}
let list = messageList?.filter((message) => {
return !this.isBreakMessage(message);
});
list = this.addThinkingMessage(conversationID, list);
if (list.length > 0) {
const lastMessage = list[list.length - 1];
this.setSteamingStatus(conversationID, this.isStreamingMessage(lastMessage));
const { payload, from } = lastMessage;
if (this.isRobotMessage(lastMessage) && from.startsWith('@RBT#')) {
const payloadData = JSONToObject(payload.data);
const isFinished = payloadData.isFinished === 1 ? true : false;
this.streamingMessages.set(conversationID, !isFinished ? lastMessage : undefined);
}
}
return list;
};
public getRobotRenderText = (message: IMessageModel) => {
const { payload, type } = message;
const isCustomMessage = type === TUIChatEngine.TYPES.MSG_CUSTOM;
if (!this.isRobotMessage(message) || !isCustomMessage) {
return '';
}
const { text } = this.getRobotRenderContent(payload.data);
return text;
};
public getRobotRenderContent = (data: string) => {
const payloadData = JSONToObject(data);
let renderText = '';
if (payloadData.chunks) {
const _chunks = payloadData.chunks;
if (typeof _chunks === 'string' || Array.isArray(_chunks)) {
renderText = Array.isArray(_chunks) ? _chunks.join('') : _chunks;
}
} else if (payloadData.content) {
const _content = payloadData.content;
if (typeof _content === 'string' || Array.isArray(_content)) {
renderText = Array.isArray(_content) ? _content.join('') : _content;
}
} else if (payloadData.text) {
renderText = payloadData.text;
}
return {
text: renderText,
payloadData,
};
};
public isBreakMessage = (message: IMessageModel) => {
const { payload } = message;
if (this.isRobotMessage(message)) {
const data = JSONToObject(payload.data);
return data.src === ChatbotBreakMsgType;
}
return false;
};
public isErrorMessage = (message: IMessageModel) => {
const { payload } = message;
if (this.isRobotMessage(message)) {
const data = JSONToObject(payload.data);
return data.src === ChatbotErrorMsgType;
}
return false;
};
public sendBreakConversation = (conversationID: string) => {
const message = this.streamingMessages.get(conversationID) as IMessageModel;
this.sendBreakMessage(message);
};
public sendBreakMessage = (message: IMessageModel) => {
if (this.isRobotMessage(message)) {
breakAiRobotPayload.msgKey = this.genMsgKey(message);
const data = JSON.stringify(breakAiRobotPayload);
return TUIChatService.sendCustomMessage({
to: message.from,
payload: { data },
}, sendMessageOptions);
}
return Promise.resolve();
};
}
export default AiRobotManager;