/** * Chat Domain Store * * Valtio-based state management for chat. * Replaces Zustand for better performance with fine-grained reactivity. */ import { proxy, subscribe } from 'valtio'; import type { Message, Conversation, Agent, AgentProfileLike } from './types'; // === Constants === const DEFAULT_AGENT: Agent = { id: '1', name: 'ZCLAW', icon: '🦞', color: 'bg-gradient-to-br from-orange-500 to-red-500', lastMessage: '发送消息开始对话', time: '', }; // === Helper Functions === function generateConvId(): string { return `conv_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`; } function deriveTitle(messages: Message[]): string { const firstUser = messages.find(m => m.role === 'user'); if (firstUser) { const text = firstUser.content.trim(); return text.length > 30 ? text.slice(0, 30) + '...' : text; } return '新对话'; } export function toChatAgent(profile: AgentProfileLike): Agent { return { id: profile.id, name: profile.name, icon: profile.nickname?.slice(0, 1) || profile.name.slice(0, 1) || '🦞', color: 'bg-gradient-to-br from-orange-500 to-red-500', lastMessage: profile.role || '新分身', time: '', }; } // === Store Interface === export interface ChatStore { // State messages: Message[]; conversations: Conversation[]; currentConversationId: string | null; agents: Agent[]; currentAgent: Agent | null; isStreaming: boolean; currentModel: string; sessionKey: string | null; // Actions addMessage: (message: Message) => void; updateMessage: (id: string, updates: Partial) => void; deleteMessage: (id: string) => void; setCurrentAgent: (agent: Agent) => void; syncAgents: (profiles: AgentProfileLike[]) => void; setCurrentModel: (model: string) => void; setStreaming: (streaming: boolean) => void; setSessionKey: (key: string | null) => void; newConversation: () => void; switchConversation: (id: string) => void; deleteConversation: (id: string) => void; clearMessages: () => void; } // === Create Proxy State === export const chatStore = proxy({ // Initial state messages: [], conversations: [], currentConversationId: null, agents: [DEFAULT_AGENT], currentAgent: DEFAULT_AGENT, isStreaming: false, currentModel: 'glm-4-flash', sessionKey: null, // === Actions === addMessage: (message: Message) => { chatStore.messages.push(message); }, updateMessage: (id: string, updates: Partial) => { const msg = chatStore.messages.find(m => m.id === id); if (msg) { Object.assign(msg, updates); } }, deleteMessage: (id: string) => { const index = chatStore.messages.findIndex(m => m.id === id); if (index >= 0) { chatStore.messages.splice(index, 1); } }, setCurrentAgent: (agent: Agent) => { chatStore.currentAgent = agent; }, syncAgents: (profiles: AgentProfileLike[]) => { if (profiles.length === 0) { chatStore.agents = [DEFAULT_AGENT]; } else { chatStore.agents = profiles.map(toChatAgent); } }, setCurrentModel: (model: string) => { chatStore.currentModel = model; }, setStreaming: (streaming: boolean) => { chatStore.isStreaming = streaming; }, setSessionKey: (key: string | null) => { chatStore.sessionKey = key; }, newConversation: () => { // Save current conversation if has messages if (chatStore.messages.length > 0) { const conversation: Conversation = { id: chatStore.currentConversationId || generateConvId(), title: deriveTitle(chatStore.messages), messages: [...chatStore.messages], sessionKey: chatStore.sessionKey, agentId: chatStore.currentAgent?.id || null, createdAt: new Date(), updatedAt: new Date(), }; // Check if conversation already exists const existingIndex = chatStore.conversations.findIndex( c => c.id === chatStore.currentConversationId ); if (existingIndex >= 0) { chatStore.conversations[existingIndex] = conversation; } else { chatStore.conversations.unshift(conversation); } } // Reset for new conversation chatStore.messages = []; chatStore.sessionKey = null; chatStore.isStreaming = false; chatStore.currentConversationId = null; }, switchConversation: (id: string) => { const conv = chatStore.conversations.find(c => c.id === id); if (conv) { // Save current first if (chatStore.messages.length > 0) { const currentConv: Conversation = { id: chatStore.currentConversationId || generateConvId(), title: deriveTitle(chatStore.messages), messages: [...chatStore.messages], sessionKey: chatStore.sessionKey, agentId: chatStore.currentAgent?.id || null, createdAt: new Date(), updatedAt: new Date(), }; const existingIndex = chatStore.conversations.findIndex( c => c.id === chatStore.currentConversationId ); if (existingIndex >= 0) { chatStore.conversations[existingIndex] = currentConv; } else { chatStore.conversations.unshift(currentConv); } } // Switch to new chatStore.messages = [...conv.messages]; chatStore.sessionKey = conv.sessionKey; chatStore.currentConversationId = conv.id; } }, deleteConversation: (id: string) => { const index = chatStore.conversations.findIndex(c => c.id === id); if (index >= 0) { chatStore.conversations.splice(index, 1); // If deleting current, clear messages if (chatStore.currentConversationId === id) { chatStore.messages = []; chatStore.sessionKey = null; chatStore.currentConversationId = null; } } }, clearMessages: () => { chatStore.messages = []; }, }); // === Dev Mode Logging === if (import.meta.env.DEV) { subscribe(chatStore, (ops) => { console.log('[ChatStore] Changes:', ops); }); }