fix(webui): name temporary chats from first message

This commit is contained in:
chengyongru 2026-08-07 13:27:53 +08:00
parent 4d77189d9c
commit 325f7b0367
5 changed files with 20 additions and 6 deletions

View File

@ -64,6 +64,7 @@ import {
import { projectNameFromPath, scopeWithAccessMode } from "@/lib/workspace";
import {
createTemporaryChatSession,
deriveTemporaryChatTitle,
} from "@/lib/temporary-chat";
type BootState =
@ -2069,7 +2070,7 @@ function Shell({
}, []);
const headerTitle = temporaryChatActive
? t("temporaryChat.title")
? deriveTemporaryChatTitle(activeSession?.preview, t("temporaryChat.title"))
: activeSession
? sidebarState.title_overrides[activeSession.key] ||
activeSession.title ||

View File

@ -44,6 +44,7 @@ import {
type ChatGroupLabels,
} from "@/lib/chat-groups";
import { clearDraggedSession, writeDraggedSession } from "@/lib/session-drag";
import { deriveTemporaryChatTitle } from "@/lib/temporary-chat";
import { cn } from "@/lib/utils";
import type { ChatSummary, SidebarDensity, SidebarSortMode } from "@/lib/types";
@ -537,7 +538,7 @@ function TemporaryChatSection({
<ul className="space-y-0.5">
{sessions.map((session) => {
const active = session.key === activeKey;
const title = deriveTitle(session.preview, t("temporaryChat.title"));
const title = deriveTemporaryChatTitle(session.preview, t("temporaryChat.title"));
return (
<li key={session.key} className="min-w-0">
<div

View File

@ -2,6 +2,15 @@ import type { ChatSummary } from "./types";
const WEBSOCKET_SESSION_KEY_PREFIX = "websocket:";
export function deriveTemporaryChatTitle(
firstMessage: string | undefined,
fallback: string,
): string {
const oneLine = firstMessage?.replace(/\s+/g, " ").trim() ?? "";
if (!oneLine) return fallback;
return oneLine.length > 60 ? `${oneLine.slice(0, 57)}` : oneLine;
}
export function createTemporaryChatSession(chatId: string): ChatSummary {
const now = new Date().toISOString();
return {

View File

@ -466,7 +466,10 @@ describe("App layout", () => {
name: "first private message",
}));
await waitFor(() => expect(window.location.hash).toBe(firstHash));
expect(screen.getByText("Temporary chat")).toBeInTheDocument();
expect(within(screen.getByTestId("thread-header")).getByText(
"first private message",
)).toBeInTheDocument();
await waitFor(() => expect(document.title).toBe("first private message · nanobot"));
expect(screen.queryByRole("button", { name: "Temporary chat" })).not.toBeInTheDocument();
fireEvent.click(within(sidebar).getByRole("button", {

View File

@ -156,7 +156,7 @@ describe("ChatList", () => {
const temporarySession = session({
key: "temporary:temporary-one",
chatId: "temporary-one",
preview: "Private planning",
preview: "hi",
});
const onSelect = vi.fn();
const onClose = vi.fn();
@ -176,11 +176,11 @@ describe("ChatList", () => {
);
const section = screen.getByRole("region", { name: "Temporary chats" });
fireEvent.click(within(section).getByRole("button", { name: "Private planning" }));
fireEvent.click(within(section).getByRole("button", { name: "hi" }));
expect(onSelect).toHaveBeenCalledWith("temporary:temporary-one");
fireEvent.click(within(section).getByRole("button", {
name: "Close temporary chat: Private planning",
name: "Close temporary chat: hi",
}));
expect(onClose).toHaveBeenCalledWith("temporary:temporary-one");
});