feat(webui): polish chat layout and titles
Align the WebUI sidebar and chat chrome with the updated design, and generate WebUI session titles asynchronously without blocking turns. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
Xubin Ren
co-authored by
Cursor
parent
d8fd4c80bf
commit
790a03ec28
+29
-31
@@ -25,7 +25,7 @@ type BootState =
|
||||
};
|
||||
|
||||
const SIDEBAR_STORAGE_KEY = "nanobot-webui.sidebar";
|
||||
const SIDEBAR_WIDTH = 279;
|
||||
const SIDEBAR_WIDTH = 272;
|
||||
type ShellView = "chat" | "settings";
|
||||
|
||||
function readSidebarOpen(): boolean {
|
||||
@@ -99,13 +99,6 @@ export default function App() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="flex flex-col items-center gap-3 animate-in fade-in-0 duration-300">
|
||||
<img
|
||||
src="/brand/nanobot_icon.png"
|
||||
alt=""
|
||||
className="h-10 w-10 animate-pulse select-none"
|
||||
aria-hidden
|
||||
draggable={false}
|
||||
/>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-foreground/40" />
|
||||
@@ -121,13 +114,6 @@ export default function App() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center px-4 text-center">
|
||||
<div className="flex max-w-md flex-col items-center gap-3">
|
||||
<img
|
||||
src="/brand/nanobot_icon.png"
|
||||
alt=""
|
||||
className="h-10 w-10 opacity-60 grayscale select-none"
|
||||
aria-hidden
|
||||
draggable={false}
|
||||
/>
|
||||
<p className="text-lg font-semibold">{t("app.error.title")}</p>
|
||||
<p className="text-sm text-muted-foreground">{state.message}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -213,7 +199,7 @@ function Shell({ onModelNameChange }: { onModelNameChange: (modelName: string |
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onNewChat = useCallback(async () => {
|
||||
const onCreateChat = useCallback(async () => {
|
||||
try {
|
||||
const chatId = await createChat();
|
||||
setActiveKey(`websocket:${chatId}`);
|
||||
@@ -226,6 +212,12 @@ function Shell({ onModelNameChange }: { onModelNameChange: (modelName: string |
|
||||
}
|
||||
}, [createChat]);
|
||||
|
||||
const onNewChat = useCallback(() => {
|
||||
setActiveKey(null);
|
||||
setView("chat");
|
||||
setMobileSidebarOpen(false);
|
||||
}, []);
|
||||
|
||||
const onSelectChat = useCallback(
|
||||
(key: string) => {
|
||||
setActiveKey(key);
|
||||
@@ -235,6 +227,15 @@ function Shell({ onModelNameChange }: { onModelNameChange: (modelName: string |
|
||||
[],
|
||||
);
|
||||
|
||||
const onOpenSettings = useCallback(() => {
|
||||
setView("settings");
|
||||
setMobileSidebarOpen(false);
|
||||
}, []);
|
||||
|
||||
const onTurnEnd = useCallback(() => {
|
||||
void refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const onConfirmDelete = useCallback(async () => {
|
||||
if (!pendingDelete) return;
|
||||
const key = pendingDelete.key;
|
||||
@@ -254,7 +255,8 @@ function Shell({ onModelNameChange }: { onModelNameChange: (modelName: string |
|
||||
}, [pendingDelete, deleteChat, activeKey, sessions]);
|
||||
|
||||
const headerTitle = activeSession
|
||||
? activeSession.preview ||
|
||||
? activeSession.title ||
|
||||
activeSession.preview ||
|
||||
t("chat.fallbackTitle", { id: activeSession.chatId.slice(0, 6) })
|
||||
: t("app.brand");
|
||||
|
||||
@@ -268,20 +270,10 @@ function Shell({ onModelNameChange }: { onModelNameChange: (modelName: string |
|
||||
sessions,
|
||||
activeKey,
|
||||
loading,
|
||||
theme,
|
||||
onToggleTheme: toggle,
|
||||
onNewChat: () => {
|
||||
void onNewChat();
|
||||
},
|
||||
onNewChat,
|
||||
onSelect: onSelectChat,
|
||||
onRefresh: () => void refresh(),
|
||||
onRequestDelete: (key: string, label: string) =>
|
||||
setPendingDelete({ key, label }),
|
||||
activeView: view,
|
||||
onOpenSettings: () => {
|
||||
setView("settings" as const);
|
||||
setMobileSidebarOpen(false);
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -296,10 +288,11 @@ function Shell({ onModelNameChange }: { onModelNameChange: (modelName: string |
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-y-0 left-0 h-full w-[279px] overflow-hidden bg-sidebar shadow-inner-right",
|
||||
"absolute inset-y-0 left-0 h-full overflow-hidden bg-sidebar shadow-inner-right",
|
||||
"transition-transform duration-300 ease-out",
|
||||
desktopSidebarOpen ? "translate-x-0" : "-translate-x-full",
|
||||
)}
|
||||
style={{ width: SIDEBAR_WIDTH }}
|
||||
>
|
||||
<Sidebar {...sidebarProps} onCollapse={closeDesktopSidebar} />
|
||||
</div>
|
||||
@@ -312,7 +305,8 @@ function Shell({ onModelNameChange }: { onModelNameChange: (modelName: string |
|
||||
<SheetContent
|
||||
side="left"
|
||||
showCloseButton={false}
|
||||
className="w-[279px] p-0 sm:max-w-[279px] lg:hidden"
|
||||
className="p-0 lg:hidden"
|
||||
style={{ width: SIDEBAR_WIDTH, maxWidth: SIDEBAR_WIDTH }}
|
||||
>
|
||||
<Sidebar {...sidebarProps} onCollapse={closeMobileSidebar} />
|
||||
</SheetContent>
|
||||
@@ -331,8 +325,12 @@ function Shell({ onModelNameChange }: { onModelNameChange: (modelName: string |
|
||||
session={activeSession}
|
||||
title={headerTitle}
|
||||
onToggleSidebar={toggleSidebar}
|
||||
onGoHome={() => setActiveKey(null)}
|
||||
onNewChat={onNewChat}
|
||||
onCreateChat={onCreateChat}
|
||||
onTurnEnd={onTurnEnd}
|
||||
theme={theme}
|
||||
onToggleTheme={toggle}
|
||||
onOpenSettings={onOpenSettings}
|
||||
hideSidebarToggleOnDesktop={desktopSidebarOpen}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user