crush-level-web/src/app/(main)/chat/[id]/stream-chat.ts

295 lines
7.6 KiB
TypeScript
Raw Normal View History

2025-12-11 11:31:56 +00:00
'use client';
import { Channel, StreamChat } from 'stream-chat';
import { create } from 'zustand';
2025-12-18 10:14:12 +00:00
import {
getUserToken,
createChannel,
deleteChannel,
fetchUserChatSetting,
updateUserChatSetting,
} from '@/services/chat';
2025-12-17 10:13:47 +00:00
import { parseSSEStream, parseData } from '@/utils/streamParser';
2025-12-18 10:14:12 +00:00
import { protect } from '@/lib/protect';
2025-12-17 10:13:47 +00:00
type Message = {
key: string;
role: string;
content: string;
};
2025-12-11 11:31:56 +00:00
2025-12-18 10:14:12 +00:00
export type ChatSettingType = {
chatModel: string;
longText: 0 | 1;
maximumReplies: number;
background: string;
font: number;
voiceActor: string;
};
type UserType = {
userId: string;
userName: string;
};
2025-12-11 11:31:56 +00:00
interface StreamChatStore {
2025-12-17 10:13:47 +00:00
client: StreamChat | null;
2025-12-18 10:14:12 +00:00
user: UserType;
chatSetting: ChatSettingType;
2025-12-17 10:13:47 +00:00
// 连接 StreamChat 客户端
2025-12-11 11:31:56 +00:00
connect: (user: any) => Promise<void>;
2025-12-17 10:13:47 +00:00
// 频道
2025-12-11 11:31:56 +00:00
channels: Channel[];
currentChannel: Channel | null;
2025-12-18 10:14:12 +00:00
// 用户聊天设置管理
setChatSetting: (chatSetting: any) => void;
fetchUserChatSetting: () => Promise<void>;
updateUserChatSetting: () => Promise<void>;
2025-12-17 10:13:47 +00:00
// 创建某个角色的聊天频道, 返回channelId
createChannel: (characterId: string) => Promise<string | false>;
2025-12-11 11:31:56 +00:00
switchToChannel: (id: string) => Promise<void>;
2025-12-18 10:14:12 +00:00
queryChannels: (filter: any) => Promise<Channel[]>;
deleteChannel: (
id: string[]
) => Promise<{ result: string; newChannels?: Channel[]; error?: unknown }>;
2025-12-17 10:13:47 +00:00
getCurrentCharacter: () => any | null;
// 消息列表
messages: Message[];
setMessages: (messages: Message[]) => void;
// 发送消息
sendMessage: (content: string) => Promise<void>;
// 清除通知
2025-12-11 11:31:56 +00:00
clearNotifications: () => Promise<void>;
2025-12-18 10:14:12 +00:00
// 推出登录,清除状态
clearClient: () => void;
2025-12-11 11:31:56 +00:00
}
export const useStreamChatStore = create<StreamChatStore>((set, get) => ({
2025-12-17 10:13:47 +00:00
client: null,
user: {
userId: '',
userName: '',
},
2025-12-18 10:14:12 +00:00
chatSetting: {
chatModel: '',
longText: 0,
maximumReplies: 0,
background: '',
font: 16,
voiceActor: '',
},
2025-12-11 11:31:56 +00:00
channels: [],
2025-12-17 10:13:47 +00:00
messages: [],
setMessages: (messages: any[]) => set({ messages }),
2025-12-11 11:31:56 +00:00
currentChannel: null,
2025-12-17 10:13:47 +00:00
// 获取当前聊天频道中的角色id
getCurrentCharacter() {
const { currentChannel, user } = get();
2025-12-18 10:14:12 +00:00
return Object.values(currentChannel?.state?.members || {})?.find((i) => {
return i.user_id !== user?.userId;
});
2025-12-17 10:13:47 +00:00
},
// 创建某个角色的聊天频道
async createChannel(characterId: string) {
const { user, client } = get();
const { switchToChannel, queryChannels } = get();
if (!client) {
return false;
}
const { data } = await createChannel({
userId: user.userId,
userName: user.userName,
characterId,
});
if (!data?.channelId) {
return false;
}
await queryChannels({});
switchToChannel(data.channelId);
return data.channelId;
},
2025-12-18 10:14:12 +00:00
setChatSetting: (setting: any) => {
const { chatSetting } = get();
set({ chatSetting: { ...chatSetting, ...setting } });
},
async fetchUserChatSetting() {
const { user } = get();
const { data } = await fetchUserChatSetting({
userId: Number(user.userId),
});
if (data) {
set({ chatSetting: data });
}
},
async updateUserChatSetting() {
const { user, chatSetting, fetchUserChatSetting } = get();
await updateUserChatSetting({
...chatSetting,
userId: user.userId,
});
fetchUserChatSetting();
},
2025-12-11 11:31:56 +00:00
async connect(user) {
2025-12-18 10:14:12 +00:00
const { client, queryChannels, fetchUserChatSetting } = get();
2025-12-17 10:13:47 +00:00
set({ user });
2025-12-15 11:31:18 +00:00
if (client) return;
2025-12-11 11:31:56 +00:00
const { data } = await getUserToken(user);
2025-12-17 10:13:47 +00:00
const streamClient = new StreamChat(process.env.NEXT_PUBLIC_STREAM_CHAT_API_KEY || '');
2025-12-18 10:14:12 +00:00
await protect(() =>
streamClient.connectUser(
{
id: user.userId,
name: user.userName,
},
data
)
2025-12-11 11:31:56 +00:00
);
2025-12-17 10:13:47 +00:00
set({ client: streamClient });
2025-12-18 10:14:12 +00:00
await queryChannels({});
await fetchUserChatSetting();
2025-12-11 11:31:56 +00:00
},
2025-12-17 10:13:47 +00:00
2025-12-11 11:31:56 +00:00
async switchToChannel(id: string) {
2025-12-17 10:13:47 +00:00
const { client, user } = get();
const channel = client!.channel('messaging', id);
2025-12-18 10:14:12 +00:00
const result = await protect(() =>
channel.query({
messages: { limit: 100 },
})
);
2025-12-17 10:13:47 +00:00
const messages = result.messages.map((i) => ({
key: i.id,
role: i.user?.id === user.userId ? 'user' : 'assistant',
content: i.text!,
}));
set({ currentChannel: channel, messages });
2025-12-11 11:31:56 +00:00
},
2025-12-17 10:13:47 +00:00
async queryChannels() {
const { user, client } = get();
2025-12-11 11:31:56 +00:00
if (!client) {
console.error('StreamChat client is not connected');
2025-12-18 10:14:12 +00:00
return [];
2025-12-11 11:31:56 +00:00
}
2025-12-18 10:14:12 +00:00
let channels: Channel[] = [];
2025-12-11 11:31:56 +00:00
try {
2025-12-18 10:14:12 +00:00
channels = await protect(() =>
client.queryChannels(
{
members: {
$in: [user.userId],
},
2025-12-17 10:13:47 +00:00
},
2025-12-18 10:14:12 +00:00
{
last_message_at: -1,
},
{
message_limit: 1, // 返回最新的1条消息
}
)
2025-12-17 10:13:47 +00:00
);
2025-12-11 11:31:56 +00:00
set({ channels });
} catch (error) {
console.error('Failed to query channels:', error);
}
2025-12-18 10:14:12 +00:00
return channels;
2025-12-11 11:31:56 +00:00
},
2025-12-17 10:13:47 +00:00
2025-12-18 10:14:12 +00:00
async deleteChannel(ids: string[]) {
const { channels, currentChannel, client, queryChannels } = get();
const deleteChannels = channels.filter((ch) => ids.includes(ch.id!));
2025-12-11 11:31:56 +00:00
try {
2025-12-18 10:14:12 +00:00
await Promise.all(
deleteChannels.map((ch) => {
return client?.channel('messaging', ch.id)?.delete();
})
);
await deleteChannel(ids);
const newChannels = await queryChannels({});
if (currentChannel?.id && ids.includes(currentChannel.id)) {
2025-12-17 10:13:47 +00:00
set({ currentChannel: null });
}
2025-12-18 10:14:12 +00:00
return { result: 'ok', newChannels };
2025-12-11 11:31:56 +00:00
} catch (error) {
2025-12-18 10:14:12 +00:00
return { result: 'error', error: error };
2025-12-11 11:31:56 +00:00
}
},
2025-12-17 10:13:47 +00:00
2025-12-11 11:31:56 +00:00
async clearNotifications() {},
2025-12-17 10:13:47 +00:00
// 发送消息
sendMessage: async (content: any) => {
const { user, currentChannel, getCurrentCharacter, setMessages, messages } = get();
// 过滤出用户和助手的消息
const filteredMessages = messages.filter((i) => i.role === 'user' || i.role === 'assistant');
let finalMessages = [
...filteredMessages,
{ key: user.userId, role: 'user', content: content },
{ key: 'assistant', role: 'assistant', content: '' },
];
setMessages(finalMessages);
// 发送消息到服务器
const response = await fetch(
2025-12-18 10:14:12 +00:00
`${process.env.NEXT_PUBLIC_CHAT_API_URL}/chat-api/chat/ai/generateReply`,
2025-12-17 10:13:47 +00:00
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: user.userId,
channelId: currentChannel?.id || '',
message: content,
2025-12-18 10:14:12 +00:00
characterId: getCurrentCharacter()?.user_id,
language: 'zh',
2025-12-17 10:13:47 +00:00
}),
}
);
// 处理服务器返回的 SSE 流
await parseSSEStream(response, (event: string, data: string) => {
if (event === 'chat-message') {
const d = parseData(data);
2025-12-18 10:14:12 +00:00
// 重新赋值最后一项,改变引用
const lastMsg = { ...finalMessages[finalMessages.length - 1] };
2025-12-17 10:13:47 +00:00
if (lastMsg.role === 'assistant') {
2025-12-18 10:14:12 +00:00
lastMsg.content = lastMsg.content + d.text || '';
2025-12-17 10:13:47 +00:00
}
2025-12-18 10:14:12 +00:00
finalMessages[finalMessages.length - 1] = lastMsg;
2025-12-17 10:13:47 +00:00
setMessages([...finalMessages]);
}
});
},
2025-12-18 10:14:12 +00:00
// 推出登录,清除状态
clearClient: async () => {
const { client } = get();
await client?.disconnectUser();
set({
client: null,
user: { userId: '', userName: '' },
chatSetting: {
chatModel: '',
longText: 0,
maximumReplies: 0,
background: '',
font: 16,
voiceActor: '',
},
channels: [],
messages: [],
currentChannel: null,
});
},
2025-12-11 11:31:56 +00:00
}));