restore settings ui
This commit is contained in:
370
web/src/App.tsx
370
web/src/App.tsx
@@ -820,6 +820,14 @@ export default function App() {
|
|||||||
const [renameChatError, setRenameChatError] = useState<string | null>(null);
|
const [renameChatError, setRenameChatError] = useState<string | null>(null);
|
||||||
const [isRenamingChat, setIsRenamingChat] = useState(false);
|
const [isRenamingChat, setIsRenamingChat] = useState(false);
|
||||||
const [isChatSettingsOpen, setIsChatSettingsOpen] = useState(false);
|
const [isChatSettingsOpen, setIsChatSettingsOpen] = useState(false);
|
||||||
|
const [isSavingChatSettings, setIsSavingChatSettings] = useState(false);
|
||||||
|
const [chatSettingsError, setChatSettingsError] = useState<string | null>(null);
|
||||||
|
const [draftChatTitle, setDraftChatTitle] = useState("");
|
||||||
|
const [chatSettingsTitleDraft, setChatSettingsTitleDraft] = useState("");
|
||||||
|
const [chatSettingsProviderDraft, setChatSettingsProviderDraft] = useState<Provider>("openai");
|
||||||
|
const [chatSettingsModelDraft, setChatSettingsModelDraft] = useState("");
|
||||||
|
const [chatSettingsPromptDraft, setChatSettingsPromptDraft] = useState("");
|
||||||
|
const [chatSettingsEnabledToolsDraft, setChatSettingsEnabledToolsDraft] = useState<string[]>([]);
|
||||||
const [additionalSystemPrompt, setAdditionalSystemPrompt] = useState("");
|
const [additionalSystemPrompt, setAdditionalSystemPrompt] = useState("");
|
||||||
const [enabledTools, setEnabledTools] = useState<string[]>([]);
|
const [enabledTools, setEnabledTools] = useState<string[]>([]);
|
||||||
const [transcriptTailSpacerHeight, setTranscriptTailSpacerHeight] = useState(TRANSCRIPT_BOTTOM_GAP);
|
const [transcriptTailSpacerHeight, setTranscriptTailSpacerHeight] = useState(TRANSCRIPT_BOTTOM_GAP);
|
||||||
@@ -948,6 +956,14 @@ export default function App() {
|
|||||||
setComposer("");
|
setComposer("");
|
||||||
setPendingAttachments([]);
|
setPendingAttachments([]);
|
||||||
setIsChatSettingsOpen(false);
|
setIsChatSettingsOpen(false);
|
||||||
|
setIsSavingChatSettings(false);
|
||||||
|
setChatSettingsError(null);
|
||||||
|
setDraftChatTitle("");
|
||||||
|
setChatSettingsTitleDraft("");
|
||||||
|
setChatSettingsProviderDraft("openai");
|
||||||
|
setChatSettingsModelDraft("");
|
||||||
|
setChatSettingsPromptDraft("");
|
||||||
|
setChatSettingsEnabledToolsDraft([]);
|
||||||
setAdditionalSystemPrompt("");
|
setAdditionalSystemPrompt("");
|
||||||
setEnabledTools([]);
|
setEnabledTools([]);
|
||||||
setIsQuickQuestionOpen(false);
|
setIsQuickQuestionOpen(false);
|
||||||
@@ -1131,6 +1147,10 @@ export default function App() {
|
|||||||
|
|
||||||
const providerModelOptions = useMemo(() => getModelOptions(modelCatalog, provider), [modelCatalog, provider]);
|
const providerModelOptions = useMemo(() => getModelOptions(modelCatalog, provider), [modelCatalog, provider]);
|
||||||
const quickProviderModelOptions = useMemo(() => getModelOptions(modelCatalog, quickProvider), [modelCatalog, quickProvider]);
|
const quickProviderModelOptions = useMemo(() => getModelOptions(modelCatalog, quickProvider), [modelCatalog, quickProvider]);
|
||||||
|
const chatSettingsProviderModelOptions = useMemo(
|
||||||
|
() => getModelOptions(modelCatalog, chatSettingsProviderDraft),
|
||||||
|
[chatSettingsProviderDraft, modelCatalog]
|
||||||
|
);
|
||||||
const providerOptions = useMemo(() => getVisibleProviders(modelCatalog), [modelCatalog]);
|
const providerOptions = useMemo(() => getVisibleProviders(modelCatalog), [modelCatalog]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1354,11 +1374,7 @@ export default function App() {
|
|||||||
}, [draftKind, selectedChat, selectedChatSummary, selectedItem]);
|
}, [draftKind, selectedChat, selectedChatSummary, selectedItem]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (draftKind === "chat") {
|
if (draftKind === "chat") return;
|
||||||
setAdditionalSystemPrompt("");
|
|
||||||
setEnabledTools(getDefaultEnabledTools(availableChatTools));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (selectedItem?.kind !== "chat") return;
|
if (selectedItem?.kind !== "chat") return;
|
||||||
const chat = selectedChat?.id === selectedItem.id ? selectedChat : selectedChatSummary;
|
const chat = selectedChat?.id === selectedItem.id ? selectedChat : selectedChatSummary;
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
@@ -1367,7 +1383,7 @@ export default function App() {
|
|||||||
}, [availableChatTools, draftKind, selectedChat, selectedChatSummary, selectedItem]);
|
}, [availableChatTools, draftKind, selectedChat, selectedChatSummary, selectedItem]);
|
||||||
|
|
||||||
const selectedTitle = useMemo(() => {
|
const selectedTitle = useMemo(() => {
|
||||||
if (draftKind === "chat") return "New chat";
|
if (draftKind === "chat") return draftChatTitle.trim() || "New chat";
|
||||||
if (draftKind === "search") return "New search";
|
if (draftKind === "search") return "New search";
|
||||||
if (!selectedItem) return "Sybil";
|
if (!selectedItem) return "Sybil";
|
||||||
if (selectedItem.kind === "chat") {
|
if (selectedItem.kind === "chat") {
|
||||||
@@ -1378,7 +1394,7 @@ export default function App() {
|
|||||||
if (selectedSearchForView) return getSearchTitle(selectedSearchForView);
|
if (selectedSearchForView) return getSearchTitle(selectedSearchForView);
|
||||||
if (selectedSearchSummary) return getSearchTitle(selectedSearchSummary);
|
if (selectedSearchSummary) return getSearchTitle(selectedSearchSummary);
|
||||||
return "New search";
|
return "New search";
|
||||||
}, [draftKind, selectedChat, selectedChatSummary, selectedItem, selectedSearchForView, selectedSearchSummary]);
|
}, [draftChatTitle, draftKind, selectedChat, selectedChatSummary, selectedItem, selectedSearchForView, selectedSearchSummary]);
|
||||||
|
|
||||||
const pageTitle = useMemo(() => {
|
const pageTitle = useMemo(() => {
|
||||||
if (draftKind || !selectedItem) return "Sybil";
|
if (draftKind || !selectedItem) return "Sybil";
|
||||||
@@ -1410,6 +1426,11 @@ export default function App() {
|
|||||||
setSelectedChat(null);
|
setSelectedChat(null);
|
||||||
setSelectedSearch(null);
|
setSelectedSearch(null);
|
||||||
setPendingAttachments([]);
|
setPendingAttachments([]);
|
||||||
|
setDraftChatTitle("");
|
||||||
|
setAdditionalSystemPrompt("");
|
||||||
|
setEnabledTools(getDefaultEnabledTools(availableChatTools));
|
||||||
|
setIsChatSettingsOpen(false);
|
||||||
|
setChatSettingsError(null);
|
||||||
setIsMobileSidebarOpen(false);
|
setIsMobileSidebarOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1427,6 +1448,8 @@ export default function App() {
|
|||||||
setSelectedChat(null);
|
setSelectedChat(null);
|
||||||
setSelectedSearch(null);
|
setSelectedSearch(null);
|
||||||
setPendingAttachments([]);
|
setPendingAttachments([]);
|
||||||
|
setIsChatSettingsOpen(false);
|
||||||
|
setChatSettingsError(null);
|
||||||
setIsMobileSidebarOpen(false);
|
setIsMobileSidebarOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1557,6 +1580,99 @@ export default function App() {
|
|||||||
setRenameChatDialog({ chatId });
|
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) => {
|
const openContextMenu = (event: MouseEvent, item: SidebarSelection) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const menuWidth = 176;
|
const menuWidth = 176;
|
||||||
@@ -1669,6 +1785,17 @@ export default function App() {
|
|||||||
return () => window.clearTimeout(timer);
|
return () => window.clearTimeout(timer);
|
||||||
}, [renameChatDialog]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (!isQuickQuestionOpen) return;
|
if (!isQuickQuestionOpen) return;
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
@@ -1829,9 +1956,17 @@ export default function App() {
|
|||||||
let chatId = draftKind === "chat" ? null : selectedItem?.kind === "chat" ? selectedItem.id : null;
|
let chatId = draftKind === "chat" ? null : selectedItem?.kind === "chat" ? selectedItem.id : null;
|
||||||
|
|
||||||
if (!chatId) {
|
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;
|
chatId = chat.id;
|
||||||
setDraftKind(null);
|
setDraftKind(null);
|
||||||
|
setDraftChatTitle("");
|
||||||
setChats((current) => {
|
setChats((current) => {
|
||||||
const withoutExisting = current.filter((existing) => existing.id !== chat.id);
|
const withoutExisting = current.filter((existing) => existing.id !== chat.id);
|
||||||
return [chat, ...withoutExisting];
|
return [chat, ...withoutExisting];
|
||||||
@@ -2903,40 +3038,22 @@ export default function App() {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full max-w-xl items-center gap-2 md:w-auto">
|
<div className="flex w-full max-w-xl items-center justify-end gap-2 md:w-auto">
|
||||||
{!isSearchMode ? (
|
{!isSearchMode ? (
|
||||||
<>
|
<Button
|
||||||
<select
|
type="button"
|
||||||
className="h-10 min-w-32 rounded-lg border border-violet-300/22 bg-background/72 px-3 text-sm text-violet-50 outline-none shadow-[inset_0_1px_0_hsl(255_100%_92%_/_0.06)] focus:border-violet-300/45 focus:ring-1 focus:ring-ring/70"
|
variant="secondary"
|
||||||
value={provider}
|
className="h-10 max-w-full gap-2 rounded-lg px-3"
|
||||||
onChange={(event) => {
|
onClick={openChatSettings}
|
||||||
const nextProvider = event.currentTarget.value as Provider;
|
|
||||||
setProvider(nextProvider);
|
|
||||||
const options = getModelOptions(modelCatalog, nextProvider);
|
|
||||||
setModel(pickProviderModel(options, providerModelPreferences[nextProvider]));
|
|
||||||
}}
|
|
||||||
disabled={isActiveSelectionSending}
|
disabled={isActiveSelectionSending}
|
||||||
|
aria-label="Open chat settings"
|
||||||
>
|
>
|
||||||
{providerOptions.map((candidate) => (
|
<Settings2 className="h-4 w-4 shrink-0" />
|
||||||
<option key={candidate} value={candidate}>
|
<span className="shrink-0">Settings</span>
|
||||||
{getProviderLabel(candidate)}
|
<span className="hidden min-w-0 max-w-[18rem] truncate text-xs font-medium text-violet-100/58 sm:inline">
|
||||||
</option>
|
{getProviderLabel(provider)} · {model || "No model"}
|
||||||
))}
|
</span>
|
||||||
</select>
|
</Button>
|
||||||
<ModelCombobox
|
|
||||||
options={providerModelOptions}
|
|
||||||
value={model}
|
|
||||||
disabled={isActiveSelectionSending}
|
|
||||||
onChange={(nextModel) => {
|
|
||||||
const normalizedModel = nextModel.trim();
|
|
||||||
setModel(normalizedModel);
|
|
||||||
setProviderModelPreferences((current) => ({
|
|
||||||
...current,
|
|
||||||
[provider]: normalizedModel || null,
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-10 items-center rounded-lg border border-cyan-300/22 bg-cyan-300/8 px-3 text-sm text-cyan-100">
|
<div className="flex h-10 items-center rounded-lg border border-cyan-300/22 bg-cyan-300/8 px-3 text-sm text-cyan-100">
|
||||||
<Globe2 className="mr-2 h-4 w-4" />
|
<Globe2 className="mr-2 h-4 w-4" />
|
||||||
@@ -3108,6 +3225,181 @@ export default function App() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{isChatSettingsOpen ? (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/72 p-3 backdrop-blur-md md:p-6"
|
||||||
|
onMouseDown={(event) => {
|
||||||
|
if (event.target === event.currentTarget && !isSavingChatSettings) setIsChatSettingsOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="chat-settings-title"
|
||||||
|
className="glass-panel flex max-h-[88vh] w-full max-w-2xl flex-col rounded-2xl border border-violet-300/24 p-4 shadow-2xl shadow-black/45 md:p-5"
|
||||||
|
onSubmit={(event) => void handleChatSettingsSubmit(event)}
|
||||||
|
>
|
||||||
|
<div className="mb-4 flex items-center justify-between gap-3">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<h2 id="chat-settings-title" className="text-sm font-semibold text-violet-50">
|
||||||
|
Chat settings
|
||||||
|
</h2>
|
||||||
|
<p className="mt-1 truncate text-xs text-muted-foreground">{chatSettingsTitleDraft.trim() || "New chat"}</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => setIsChatSettingsOpen(false)}
|
||||||
|
disabled={isSavingChatSettings}
|
||||||
|
aria-label="Close chat settings"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto pr-1">
|
||||||
|
<label className="block">
|
||||||
|
<span className="mb-1.5 block text-xs font-semibold text-violet-100/72">Chat title</span>
|
||||||
|
<input
|
||||||
|
value={chatSettingsTitleDraft}
|
||||||
|
onInput={(event) => {
|
||||||
|
setChatSettingsTitleDraft(event.currentTarget.value);
|
||||||
|
if (chatSettingsError) setChatSettingsError(null);
|
||||||
|
}}
|
||||||
|
maxLength={120}
|
||||||
|
placeholder={draftKind === null && selectedItem?.kind === "chat" ? "Chat title" : "Optional title"}
|
||||||
|
className="h-11 w-full rounded-lg border border-violet-300/22 bg-background/72 px-3 text-sm text-violet-50 outline-none shadow-[inset_0_1px_0_hsl(255_100%_92%_/_0.06)] placeholder:text-muted-foreground focus:border-violet-300/45 focus:ring-1 focus:ring-ring/70"
|
||||||
|
disabled={isSavingChatSettings}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="grid gap-3 md:grid-cols-[minmax(9rem,0.7fr)_minmax(14rem,1fr)]">
|
||||||
|
<label className="block">
|
||||||
|
<span className="mb-1.5 block text-xs font-semibold text-violet-100/72">Provider</span>
|
||||||
|
<select
|
||||||
|
className="h-10 w-full rounded-lg border border-violet-300/22 bg-background/72 px-3 text-sm text-violet-50 outline-none shadow-[inset_0_1px_0_hsl(255_100%_92%_/_0.06)] focus:border-violet-300/45 focus:ring-1 focus:ring-ring/70"
|
||||||
|
value={chatSettingsProviderDraft}
|
||||||
|
onChange={(event) => {
|
||||||
|
const nextProvider = event.currentTarget.value as Provider;
|
||||||
|
setChatSettingsProviderDraft(nextProvider);
|
||||||
|
const options = getModelOptions(modelCatalog, nextProvider);
|
||||||
|
setChatSettingsModelDraft(pickProviderModel(options, providerModelPreferences[nextProvider]));
|
||||||
|
setChatSettingsError(null);
|
||||||
|
}}
|
||||||
|
disabled={isSavingChatSettings}
|
||||||
|
>
|
||||||
|
{providerOptions.map((candidate) => (
|
||||||
|
<option key={candidate} value={candidate}>
|
||||||
|
{getProviderLabel(candidate)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="block min-w-0">
|
||||||
|
<span className="mb-1.5 block text-xs font-semibold text-violet-100/72">Model</span>
|
||||||
|
<ModelCombobox
|
||||||
|
options={chatSettingsProviderModelOptions}
|
||||||
|
value={chatSettingsModelDraft}
|
||||||
|
disabled={isSavingChatSettings}
|
||||||
|
onChange={(nextModel) => {
|
||||||
|
setChatSettingsModelDraft(nextModel.trim());
|
||||||
|
setChatSettingsError(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block">
|
||||||
|
<span className="mb-1.5 block text-xs font-semibold text-violet-100/72">Additional system prompt</span>
|
||||||
|
<Textarea
|
||||||
|
rows={5}
|
||||||
|
value={chatSettingsPromptDraft}
|
||||||
|
onInput={(event) => {
|
||||||
|
setChatSettingsPromptDraft(event.currentTarget.value);
|
||||||
|
if (chatSettingsError) setChatSettingsError(null);
|
||||||
|
}}
|
||||||
|
placeholder="Add per-chat instructions"
|
||||||
|
className="min-h-32 resize-y border-violet-300/24 bg-background/72 text-sm text-violet-50 placeholder:text-violet-200/45"
|
||||||
|
disabled={isSavingChatSettings}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div className="mb-2 flex items-center justify-between gap-3">
|
||||||
|
<h3 className="text-xs font-semibold text-violet-100/72">Tools</h3>
|
||||||
|
{availableChatTools.length ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => setChatSettingsEnabledToolsDraft(getDefaultEnabledTools(availableChatTools))}
|
||||||
|
disabled={isSavingChatSettings}
|
||||||
|
>
|
||||||
|
<Check className="h-3.5 w-3.5" />
|
||||||
|
All
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => setChatSettingsEnabledToolsDraft([])}
|
||||||
|
disabled={isSavingChatSettings}
|
||||||
|
>
|
||||||
|
<X className="h-3.5 w-3.5" />
|
||||||
|
None
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{availableChatTools.length ? (
|
||||||
|
availableChatTools.map((tool) => {
|
||||||
|
const checked = chatSettingsEnabledToolsDraft.includes(tool.name);
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
key={tool.name}
|
||||||
|
className="flex cursor-pointer items-start gap-3 rounded-lg border border-violet-300/18 bg-background/44 px-3 py-2.5 transition hover:border-violet-300/34 hover:bg-violet-400/8"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={checked}
|
||||||
|
onChange={() => toggleChatSettingsTool(tool.name)}
|
||||||
|
className="mt-1 h-4 w-4 rounded border-violet-300/35 bg-background/80 accent-violet-400"
|
||||||
|
disabled={isSavingChatSettings}
|
||||||
|
/>
|
||||||
|
<span className="min-w-0">
|
||||||
|
<span className="block text-sm font-medium text-violet-50">{getToolLabel(tool.name)}</span>
|
||||||
|
<span className="mt-0.5 block text-xs leading-5 text-muted-foreground">{tool.description}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<p className="rounded-lg border border-violet-300/18 bg-background/44 px-3 py-2.5 text-sm text-muted-foreground">
|
||||||
|
No chat tools are available.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{chatSettingsError ? <p className="mt-3 text-sm text-rose-300">{chatSettingsError}</p> : null}
|
||||||
|
<div className="mt-4 flex justify-end gap-2">
|
||||||
|
<Button type="button" variant="secondary" onClick={() => setIsChatSettingsOpen(false)} disabled={isSavingChatSettings}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={isSavingChatSettings}>
|
||||||
|
{isSavingChatSettings ? <LoaderCircle className="h-4 w-4 animate-spin" /> : <Check className="h-4 w-4" />}
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{renameChatDialog ? (
|
{renameChatDialog ? (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/72 p-3 backdrop-blur-md md:p-6"
|
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/72 p-3 backdrop-blur-md md:p-6"
|
||||||
|
|||||||
@@ -307,7 +307,10 @@ export async function updateChatStar(chatId: string, starred: boolean) {
|
|||||||
return data.chat;
|
return data.chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateChatSettings(chatId: string, body: { additionalSystemPrompt?: string | null; enabledTools?: string[] }) {
|
export async function updateChatSettings(
|
||||||
|
chatId: string,
|
||||||
|
body: { title?: string; additionalSystemPrompt?: string | null; enabledTools?: string[] }
|
||||||
|
) {
|
||||||
const data = await api<{ chat: ChatSummary }>(`/v1/chats/${chatId}`, {
|
const data = await api<{ chat: ChatSummary }>(`/v1/chats/${chatId}`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
|
|||||||
Reference in New Issue
Block a user