import {chatSearchInput, msgInput, messagesDiv} from "./dom.js?v=20260909-account-controls"; import {get, post} from "./api.js?v=20260909-account-controls"; import {requestedChatId} from "./constants.js?v=20260909-account-controls"; export function createConversationController({ state, renderChatList, attachmentComposer, resizeComposerInput, isMobileLayout, closeMobileSidebar, setChatLoading, setChatActivity, updateWorkspaceCopy, updateChatActionState, setNotice, populateGenerationSettings, renderMessages, updateJumpToLatest, updateSendState, loadSummary }) { function getChatById(id) { return state.chatSessions.find((chat) => chat.id === id); } async function openCreatedChat(chat) { if (!chat?.id) throw new Error("The server did not return the newly created chat"); if (chatSearchInput) { chatSearchInput.value = ""; scheduleChatSearch(); } const chatsResponse = await get("/chats"); if (chatsResponse.error) throw new Error(chatsResponse.error); state.chatSessions = chatsResponse.chats || []; if (!state.chatSessions.some((item) => item.id === chat.id)) { throw new Error("The newly created chat was not persisted"); } renderChatList(); await setActiveChat(chat.id); } async function setActiveChat(id, {preserveComposer = false, matchMessageId = null} = {}) { const openRequest = ++state.chatOpenRequest; if (!preserveComposer && id !== state.activeChatId) { if (state.activeChatId != null) { state.chatDrafts.set(state.activeChatId, msgInput.value); attachmentComposer.saveDraft(state.activeChatId); msgInput.value = state.chatDrafts.get(id) || ""; attachmentComposer.restoreDraft(id); resizeComposerInput(); } else if (!msgInput.value && !attachmentComposer.pending && !attachmentComposer.attachments.length) { msgInput.value = state.chatDrafts.get(id) || ""; attachmentComposer.restoreDraft(id); resizeComposerInput(); } } if (isMobileLayout()) closeMobileSidebar(); setChatLoading(true, "Opening chat..."); setChatActivity(true, { eyebrow: "Opening chat", title: "Loading conversation", detail: "Fetching messages and restoring the current scene." }); state.activeChatId = id; history.replaceState(null, "", `/app/chats/${id}`); state.messageRetryState = new Map(); updateWorkspaceCopy(); renderChatList(); updateChatActionState(); let res = await get(`/chats/${id}/messages`); if (openRequest !== state.chatOpenRequest) { setChatLoading(false); setChatActivity(false); return; } if (res.error) { setChatLoading(false); setChatActivity(false); return setNotice(res.error, "error"); } if (matchMessageId && !res.messages?.some(message => message.id === matchMessageId)) { const activated = await post(`/chats/${id}/branches/${matchMessageId}/activate`, {includeDescendants: true}); if (!activated.error) res = await get(`/chats/${id}/messages`); if (openRequest !== state.chatOpenRequest) { setChatLoading(false); setChatActivity(false); return; } } const existing = getChatById(id); if (res.chat && existing) Object.assign(existing, res.chat); populateGenerationSettings(res.chat || existing); state.currentMessages = res.messages || []; renderMessages(); if (matchMessageId) requestAnimationFrame(() => { const target = [...messagesDiv.querySelectorAll("[data-message-id]")] .find(node => Number(node.dataset.messageId) === Number(matchMessageId)); if (target) { target.scrollIntoView({block: "center"}); updateJumpToLatest(); } }); updateSendState(); setChatLoading(false); setChatActivity(false); } async function loadChatSessions() { setChatLoading(true, "Loading chats..."); const res = await get("/chats"); if (res.error) { setChatLoading(false); return setNotice(res.error, "error"); } state.chatSessions = res.chats || []; renderChatList(); if (state.activeChatId && getChatById(state.activeChatId)) { updateWorkspaceCopy(); setChatLoading(false); return; } if (location.pathname.endsWith("/new") && !state.activeChatId) { state.currentMessages = []; renderMessages(); updateWorkspaceCopy(); updateSendState(); setChatLoading(false); return; } if (requestedChatId && !getChatById(requestedChatId) && !state.activeChatId) { setChatLoading(false); return setNotice("Conversation not found. Choose a chat from the sidebar or start a new one.", "error"); } if (state.chatSessions.length) { const preferredChatId = state.activeChatId && getChatById(state.activeChatId) ? state.activeChatId : (requestedChatId && getChatById(requestedChatId)) ? requestedChatId : state.chatSessions[0].id; setChatLoading(false); return setActiveChat(preferredChatId); } state.activeChatId = null; state.currentMessages = []; renderMessages(); updateWorkspaceCopy(); updateSendState(); setChatLoading(false); return setNotice("Pick a starting action to begin.", "success"); } async function searchChatSessions(query, requestId) { const res = await get(`/chats/search?q=${encodeURIComponent(query)}`); if (requestId !== state.chatSearchRequest || chatSearchInput.value.trim() !== query) return; if (res.error) { state.chatSearchError = "Unable to search chats."; renderChatList(); setNotice(res.error, "error"); return; } state.chatSearchResults = res.chats || []; renderChatList(); } function scheduleChatSearch() { clearTimeout(state.chatSearchTimer); const requestId = ++state.chatSearchRequest; const query = chatSearchInput.value.trim(); state.chatSearchResults = null; state.chatSearchError = ""; if (!query) { renderChatList(); return; } renderChatList(); state.chatSearchTimer = setTimeout(() => { void searchChatSessions(query, requestId); }, 300); } async function createNewChat({preserveComposer = false, signal} = {}) { const previousChatId = state.activeChatId; setChatLoading(true, "Creating chat..."); setChatActivity(true, { eyebrow: "Creating chat", title: "Opening a new conversation", detail: "Setting up a fresh chat so you can start sending messages." }); try { const res = await post("/chats", {title: "New chat"}, {signal}); if (res.error || !res.chat) { const error = res.error || "Unable to create chat."; setNotice(error, "error"); return {error}; } state.chatSessions.unshift(res.chat); await loadSummary(); renderChatList(); if (!signal?.aborted && state.activeChatId === previousChatId) { await setActiveChat(res.chat.id, {preserveComposer}); msgInput.focus(); } setNotice("New chat created.", "success"); return res.chat; } finally { setChatLoading(false); setChatActivity(false); } } return { getChatById, openCreatedChat, setActiveChat, loadChatSessions, scheduleChatSearch, createNewChat }; }