import { defineStore } from 'pinia'
import { computed } from 'vue'
import { useSessionsStore } from '@/stores/sessions.js'
import { createChatState } from './state.js'
import { createMessageHelpers } from './messages.js'
import { createDraftActions } from './drafts.js'
import { createContentActions } from './content.js'
import { createRecallActions } from './recall.js'
import { createTaskTracking } from './tasks.js'
import { createToolTracking } from './tools.js'
import { createTerminalTracking } from './terminals.js'
import { createTurnEvents } from './turn.js'
import { createSessionActions } from './session.js'
/**
* The chat store, split by area:
*
* * `state` — every ref, map and set;
* * `messages` — transcript building, user messages, viewed images, proposals;
* * `session` — load/reload/clear, catch-up, archive pagination;
* * `turn` — stream lifecycle, thinking, planning, compression, errors;
* * `tools` — tool cards and their id-first matching;
* * `tasks` — background-task snapshot and live task_update;
* * `terminals` — terminal metadata and live output;
* * `content` — artifacts, files, feedback;
* * `recall` — the scheduled recall banner and its actions;
* * `drafts` — localStorage drafts;
* * `naming` — background session naming.
*
* The modules share one state object, so they work on the same refs and maps
* exactly as the former single closure did. Only the public surface is
* exposed here — helpers such as `_applyToolResult` stay internal.
*/
export const useChatStore = defineStore('chat', () => {
const sessionsStore = useSessionsStore()
const state = createChatState()
const messageHelpers = createMessageHelpers(state)
const drafts = createDraftActions(state)
const content = createContentActions(state)
const recallActions = createRecallActions(state)
const tasks = createTaskTracking(state)
const tools = createToolTracking({ state, content, recall: recallActions, messageHelpers })
const terminals = createTerminalTracking(state)
const turn = createTurnEvents({ state, tools, messageHelpers })
const session = createSessionActions({ state, messageHelpers, content, tasks, sessionsStore })
// Key of the newest published-content card in the transcript. It used to be
// a per-message computed: every AssistantMessage instance walked every
// message and every tool on every tool event (O(n²) across the list). One
// store-level computed is evaluated once and shared by all cards.
const latestContentToolKey = computed(() => {
let latest = null
for (const message of state.messages.value) {
const tools = message?.tools
if (!tools?.length) continue
for (let i = 0; i < tools.length; i++) {
const tool = tools[i]
if (tool.kind === 'tool' && tool.name === 'content_publish') {
latest = `${message.id}:content:${i}`
}
}
}
return latest
})
return {
currentId: state.currentId,
currentProfileId: state.currentProfileId,
messages: state.messages,
streaming: state.streaming,
pendingImages: state.pendingImages,
pendingFiles: state.pendingFiles,
artifacts: state.artifacts,
files: state.files,
viewedImages: state.viewedImages,
contextTokens: state.contextTokens,
maxContextTokens: state.maxContextTokens,
servedModel: state.servedModel,
servedTarget: state.servedTarget,
servedFallback: state.servedFallback,
wsConnected: state.wsConnected,
wsReconnecting: state.wsReconnecting,
loading: state.loading,
loadError: state.loadError,
loadErrorSessionId: state.loadErrorSessionId,
latestContentToolKey,
imagesProcessing: state.imagesProcessing,
awaitingStream: state.awaitingStream,
streamingMsg: state.streamingMsg,
scrollToMessageIndex: state.scrollToMessageIndex,
recall: state.recall,
terminals: state.terminals,
backgroundTasks: state.backgroundTasks,
archiveLoading: state.archiveLoading,
archiveHasMore: state.archiveHasMore,
archiveNextBefore: state.archiveNextBefore,
loadSession: session.loadSession,
clearSession: session.clearSession,
dismissLoadError: session.dismissLoadError,
reloadSession: session.reloadSession,
dropStaleStream: session.dropStaleStream,
checkForUpdates: session.checkForUpdates,
fetchArtifacts: content.fetchArtifacts,
fetchFiles: content.fetchFiles,
fetchTasks: tasks.fetchTasks,
rateMessage: content.rateMessage,
saveDraft: drafts.saveDraft,
loadDraft: drafts.loadDraft,
appendUserMessage: messageHelpers.appendUserMessage,
markMessageFailed: messageHelpers.markMessageFailed,
buildMessageList: messageHelpers.buildMessageList,
onStreamStart: turn.onStreamStart,
onReplayStart: turn.onReplayStart,
onReplayEnd: turn.onReplayEnd,
onThinkingDelta: turn.onThinkingDelta,
onThinkingEnd: turn.onThinkingEnd,
onTurnThinking: turn.onTurnThinking,
onPlanningStatus: turn.onPlanningStatus,
onPlanReady: turn.onPlanReady,
onToolStarted: tools.onToolStarted,
onToolCall: tools.onToolCall,
onTerminalOutput: terminals.onTerminalOutput,
onTerminalClosed: terminals.onTerminalClosed,
onTerminalOpened: terminals.onTerminalOpened,
onTaskUpdate: tasks.onTaskUpdate,
onMessageQueued: turn.onMessageQueued,
onMessageDropped: turn.onMessageDropped,
onStreamDelta: turn.onStreamDelta,
onStreamEnd: turn.onStreamEnd,
onStreamStopped: turn.onStreamStopped,
onProfileSwitched: turn.onProfileSwitched,
onModelInfo: turn.onModelInfo,
onCompressionStarted: turn.onCompressionStarted,
onContextCompressed: turn.onContextCompressed,
onError: turn.onError,
loadRecall: recallActions.loadRecall,
cancelRecall: recallActions.cancelRecall,
skipRecall: recallActions.skipRecall,
onRecallUpdate: recallActions.onRecallUpdate,
onProposalDecided: messageHelpers.onProposalDecided,
loadArchivedMessages: session.loadArchivedMessages,
initArchiveState: session.initArchiveState,
}
})