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