From dda20955bb3dfffaf62bc02a735a54ee21d03d2e Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sat, 30 May 2026 18:28:31 -0700 Subject: [PATCH] restore settings ui --- web/src/App.tsx | 374 ++++++++++++++++++++++++++++++++++++++++----- web/src/lib/api.ts | 5 +- 2 files changed, 337 insertions(+), 42 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index 82ed546..f38b496 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -820,6 +820,14 @@ export default function App() { const [renameChatError, setRenameChatError] = useState(null); const [isRenamingChat, setIsRenamingChat] = useState(false); const [isChatSettingsOpen, setIsChatSettingsOpen] = useState(false); + const [isSavingChatSettings, setIsSavingChatSettings] = useState(false); + const [chatSettingsError, setChatSettingsError] = useState(null); + const [draftChatTitle, setDraftChatTitle] = useState(""); + const [chatSettingsTitleDraft, setChatSettingsTitleDraft] = useState(""); + const [chatSettingsProviderDraft, setChatSettingsProviderDraft] = useState("openai"); + const [chatSettingsModelDraft, setChatSettingsModelDraft] = useState(""); + const [chatSettingsPromptDraft, setChatSettingsPromptDraft] = useState(""); + const [chatSettingsEnabledToolsDraft, setChatSettingsEnabledToolsDraft] = useState([]); const [additionalSystemPrompt, setAdditionalSystemPrompt] = useState(""); const [enabledTools, setEnabledTools] = useState([]); const [transcriptTailSpacerHeight, setTranscriptTailSpacerHeight] = useState(TRANSCRIPT_BOTTOM_GAP); @@ -948,6 +956,14 @@ export default function App() { setComposer(""); setPendingAttachments([]); setIsChatSettingsOpen(false); + setIsSavingChatSettings(false); + setChatSettingsError(null); + setDraftChatTitle(""); + setChatSettingsTitleDraft(""); + setChatSettingsProviderDraft("openai"); + setChatSettingsModelDraft(""); + setChatSettingsPromptDraft(""); + setChatSettingsEnabledToolsDraft([]); setAdditionalSystemPrompt(""); setEnabledTools([]); setIsQuickQuestionOpen(false); @@ -1131,6 +1147,10 @@ export default function App() { const providerModelOptions = useMemo(() => getModelOptions(modelCatalog, provider), [modelCatalog, provider]); const quickProviderModelOptions = useMemo(() => getModelOptions(modelCatalog, quickProvider), [modelCatalog, quickProvider]); + const chatSettingsProviderModelOptions = useMemo( + () => getModelOptions(modelCatalog, chatSettingsProviderDraft), + [chatSettingsProviderDraft, modelCatalog] + ); const providerOptions = useMemo(() => getVisibleProviders(modelCatalog), [modelCatalog]); useEffect(() => { @@ -1354,11 +1374,7 @@ export default function App() { }, [draftKind, selectedChat, selectedChatSummary, selectedItem]); useEffect(() => { - if (draftKind === "chat") { - setAdditionalSystemPrompt(""); - setEnabledTools(getDefaultEnabledTools(availableChatTools)); - return; - } + if (draftKind === "chat") return; if (selectedItem?.kind !== "chat") return; const chat = selectedChat?.id === selectedItem.id ? selectedChat : selectedChatSummary; if (!chat) return; @@ -1367,7 +1383,7 @@ export default function App() { }, [availableChatTools, draftKind, selectedChat, selectedChatSummary, selectedItem]); const selectedTitle = useMemo(() => { - if (draftKind === "chat") return "New chat"; + if (draftKind === "chat") return draftChatTitle.trim() || "New chat"; if (draftKind === "search") return "New search"; if (!selectedItem) return "Sybil"; if (selectedItem.kind === "chat") { @@ -1378,7 +1394,7 @@ export default function App() { if (selectedSearchForView) return getSearchTitle(selectedSearchForView); if (selectedSearchSummary) return getSearchTitle(selectedSearchSummary); return "New search"; - }, [draftKind, selectedChat, selectedChatSummary, selectedItem, selectedSearchForView, selectedSearchSummary]); + }, [draftChatTitle, draftKind, selectedChat, selectedChatSummary, selectedItem, selectedSearchForView, selectedSearchSummary]); const pageTitle = useMemo(() => { if (draftKind || !selectedItem) return "Sybil"; @@ -1410,6 +1426,11 @@ export default function App() { setSelectedChat(null); setSelectedSearch(null); setPendingAttachments([]); + setDraftChatTitle(""); + setAdditionalSystemPrompt(""); + setEnabledTools(getDefaultEnabledTools(availableChatTools)); + setIsChatSettingsOpen(false); + setChatSettingsError(null); setIsMobileSidebarOpen(false); }; @@ -1427,6 +1448,8 @@ export default function App() { setSelectedChat(null); setSelectedSearch(null); setPendingAttachments([]); + setIsChatSettingsOpen(false); + setChatSettingsError(null); setIsMobileSidebarOpen(false); }; @@ -1557,6 +1580,99 @@ export default function App() { setRenameChatDialog({ chatId }); }; + const getChatSettingsSeedTitle = () => { + if (draftKind === "chat") return draftChatTitle; + if (selectedItem?.kind === "chat") { + if (selectedChat?.id === selectedItem.id) return getChatTitle(selectedChat, selectedChat.messages); + if (selectedChatSummary) return getChatTitle(selectedChatSummary); + } + return draftChatTitle; + }; + + const openChatSettings = () => { + if (isSearchMode) return; + setContextMenu(null); + setRenameChatDialog(null); + setChatSettingsError(null); + setChatSettingsTitleDraft(getChatSettingsSeedTitle()); + setChatSettingsProviderDraft(provider); + setChatSettingsModelDraft(model); + setChatSettingsPromptDraft(additionalSystemPrompt); + setChatSettingsEnabledToolsDraft(normalizeEnabledTools(enabledTools, availableChatTools)); + setIsChatSettingsOpen(true); + }; + + const toggleChatSettingsTool = (toolName: string) => { + setChatSettingsEnabledToolsDraft((current) => { + if (current.includes(toolName)) return current.filter((name) => name !== toolName); + return current.concat(toolName); + }); + }; + + const commitLocalChatSettings = (nextProvider: Provider, nextModel: string, nextPrompt: string, nextTools: string[], nextTitle: string) => { + setProvider(nextProvider); + setModel(nextModel); + setProviderModelPreferences((current) => ({ + ...current, + [nextProvider]: nextModel || null, + })); + setAdditionalSystemPrompt(nextPrompt); + setEnabledTools(nextTools); + setDraftChatTitle(nextTitle); + }; + + const handleChatSettingsSubmit = async (event?: Event) => { + event?.preventDefault(); + if (isSavingChatSettings) return; + + const nextModel = chatSettingsModelDraft.trim(); + if (!nextModel) { + setChatSettingsError("Enter a model."); + return; + } + + const existingChatId = draftKind === null && selectedItem?.kind === "chat" ? selectedItem.id : null; + const isExistingChat = existingChatId !== null; + const nextTitle = chatSettingsTitleDraft.trim(); + if (isExistingChat && !nextTitle) { + setChatSettingsError("Enter a chat title."); + return; + } + + const nextPrompt = chatSettingsPromptDraft.trim(); + const nextTools = availableChatTools.length + ? normalizeEnabledTools(chatSettingsEnabledToolsDraft, availableChatTools) + : chatSettingsEnabledToolsDraft; + + setIsSavingChatSettings(true); + setChatSettingsError(null); + setError(null); + try { + if (isExistingChat) { + const updatedChat = await updateChatSettings(existingChatId, { + title: nextTitle, + additionalSystemPrompt: nextPrompt || null, + ...(availableChatTools.length ? { enabledTools: nextTools } : {}), + }); + applyChatSummary(updatedChat); + } else if (!selectedItem && draftKind !== "chat") { + setDraftKind("chat"); + } + + commitLocalChatSettings(chatSettingsProviderDraft, nextModel, nextPrompt, nextTools, nextTitle); + setIsChatSettingsOpen(false); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (message.includes("bearer token")) { + handleAuthFailure(message); + } else { + setChatSettingsError(message); + } + } finally { + setIsSavingChatSettings(false); + } + }; + const openContextMenu = (event: MouseEvent, item: SidebarSelection) => { event.preventDefault(); const menuWidth = 176; @@ -1669,6 +1785,17 @@ export default function App() { return () => window.clearTimeout(timer); }, [renameChatDialog]); + useEffect(() => { + if (!isChatSettingsOpen) return; + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key !== "Escape" || isSavingChatSettings) return; + event.preventDefault(); + setIsChatSettingsOpen(false); + }; + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [isChatSettingsOpen, isSavingChatSettings]); + useEffect(() => { if (!isQuickQuestionOpen) return; const handleKeyDown = (event: KeyboardEvent) => { @@ -1829,9 +1956,17 @@ export default function App() { let chatId = draftKind === "chat" ? null : selectedItem?.kind === "chat" ? selectedItem.id : null; if (!chatId) { - const chat = await createChat(); + const initialEnabledTools = availableChatTools.length ? normalizeEnabledTools(enabledTools, availableChatTools) : undefined; + const chat = await createChat({ + ...(draftChatTitle.trim() ? { title: draftChatTitle.trim() } : {}), + provider, + model: selectedModel, + ...(additionalSystemPrompt.trim() ? { additionalSystemPrompt: additionalSystemPrompt.trim() } : {}), + ...(initialEnabledTools !== undefined ? { enabledTools: initialEnabledTools } : {}), + }); chatId = chat.id; setDraftKind(null); + setDraftChatTitle(""); setChats((current) => { const withoutExisting = current.filter((existing) => existing.id !== chat.id); return [chat, ...withoutExisting]; @@ -2903,40 +3038,22 @@ export default function App() { ) : null} -
+
{!isSearchMode ? ( - <> - - { - const normalizedModel = nextModel.trim(); - setModel(normalizedModel); - setProviderModelPreferences((current) => ({ - ...current, - [provider]: normalizedModel || null, - })); - }} - /> - + ) : (
@@ -3108,6 +3225,181 @@ export default function App() {
) : null} + {isChatSettingsOpen ? ( +
{ + if (event.target === event.currentTarget && !isSavingChatSettings) setIsChatSettingsOpen(false); + }} + > +
void handleChatSettingsSubmit(event)} + > +
+
+

+ Chat settings +

+

{chatSettingsTitleDraft.trim() || "New chat"}

+
+ +
+ +
+ + +
+ + + +
+ +