Fix admin authorization and harden chat requests
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user