mirror of
https://github.com/coder/coder.git
synced 2026-09-21 12:44:32 +08:00
chore: remove frontend related regenerate chat title code (#26867)
Co-authored-by: Tracy Johnson <tracy@coder.com>
This commit is contained in:
co-authored by
Tracy Johnson
parent
39da38b189
commit
14d17abae6
@@ -3437,13 +3437,6 @@ class ExperimentalApiMethods {
|
||||
await this.axios.patch(`/api/experimental/chats/${chatId}`, req);
|
||||
};
|
||||
|
||||
regenerateChatTitle = async (chatId: string): Promise<TypesGen.Chat> => {
|
||||
const response = await this.axios.post<TypesGen.Chat>(
|
||||
`/api/experimental/chats/${chatId}/title/regenerate`,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
proposeChatTitle = async (chatId: string): Promise<{ title: string }> => {
|
||||
const response = await this.axios.post<{ title: string }>(
|
||||
`/api/experimental/chats/${chatId}/title/propose`,
|
||||
|
||||
@@ -38,7 +38,6 @@ import {
|
||||
prependToInfiniteChatsCache,
|
||||
promoteChatQueuedMessage,
|
||||
proposeChatTitle,
|
||||
regenerateChatTitle,
|
||||
removeChildFromParentInCache,
|
||||
reorderPinnedChat,
|
||||
setChatGroupRole,
|
||||
@@ -67,7 +66,6 @@ vi.mock("#/api/api", () => ({
|
||||
interruptChat: vi.fn(),
|
||||
promoteChatQueuedMessage: vi.fn(),
|
||||
proposeChatTitle: vi.fn(),
|
||||
regenerateChatTitle: vi.fn(),
|
||||
getChatAdvisorConfig: vi.fn(),
|
||||
updateChatAdvisorConfig: vi.fn(),
|
||||
getChatACL: vi.fn(),
|
||||
@@ -844,51 +842,6 @@ describe("reorderPinnedChat", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("regenerateChatTitle cache updates", () => {
|
||||
it("preserves existing chat detail fields when the response is partial", () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
const cachedChat = makeChat(chatId, {
|
||||
diff_status: {
|
||||
chat_id: chatId,
|
||||
url: "https://example.com/pr/1",
|
||||
pull_request_state: "open",
|
||||
pull_request_title: "",
|
||||
pull_request_draft: false,
|
||||
changes_requested: false,
|
||||
additions: 1,
|
||||
deletions: 2,
|
||||
changed_files: 3,
|
||||
refreshed_at: "2025-01-01T00:00:00.000Z",
|
||||
stale_at: "2025-01-01T01:00:00.000Z",
|
||||
},
|
||||
});
|
||||
queryClient.setQueryData(chatKey(chatId), cachedChat);
|
||||
seedInfiniteChats(queryClient, [cachedChat]);
|
||||
|
||||
const mutation = regenerateChatTitle(queryClient);
|
||||
const updatedChat = {
|
||||
id: chatId,
|
||||
title: "New title",
|
||||
} satisfies Partial<TypesGen.Chat>;
|
||||
|
||||
mutation.onSuccess(updatedChat as TypesGen.Chat);
|
||||
|
||||
const cachedDetail = queryClient.getQueryData<TypesGen.Chat>(
|
||||
chatKey(chatId),
|
||||
);
|
||||
expect(cachedDetail).toEqual({
|
||||
...cachedChat,
|
||||
title: "New title",
|
||||
});
|
||||
expect(cachedDetail?.diff_status).toEqual(cachedChat.diff_status);
|
||||
expect(readInfiniteChats(queryClient)?.[0]).toMatchObject({
|
||||
id: chatId,
|
||||
title: "New title",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("chat cost query factories", () => {
|
||||
it("builds the summary query key and forwards snake_case params", async () => {
|
||||
const user = "user-1";
|
||||
@@ -1504,28 +1457,6 @@ describe("mutation invalidation scope", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("regenerateChatTitle invalidates debug runs so the title_generation run surfaces immediately", async () => {
|
||||
const queryClient = createTestQueryClient();
|
||||
const chatId = "chat-1";
|
||||
seedAllActiveQueries(queryClient, chatId);
|
||||
|
||||
const mutation = regenerateChatTitle(queryClient);
|
||||
await mutation.onSettled(undefined, undefined, chatId);
|
||||
|
||||
expect(
|
||||
queryClient.getQueryState(chatDebugRunsKey(chatId))?.isInvalidated,
|
||||
"chatDebugRunsKey should be invalidated",
|
||||
).toBe(true);
|
||||
|
||||
for (const { label, key } of unrelatedKeys(chatId)) {
|
||||
const state = queryClient.getQueryState(key);
|
||||
expect(
|
||||
state?.isInvalidated,
|
||||
`${label} should NOT be invalidated by regenerateChatTitle`,
|
||||
).not.toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
for (const { label, error } of [
|
||||
{ label: "success", error: undefined },
|
||||
{ label: "failure", error: new Error("proposal failed") },
|
||||
|
||||
@@ -1197,38 +1197,6 @@ export const reorderPinnedChat = (queryClient: QueryClient) => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const regenerateChatTitle = (queryClient: QueryClient) => ({
|
||||
mutationFn: (chatId: string) => API.experimental.regenerateChatTitle(chatId),
|
||||
|
||||
onSuccess: (updatedChat: TypesGen.Chat) => {
|
||||
queryClient.setQueryData<TypesGen.Chat>(
|
||||
chatKey(updatedChat.id),
|
||||
(previousChat) =>
|
||||
previousChat ? { ...previousChat, ...updatedChat } : updatedChat,
|
||||
);
|
||||
updateInfiniteChatsCache(queryClient, (chats) =>
|
||||
chats.map((chat) =>
|
||||
chat.id === updatedChat.id
|
||||
? { ...chat, title: updatedChat.title }
|
||||
: chat,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
onSettled: async (
|
||||
_data: TypesGen.Chat | undefined,
|
||||
_error: unknown,
|
||||
chatId: string,
|
||||
) => {
|
||||
await invalidateChatListQueries(queryClient);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: chatKey(chatId),
|
||||
exact: true,
|
||||
});
|
||||
void invalidateChatDebugRuns(queryClient, chatId);
|
||||
},
|
||||
});
|
||||
|
||||
export const proposeChatTitle = (queryClient: QueryClient) => ({
|
||||
mutationFn: (chatId: string) => API.experimental.proposeChatTitle(chatId),
|
||||
|
||||
|
||||
@@ -62,8 +62,6 @@ const AgentChatPageLayout: FC = () => {
|
||||
requestUnpinAgent: () => {},
|
||||
isArchiving: false,
|
||||
archivingChatId: undefined,
|
||||
onRegenerateTitle: () => {},
|
||||
regeneratingTitleChatIds: [],
|
||||
isSidebarCollapsed: false,
|
||||
onToggleSidebarCollapsed: () => {},
|
||||
onExpandSidebar: () => {},
|
||||
|
||||
@@ -710,7 +710,6 @@ const AgentChatPage: FC = () => {
|
||||
isArchiving,
|
||||
archivingChatId,
|
||||
onOpenRenameDialog,
|
||||
regeneratingTitleChatIds,
|
||||
isSidebarCollapsed,
|
||||
onToggleSidebarCollapsed,
|
||||
onChatReady,
|
||||
@@ -747,10 +746,6 @@ const AgentChatPage: FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const isRegeneratingThisChat = agentId
|
||||
? regeneratingTitleChatIds.includes(agentId)
|
||||
: false;
|
||||
|
||||
const chatQuery = useQuery({
|
||||
...chat(agentId ?? ""),
|
||||
enabled: Boolean(agentId),
|
||||
@@ -1702,7 +1697,6 @@ const AgentChatPage: FC = () => {
|
||||
}
|
||||
isPinned={(chatRecord?.pin_order ?? 0) > 0}
|
||||
isChildChat={parentChatID !== undefined}
|
||||
isRegeneratingTitle={isRegeneratingThisChat}
|
||||
urlTransform={urlTransform}
|
||||
scrollContainerRef={scrollContainerRef}
|
||||
scrollToBottomRef={scrollToBottomRef}
|
||||
|
||||
@@ -197,7 +197,6 @@ interface AgentChatPageViewProps {
|
||||
isPinned?: boolean;
|
||||
isChildChat?: boolean;
|
||||
isArchivingThisChat?: boolean;
|
||||
isRegeneratingTitle?: boolean;
|
||||
|
||||
// Scroll container ref.
|
||||
scrollContainerRef: RefObject<HTMLDivElement | null>;
|
||||
@@ -370,7 +369,6 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
|
||||
isPinned,
|
||||
isChildChat,
|
||||
isArchivingThisChat,
|
||||
isRegeneratingTitle,
|
||||
scrollContainerRef,
|
||||
scrollToBottomRef,
|
||||
hasMoreMessages,
|
||||
@@ -842,7 +840,6 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
|
||||
isPinned={isPinned}
|
||||
isChildChat={isChildChat}
|
||||
isArchiving={isArchivingThisChat}
|
||||
isRegeneratingTitle={isRegeneratingTitle}
|
||||
hasWorkspace={Boolean(workspace)}
|
||||
isArchived={isArchived}
|
||||
diffStatusData={diffStatusData}
|
||||
|
||||
@@ -232,8 +232,6 @@ const AgentEmbedPage: FC = () => {
|
||||
requestArchiveAndDeleteWorkspace,
|
||||
isArchiving: false,
|
||||
archivingChatId: undefined,
|
||||
// Title regeneration is not supported in embed mode.
|
||||
regeneratingTitleChatIds: [],
|
||||
isSidebarCollapsed,
|
||||
onToggleSidebarCollapsed,
|
||||
onExpandSidebar: () => {},
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
prependToInfiniteChatsCache,
|
||||
proposeChatTitle,
|
||||
readInfiniteChatsCache,
|
||||
regenerateChatTitle,
|
||||
removeChildFromParentInCache,
|
||||
reorderPinnedChat,
|
||||
unarchiveChat,
|
||||
@@ -321,12 +320,6 @@ const AgentsPage: FC = () => {
|
||||
toast.error(getErrorMessage(error, "Failed to reorder pinned agents."));
|
||||
},
|
||||
});
|
||||
const regenerateTitleMutation = useMutation({
|
||||
...regenerateChatTitle(queryClient),
|
||||
onError: (error: unknown) => {
|
||||
toast.error(getErrorMessage(error, "Failed to generate new title."));
|
||||
},
|
||||
});
|
||||
const proposeTitleMutation = useMutation(proposeChatTitle(queryClient));
|
||||
const renameTitleMutation = useMutation({
|
||||
...updateChatTitle(queryClient),
|
||||
@@ -334,13 +327,6 @@ const AgentsPage: FC = () => {
|
||||
toast.error(getErrorMessage(error, "Failed to rename chat."));
|
||||
},
|
||||
});
|
||||
const regeneratingTitleChatIdsRef = useRef<ReadonlySet<string>>(new Set());
|
||||
const [regeneratingTitleChatIds, setRegeneratingTitleChatIds] = useState<
|
||||
readonly string[]
|
||||
>([]);
|
||||
const regeneratingTitlePromisesRef = useRef(
|
||||
new Map<string, Promise<string>>(),
|
||||
);
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
|
||||
const catalogModelOptions = getModelOptionsFromConfigs(
|
||||
chatModelConfigsQuery.data,
|
||||
@@ -489,48 +475,6 @@ const AgentsPage: FC = () => {
|
||||
const requestReorderPinnedAgent = (chatId: string, pinOrder: number) => {
|
||||
reorderPinnedChatMutation.mutate({ chatId, pinOrder });
|
||||
};
|
||||
const addRegeneratingTitleChatId = (chatId: string) => {
|
||||
if (!chatId || regeneratingTitleChatIdsRef.current.has(chatId)) {
|
||||
return false;
|
||||
}
|
||||
const next = new Set(regeneratingTitleChatIdsRef.current);
|
||||
next.add(chatId);
|
||||
regeneratingTitleChatIdsRef.current = next;
|
||||
setRegeneratingTitleChatIds(Array.from(next));
|
||||
return true;
|
||||
};
|
||||
const removeRegeneratingTitleChatId = (chatId: string) => {
|
||||
if (!regeneratingTitleChatIdsRef.current.has(chatId)) {
|
||||
return;
|
||||
}
|
||||
const next = new Set(regeneratingTitleChatIdsRef.current);
|
||||
next.delete(chatId);
|
||||
regeneratingTitleChatIdsRef.current = next;
|
||||
setRegeneratingTitleChatIds(Array.from(next));
|
||||
};
|
||||
const requestRegenerateTitle = (chatId: string): Promise<string> => {
|
||||
const existing = regeneratingTitlePromisesRef.current.get(chatId);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
addRegeneratingTitleChatId(chatId);
|
||||
const clearRegenerateTitleTracking = () => {
|
||||
regeneratingTitlePromisesRef.current.delete(chatId);
|
||||
removeRegeneratingTitleChatId(chatId);
|
||||
};
|
||||
const promise = regenerateTitleMutation.mutateAsync(chatId).then(
|
||||
(updated) => {
|
||||
clearRegenerateTitleTracking();
|
||||
return updated.title;
|
||||
},
|
||||
(error) => {
|
||||
clearRegenerateTitleTracking();
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
regeneratingTitlePromisesRef.current.set(chatId, promise);
|
||||
return promise;
|
||||
};
|
||||
const requestProposeTitle = async (chatId: string): Promise<string> => {
|
||||
const result = await proposeTitleMutation.mutateAsync(chatId);
|
||||
return result.title;
|
||||
@@ -743,10 +687,8 @@ const AgentsPage: FC = () => {
|
||||
requestPinAgent={requestPinAgent}
|
||||
requestUnpinAgent={requestUnpinAgent}
|
||||
requestReorderPinnedAgent={requestReorderPinnedAgent}
|
||||
onRegenerateTitle={requestRegenerateTitle}
|
||||
onProposeTitle={requestProposeTitle}
|
||||
onRenameTitle={requestRenameTitle}
|
||||
regeneratingTitleChatIds={regeneratingTitleChatIds}
|
||||
onToggleSidebarCollapsed={handleToggleSidebarCollapsed}
|
||||
isPersonalModelOverridesEnabled={
|
||||
personalModelOverridesQuery.data?.enabled
|
||||
|
||||
@@ -365,10 +365,8 @@ const defaultArgs: ComponentProps<typeof AgentsPageView> = {
|
||||
requestArchiveAndDeleteWorkspace: fn(),
|
||||
requestPinAgent: fn(),
|
||||
requestUnpinAgent: fn(),
|
||||
onRegenerateTitle: fn(async () => "Generated title"),
|
||||
onProposeTitle: fn(async () => "Proposed title"),
|
||||
onRenameTitle: fn(async () => {}),
|
||||
regeneratingTitleChatIds: [],
|
||||
onToggleSidebarCollapsed: fn(),
|
||||
isAgentsAdmin: false,
|
||||
sidebarFilters: defaultSidebarFilters,
|
||||
|
||||
@@ -28,10 +28,9 @@ export interface AgentsOutletContext {
|
||||
requestReorderPinnedAgent?: (chatId: string, pinOrder: number) => void;
|
||||
isArchiving: boolean;
|
||||
archivingChatId: string | undefined;
|
||||
onRegenerateTitle?: (chatId: string) => void;
|
||||
onRenameTitle?: (chatId: string, title: string) => Promise<void>;
|
||||
/** Opens the shared rename dialog so both menus drive the same instance. */
|
||||
onOpenRenameDialog?: (chat: TypesGen.Chat) => void;
|
||||
regeneratingTitleChatIds: readonly string[];
|
||||
isSidebarCollapsed: boolean;
|
||||
onToggleSidebarCollapsed: () => void;
|
||||
onExpandSidebar: () => void;
|
||||
@@ -70,10 +69,8 @@ interface AgentsPageViewProps {
|
||||
requestPinAgent: (chatId: string) => void;
|
||||
requestUnpinAgent: (chatId: string) => void;
|
||||
requestReorderPinnedAgent?: (chatId: string, pinOrder: number) => void;
|
||||
onRegenerateTitle: (chatId: string) => Promise<string>;
|
||||
onProposeTitle: (chatId: string) => Promise<string>;
|
||||
onRenameTitle: (chatId: string, title: string) => Promise<void>;
|
||||
regeneratingTitleChatIds: readonly string[];
|
||||
onToggleSidebarCollapsed: () => void;
|
||||
isPersonalModelOverridesEnabled?: boolean;
|
||||
isAgentsAdmin: boolean;
|
||||
@@ -111,10 +108,8 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
|
||||
requestPinAgent,
|
||||
requestUnpinAgent,
|
||||
requestReorderPinnedAgent,
|
||||
onRegenerateTitle,
|
||||
onProposeTitle,
|
||||
onRenameTitle,
|
||||
regeneratingTitleChatIds,
|
||||
onToggleSidebarCollapsed,
|
||||
isPersonalModelOverridesEnabled,
|
||||
isAgentsAdmin,
|
||||
@@ -145,6 +140,8 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
|
||||
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// State for the shared rename-chat dialog. Lifted here so both the
|
||||
// sidebar menu and the chat top bar open the same dialog instance.
|
||||
const [chatPendingRename, setChatPendingRename] =
|
||||
useState<TypesGen.Chat | null>(null);
|
||||
|
||||
@@ -160,11 +157,7 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
|
||||
requestReorderPinnedAgent,
|
||||
isArchiving,
|
||||
archivingChatId,
|
||||
onRegenerateTitle: (chatId: string) => {
|
||||
onRegenerateTitle(chatId).catch(() => {});
|
||||
},
|
||||
onOpenRenameDialog: setChatPendingRename,
|
||||
regeneratingTitleChatIds,
|
||||
isSidebarCollapsed,
|
||||
onToggleSidebarCollapsed,
|
||||
onExpandSidebar,
|
||||
@@ -205,7 +198,6 @@ export const AgentsPageView: FC<AgentsPageViewProps> = ({
|
||||
onProposeTitle={onProposeTitle}
|
||||
chatPendingRename={chatPendingRename}
|
||||
onChatPendingRenameChange={setChatPendingRename}
|
||||
regeneratingTitleChatIds={regeneratingTitleChatIds}
|
||||
onBeforeNewAgent={handleNewAgent}
|
||||
isSearchDialogOpen={isSearchDialogOpen}
|
||||
onSearchDialogOpenChange={onSearchDialogOpenChange}
|
||||
|
||||
@@ -41,13 +41,6 @@ type Story = StoryObj<typeof ChatTopBar>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const RegeneratingTitle: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
isRegeneratingTitle: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const SharedChat: Story = {
|
||||
args: {
|
||||
isSharedChat: true,
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "#/components/DropdownMenu/DropdownMenu";
|
||||
import { Popover, PopoverTrigger } from "#/components/Popover/Popover";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { parsePullRequestUrl } from "../utils/pullRequest";
|
||||
import { ChatActionsMenuItems } from "./ChatActionsMenuItems";
|
||||
@@ -47,7 +46,6 @@ type ChatTopBarProps = {
|
||||
onPinAgent?: () => void;
|
||||
onUnpinAgent?: () => void;
|
||||
onOpenRenameDialog?: () => void;
|
||||
isRegeneratingTitle?: boolean;
|
||||
hasWorkspace?: boolean;
|
||||
isArchived?: boolean;
|
||||
isArchiving?: boolean;
|
||||
@@ -103,7 +101,6 @@ export const ChatTopBar: FC<ChatTopBarProps> = ({
|
||||
onPinAgent,
|
||||
onUnpinAgent,
|
||||
onOpenRenameDialog,
|
||||
isRegeneratingTitle,
|
||||
hasWorkspace = false,
|
||||
isArchived = false,
|
||||
isArchiving = false,
|
||||
@@ -163,7 +160,6 @@ export const ChatTopBar: FC<ChatTopBarProps> = ({
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-busy={isRegeneratingTitle}
|
||||
className="flex min-w-0 items-center gap-1.5"
|
||||
>
|
||||
{parentChat && (
|
||||
@@ -186,12 +182,7 @@ export const ChatTopBar: FC<ChatTopBarProps> = ({
|
||||
<ChevronRightIcon className="size-3.5 shrink-0 text-content-secondary/70 -ml-0.5" />
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"truncate text-sm text-content-primary",
|
||||
isRegeneratingTitle && "animate-pulse",
|
||||
)}
|
||||
>
|
||||
<span className="truncate text-sm text-content-primary">
|
||||
{chatTitle}
|
||||
</span>
|
||||
{isSharedChat && (
|
||||
@@ -200,13 +191,6 @@ export const ChatTopBar: FC<ChatTopBarProps> = ({
|
||||
aria-label="Shared chat"
|
||||
/>
|
||||
)}
|
||||
{isRegeneratingTitle && (
|
||||
<Spinner
|
||||
aria-label="Regenerating title"
|
||||
className="h-3.5 w-3.5 shrink-0 text-content-secondary"
|
||||
loading
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* Actions menu sits inline with the title so it tracks the title's right edge.
|
||||
|
||||
@@ -104,7 +104,6 @@ const meta: Meta<typeof ChatsSidebar> = {
|
||||
isSearchDialogOpen: false,
|
||||
onSearchDialogOpenChange: fn(),
|
||||
isCreating: false,
|
||||
regeneratingTitleChatIds: [],
|
||||
currentUserId: MockUserOwner.id,
|
||||
sidebarFilters: defaultSidebarFilters,
|
||||
isPersonalModelOverridesEnabled: true,
|
||||
@@ -945,66 +944,6 @@ export const SearchDialogKeyboardShortcutHandlesRenameInput: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const RenameChatAvailableDuringRegeneration: Story = {
|
||||
args: {
|
||||
chats: [
|
||||
buildChat({
|
||||
id: "regenerating-chat",
|
||||
title: "Regenerating agent",
|
||||
updated_at: recentTimestamp,
|
||||
}),
|
||||
buildChat({
|
||||
id: "idle-chat",
|
||||
title: "Idle agent",
|
||||
updated_at: recentTimestamp,
|
||||
}),
|
||||
],
|
||||
regeneratingTitleChatIds: ["regenerating-chat"],
|
||||
onProposeTitle: fn(async () => "Proposed replacement"),
|
||||
onRenameTitle: fn(async () => {}),
|
||||
},
|
||||
parameters: {
|
||||
reactRouter: reactRouterParameters({
|
||||
location: { path: "/agents" },
|
||||
routing: agentsRouting,
|
||||
}),
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const body = within(document.body);
|
||||
|
||||
await expect(canvas.getByText("Regenerating agent")).toHaveAttribute(
|
||||
"aria-busy",
|
||||
"true",
|
||||
);
|
||||
|
||||
await userEvent.click(
|
||||
canvas.getByRole("button", {
|
||||
name: "Open actions for Regenerating agent",
|
||||
}),
|
||||
);
|
||||
await expect(
|
||||
await body.findByRole("menuitem", { name: "Rename chat" }),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await userEvent.keyboard("{Escape}");
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
body.queryByRole("menuitem", { name: "Rename chat" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
await userEvent.click(
|
||||
canvas.getByRole("button", {
|
||||
name: "Open actions for Idle agent",
|
||||
}),
|
||||
);
|
||||
await expect(
|
||||
await body.findByRole("menuitem", { name: "Rename chat" }),
|
||||
).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const RenameChatSubmitsNewTitle: Story = {
|
||||
args: {
|
||||
chats: [
|
||||
|
||||
@@ -111,7 +111,6 @@ const defaultProps: React.ComponentProps<typeof ChatsSidebar> = {
|
||||
onPinAgent: vi.fn(),
|
||||
onUnpinAgent: vi.fn(),
|
||||
onRenameTitle: vi.fn(async () => {}),
|
||||
regeneratingTitleChatIds: [],
|
||||
onBeforeNewAgent: vi.fn(),
|
||||
isSearchDialogOpen: false,
|
||||
onSearchDialogOpenChange: vi.fn(),
|
||||
|
||||
@@ -39,7 +39,6 @@ interface ChatsSidebarProps {
|
||||
isCreating: boolean;
|
||||
isArchiving?: boolean;
|
||||
archivingChatId?: string | null;
|
||||
regeneratingTitleChatIds: readonly string[];
|
||||
isLoading?: boolean;
|
||||
loadError?: unknown;
|
||||
onRetryLoad?: () => void;
|
||||
@@ -76,7 +75,6 @@ export const ChatsSidebar: FC<ChatsSidebarProps> = (props) => {
|
||||
isCreating,
|
||||
isArchiving = false,
|
||||
archivingChatId = null,
|
||||
regeneratingTitleChatIds,
|
||||
isLoading = false,
|
||||
loadError,
|
||||
onRetryLoad,
|
||||
@@ -139,7 +137,6 @@ export const ChatsSidebar: FC<ChatsSidebarProps> = (props) => {
|
||||
isCreating={isCreating}
|
||||
isArchiving={isArchiving}
|
||||
archivingChatId={archivingChatId}
|
||||
regeneratingTitleChatIds={regeneratingTitleChatIds}
|
||||
isLoading={isLoading}
|
||||
loadError={loadError}
|
||||
onRetryLoad={onRetryLoad}
|
||||
|
||||
@@ -85,7 +85,6 @@ interface ChatsPanelProps {
|
||||
readonly isCreating: boolean;
|
||||
readonly isArchiving: boolean;
|
||||
readonly archivingChatId: string | null;
|
||||
readonly regeneratingTitleChatIds: readonly string[];
|
||||
readonly isLoading: boolean;
|
||||
readonly loadError?: unknown;
|
||||
readonly onRetryLoad?: () => void;
|
||||
@@ -119,7 +118,6 @@ export const ChatsPanel: FC<ChatsPanelProps> = ({
|
||||
isCreating,
|
||||
isArchiving,
|
||||
archivingChatId,
|
||||
regeneratingTitleChatIds,
|
||||
isLoading,
|
||||
loadError,
|
||||
onRetryLoad,
|
||||
@@ -305,7 +303,6 @@ export const ChatsPanel: FC<ChatsPanelProps> = ({
|
||||
activeChatId,
|
||||
isArchiving,
|
||||
archivingChatId,
|
||||
regeneratingTitleChatIds,
|
||||
toggleExpanded,
|
||||
onArchiveAgent,
|
||||
onUnarchiveAgent,
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface ChatTreeContextValue {
|
||||
readonly activeChatId: string | undefined;
|
||||
readonly isArchiving: boolean;
|
||||
readonly archivingChatId: string | null;
|
||||
readonly regeneratingTitleChatIds: readonly string[];
|
||||
readonly toggleExpanded: (chatID: string) => void;
|
||||
readonly onArchiveAgent: (chatId: string) => void;
|
||||
readonly onUnarchiveAgent: (chatId: string) => void;
|
||||
|
||||
@@ -53,7 +53,6 @@ export const ChatTreeNode: FC<ChatTreeNodeProps> = ({ chat, isChildNode }) => {
|
||||
activeChatId,
|
||||
isArchiving,
|
||||
archivingChatId,
|
||||
regeneratingTitleChatIds,
|
||||
toggleExpanded,
|
||||
onArchiveAgent,
|
||||
onUnarchiveAgent,
|
||||
@@ -137,7 +136,6 @@ export const ChatTreeNode: FC<ChatTreeNodeProps> = ({ chat, isChildNode }) => {
|
||||
}`;
|
||||
const workspaceId = chat.workspace_id;
|
||||
const isArchivingThisChat = isArchiving && archivingChatId === chat.id;
|
||||
const isRegeneratingThisChat = regeneratingTitleChatIds.includes(chat.id);
|
||||
const isExpanded = normalizedSearch ? true : (expandedById[chatID] ?? false);
|
||||
|
||||
const sharedMenuItemProps = {
|
||||
@@ -224,11 +222,9 @@ export const ChatTreeNode: FC<ChatTreeNodeProps> = ({ chat, isChildNode }) => {
|
||||
<div className="min-w-0 flex-1 overflow-hidden text-left">
|
||||
<div className="flex min-w-0 items-center gap-1.5 overflow-hidden">
|
||||
<span
|
||||
aria-busy={isRegeneratingThisChat}
|
||||
className={cn(
|
||||
"block flex-1 truncate text-[13px] text-content-primary",
|
||||
isActive && "font-medium",
|
||||
isRegeneratingThisChat && "animate-pulse",
|
||||
)}
|
||||
>
|
||||
{chat.title}
|
||||
@@ -236,11 +232,6 @@ export const ChatTreeNode: FC<ChatTreeNodeProps> = ({ chat, isChildNode }) => {
|
||||
{chat.has_unread && !isActiveChat && (
|
||||
<span className="sr-only">(unread)</span>
|
||||
)}
|
||||
{isRegeneratingThisChat && (
|
||||
<span className="sr-only" role="status">
|
||||
Regenerating title…
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
{hasLinkedDiffStatus && hasLineStats && (
|
||||
|
||||
Reference in New Issue
Block a user