import { defineStore } from 'pinia'; interface ScrollStatePayload { autoScrollEnabled?: boolean; userScrolling?: boolean; } interface ChatState { messages: Array; currentMessageIndex: number; streamingMessage: boolean; expandedBlocks: Set; autoScrollEnabled: boolean; userScrolling: boolean; thinkingScrollLocks: Map; } const GENERATING_LABELS = [ '正在构思…', '稍候,AI 正在准备', '准备工具中', '容我三思…', '答案马上就来', '灵感加载中', '思路拼装中', '琢磨最佳方案', '脑内开会中', '整理资料中', '润色回复中', '调配上下文', '搜刮记忆中', '快敲完了,别急' ]; function randomGeneratingLabel() { if (!GENERATING_LABELS.length) { return ''; } const index = Math.floor(Math.random() * GENERATING_LABELS.length); return GENERATING_LABELS[index]; } function createAssistantMessage() { return { role: 'assistant', actions: [], streamingThinking: '', streamingText: '', currentStreamingType: null, activeThinkingId: null, awaitingFirstContent: false, generatingLabel: randomGeneratingLabel() }; } function randomId(prefix: string) { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; } function cloneSet(source: Set) { return new Set(Array.from(source)); } function cloneMap(source: Map) { return new Map(Array.from(source.entries())); } function clearAwaitingFirstContent(message: any) { if (message && message.awaitingFirstContent) { message.awaitingFirstContent = false; } } export const useChatStore = defineStore('chat', { state: (): ChatState => ({ messages: [], currentMessageIndex: -1, streamingMessage: false, expandedBlocks: new Set(), autoScrollEnabled: true, userScrolling: false, thinkingScrollLocks: new Map() }), getters: { isScrollLocked: state => state.autoScrollEnabled && !state.userScrolling }, actions: { setStreamingMessage(active: boolean) { this.streamingMessage = !!active; }, setCurrentMessageIndex(index: number) { this.currentMessageIndex = index; }, setMessages(messages: Array) { this.messages = messages; }, clearMessages() { this.messages = []; this.currentMessageIndex = -1; }, toggleBlock(blockId: string) { const next = cloneSet(this.expandedBlocks); if (next.has(blockId)) { next.delete(blockId); } else { next.add(blockId); } this.expandedBlocks = next; }, expandBlock(blockId: string) { const next = cloneSet(this.expandedBlocks); next.add(blockId); this.expandedBlocks = next; }, collapseBlock(blockId: string) { const next = cloneSet(this.expandedBlocks); next.delete(blockId); this.expandedBlocks = next; }, clearExpandedBlocks() { this.expandedBlocks = new Set(); }, setThinkingLock(blockId: string, locked: boolean) { const next = cloneMap(this.thinkingScrollLocks); if (locked) { next.set(blockId, true); } else { next.delete(blockId); } this.thinkingScrollLocks = next; }, clearThinkingLocks() { this.thinkingScrollLocks = new Map(); }, setScrollState(payload: ScrollStatePayload) { if (typeof payload.autoScrollEnabled !== 'undefined') { this.autoScrollEnabled = !!payload.autoScrollEnabled; } if (typeof payload.userScrolling !== 'undefined') { this.userScrolling = !!payload.userScrolling; } }, enableAutoScroll() { this.setScrollState({ autoScrollEnabled: true, userScrolling: false }); }, disableAutoScroll() { this.setScrollState({ autoScrollEnabled: false, userScrolling: false }); }, toggleScrollLockState() { const locked = this.isScrollLocked; if (locked) { this.disableAutoScroll(); return false; } this.enableAutoScroll(); return true; }, ensureAssistantMessage() { if (this.currentMessageIndex >= 0) { return this.messages[this.currentMessageIndex]; } const message = createAssistantMessage(); this.messages.push(message); this.currentMessageIndex = this.messages.length - 1; return message; }, addUserMessage(content: string, images: string[] = []) { this.messages.push({ role: 'user', content, images }); this.currentMessageIndex = -1; }, startAssistantMessage() { const message = createAssistantMessage(); this.messages.push(message); this.currentMessageIndex = this.messages.length - 1; this.streamingMessage = true; message.awaitingFirstContent = true; return message; }, startThinkingAction() { const msg = this.ensureAssistantMessage(); clearAwaitingFirstContent(msg); msg.streamingThinking = ''; msg.currentStreamingType = 'thinking'; const actionId = randomId('thinking'); const blockId = actionId; const action = { id: actionId, type: 'thinking', content: '', streaming: true, timestamp: Date.now(), blockId }; msg.actions.push(action); msg.activeThinkingId = actionId; return { action, blockId }; }, appendThinkingChunk(content: string) { if (this.currentMessageIndex < 0) return null; const msg = this.messages[this.currentMessageIndex]; msg.streamingThinking += content; const thinkingAction = this.getActiveThinkingAction(msg); if (thinkingAction) { thinkingAction.content += content; return thinkingAction; } return null; }, completeThinking(fullContent: string) { if (this.currentMessageIndex < 0) return null; const msg = this.messages[this.currentMessageIndex]; const thinkingAction = this.getActiveThinkingAction(msg); if (thinkingAction) { thinkingAction.streaming = false; thinkingAction.content = fullContent; msg.streamingThinking = ''; msg.currentStreamingType = null; msg.activeThinkingId = null; return thinkingAction.blockId || thinkingAction.id; } return null; }, startTextAction() { const msg = this.ensureAssistantMessage(); if (!msg) { return null; } clearAwaitingFirstContent(msg); msg.streamingText = ''; msg.currentStreamingType = 'text'; const action = { id: randomId('text'), type: 'text', content: '', streaming: true, timestamp: Date.now() }; msg.actions.push(action); return action; }, appendTextChunk(content: string) { if (this.currentMessageIndex < 0) return null; const msg = this.messages[this.currentMessageIndex]; if (!msg) { return null; } if (typeof msg.streamingText !== 'string') { msg.streamingText = ''; } msg.streamingText += content; const lastAction = msg.actions[msg.actions.length - 1]; if (lastAction && lastAction.type === 'text') { lastAction.content += content; return lastAction; } return null; }, completeText(fullContent: string) { if (this.currentMessageIndex < 0) return; const msg = this.messages[this.currentMessageIndex]; for (let i = msg.actions.length - 1; i >= 0; i--) { const action = msg.actions[i]; if (action.type === 'text' && action.streaming) { action.streaming = false; if (typeof fullContent === 'string' && fullContent.length) { action.content = fullContent; } break; } } msg.streamingText = ''; msg.currentStreamingType = null; }, addSystemMessage(content: string) { const msg = this.ensureAssistantMessage(); clearAwaitingFirstContent(msg); msg.actions.push({ id: randomId('system'), type: 'system', content, timestamp: Date.now() }); }, addAppendPayloadAction(data: any) { const msg = this.ensureAssistantMessage(); clearAwaitingFirstContent(msg); msg.actions.push({ id: `append-payload-${Date.now()}-${Math.random()}`, type: 'append_payload', append: data, timestamp: Date.now() }); }, addModifyPayloadAction(data: any) { const msg = this.ensureAssistantMessage(); clearAwaitingFirstContent(msg); msg.actions.push({ id: `modify-payload-${Date.now()}-${Math.random()}`, type: 'modify_payload', modify: data, timestamp: Date.now() }); }, getActiveThinkingAction(msg: any) { if (!msg || !Array.isArray(msg.actions)) { return null; } if (msg.activeThinkingId) { const found = msg.actions.find( (action: any) => action && action.id === msg.activeThinkingId && action.type === 'thinking' ); if (found) { return found; } } for (let i = msg.actions.length - 1; i >= 0; i--) { const action = msg.actions[i]; if (action && action.type === 'thinking' && action.streaming !== false) { return action; } } return null; }, resetChatState() { this.messages = []; this.currentMessageIndex = -1; this.streamingMessage = false; this.clearExpandedBlocks(); this.clearThinkingLocks(); } } });