Fix sidebar session list responsiveness
This commit is contained in:
@@ -95,7 +95,7 @@ type ClawStoredToolCall = {
|
||||
|
||||
export const ThreadList: FC = () => {
|
||||
return (
|
||||
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex flex-col gap-1">
|
||||
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex min-h-0 flex-1 flex-col gap-1">
|
||||
<ThreadListNew />
|
||||
<ClawSessionList />
|
||||
</ThreadListPrimitive.Root>
|
||||
@@ -146,8 +146,7 @@ const ClawSessionList: FC = () => {
|
||||
fetch("/api/claw/sessions")
|
||||
.then((res) => (res.ok ? res.json() : []))
|
||||
.then((payload) => {
|
||||
if (Array.isArray(payload))
|
||||
setSessions(dedupeSessions(payload).slice(0, 8));
|
||||
if (Array.isArray(payload)) setSessions(dedupeSessions(payload));
|
||||
})
|
||||
.catch(() => setSessions([]));
|
||||
};
|
||||
@@ -204,154 +203,158 @@ const ClawSessionList: FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
{sessions.map((session) => {
|
||||
const active = activeSessionId === session.session_id;
|
||||
const loading = loadingSessionId === session.session_id;
|
||||
return (
|
||||
<div
|
||||
key={`claw-session-${session.session_id}-${session.modified_at}`}
|
||||
className="aui-thread-list-item group relative flex h-9 items-center gap-2 rounded-lg transition-colors hover:bg-muted data-[active=true]:bg-muted"
|
||||
data-active={active}
|
||||
>
|
||||
{renamingSessionId === session.session_id ? (
|
||||
<div className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center px-3">
|
||||
<input
|
||||
ref={renameInputRef}
|
||||
type="text"
|
||||
className="h-7 min-w-0 flex-1 rounded-md border bg-background px-2 text-sm outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
value={draftTitle}
|
||||
onChange={(event) => setDraftTitle(event.target.value)}
|
||||
onBlur={() => {
|
||||
saveTitle(
|
||||
session.session_id,
|
||||
session.preview || session.session_id,
|
||||
);
|
||||
}}
|
||||
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
setRenamingSessionId(null);
|
||||
setDraftTitle("");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center truncate px-3 text-start text-sm"
|
||||
onClick={async () => {
|
||||
setOpenMenuSessionId(null);
|
||||
window.localStorage.setItem(
|
||||
"claw.activeSessionId",
|
||||
session.session_id,
|
||||
);
|
||||
setActiveSessionId(session.session_id);
|
||||
setLoadingSessionId(session.session_id);
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/claw/sessions/${session.session_id}`,
|
||||
);
|
||||
if (!response.ok) return;
|
||||
const payload =
|
||||
(await response.json()) as ClawStoredSession;
|
||||
const runStatus = await fetchLatestRunStatus(
|
||||
session.session_id,
|
||||
);
|
||||
replaySession(
|
||||
session.session_id,
|
||||
toReplayRepository(
|
||||
payload,
|
||||
session.session_id,
|
||||
runStatus,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
setLoadingSessionId(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="truncate">
|
||||
{loading
|
||||
? "回放中..."
|
||||
: session.preview || session.session_id}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="aui-thread-list-item-more mr-2 flex size-7 shrink-0 items-center justify-center rounded-md p-0 text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover:opacity-100 data-[busy=true]:opacity-100 data-[open=true]:bg-accent data-[open=true]:opacity-100"
|
||||
data-busy={deletingSessionId === session.session_id}
|
||||
data-open={openMenuSessionId === session.session_id}
|
||||
aria-expanded={openMenuSessionId === session.session_id}
|
||||
aria-label="打开历史会话菜单"
|
||||
title="更多选项"
|
||||
onClick={() => {
|
||||
setOpenMenuSessionId((current) =>
|
||||
current === session.session_id ? null : session.session_id,
|
||||
);
|
||||
}}
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pr-1">
|
||||
<div className="flex flex-col gap-1">
|
||||
{sessions.map((session) => {
|
||||
const active = activeSessionId === session.session_id;
|
||||
const loading = loadingSessionId === session.session_id;
|
||||
return (
|
||||
<div
|
||||
key={`claw-session-${session.session_id}-${session.modified_at}`}
|
||||
className="aui-thread-list-item group relative flex h-9 items-center gap-2 rounded-lg transition-colors hover:bg-muted data-[active=true]:bg-muted"
|
||||
data-active={active}
|
||||
>
|
||||
<MoreHorizontalIcon className="size-4" />
|
||||
</button>
|
||||
{openMenuSessionId === session.session_id ? (
|
||||
<div className="aui-thread-list-item-more-content absolute top-8 right-1 z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md">
|
||||
{renamingSessionId === session.session_id ? (
|
||||
<div className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center px-3">
|
||||
<input
|
||||
ref={renameInputRef}
|
||||
type="text"
|
||||
className="h-7 min-w-0 flex-1 rounded-md border bg-background px-2 text-sm outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
value={draftTitle}
|
||||
onChange={(event) => setDraftTitle(event.target.value)}
|
||||
onBlur={() => {
|
||||
saveTitle(
|
||||
session.session_id,
|
||||
session.preview || session.session_id,
|
||||
);
|
||||
}}
|
||||
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
setRenamingSessionId(null);
|
||||
setDraftTitle("");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent focus:bg-accent"
|
||||
disabled={renamingSessionId === session.session_id}
|
||||
onClick={() => {
|
||||
setRenamingSessionId(session.session_id);
|
||||
setDraftTitle(session.preview || session.session_id);
|
||||
setOpenMenuSessionId(null);
|
||||
}}
|
||||
>
|
||||
<PencilIcon className="size-4" />
|
||||
重命名
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-destructive outline-none hover:bg-accent focus:bg-accent"
|
||||
disabled={deletingSessionId === session.session_id}
|
||||
className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center truncate px-3 text-start text-sm"
|
||||
onClick={async () => {
|
||||
setDeletingSessionId(session.session_id);
|
||||
setOpenMenuSessionId(null);
|
||||
window.localStorage.setItem(
|
||||
"claw.activeSessionId",
|
||||
session.session_id,
|
||||
);
|
||||
setActiveSessionId(session.session_id);
|
||||
setLoadingSessionId(session.session_id);
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/claw/sessions/${session.session_id}`,
|
||||
{ method: "DELETE" },
|
||||
);
|
||||
if (!response.ok) return;
|
||||
setSessions((current) =>
|
||||
current.filter(
|
||||
(item) => item.session_id !== session.session_id,
|
||||
const payload =
|
||||
(await response.json()) as ClawStoredSession;
|
||||
const runStatus = await fetchLatestRunStatus(
|
||||
session.session_id,
|
||||
);
|
||||
replaySession(
|
||||
session.session_id,
|
||||
toReplayRepository(
|
||||
payload,
|
||||
session.session_id,
|
||||
runStatus,
|
||||
),
|
||||
);
|
||||
setOpenMenuSessionId(null);
|
||||
if (activeSessionId === session.session_id) {
|
||||
window.localStorage.removeItem("claw.activeSessionId");
|
||||
setActiveSessionId(null);
|
||||
window.dispatchEvent(
|
||||
new Event("claw-active-session-cleared"),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setDeletingSessionId(null);
|
||||
setLoadingSessionId(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Trash2Icon className="size-4" />
|
||||
删除
|
||||
<span className="truncate">
|
||||
{loading
|
||||
? "回放中..."
|
||||
: session.preview || session.session_id}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="aui-thread-list-item-more mr-2 flex size-7 shrink-0 items-center justify-center rounded-md p-0 text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover:opacity-100 data-[busy=true]:opacity-100 data-[open=true]:bg-accent data-[open=true]:opacity-100"
|
||||
data-busy={deletingSessionId === session.session_id}
|
||||
data-open={openMenuSessionId === session.session_id}
|
||||
aria-expanded={openMenuSessionId === session.session_id}
|
||||
aria-label="打开历史会话菜单"
|
||||
title="更多选项"
|
||||
onClick={() => {
|
||||
setOpenMenuSessionId((current) =>
|
||||
current === session.session_id ? null : session.session_id,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<MoreHorizontalIcon className="size-4" />
|
||||
</button>
|
||||
{openMenuSessionId === session.session_id ? (
|
||||
<div className="aui-thread-list-item-more-content absolute top-8 right-1 z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md">
|
||||
<button
|
||||
type="button"
|
||||
className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent focus:bg-accent"
|
||||
disabled={renamingSessionId === session.session_id}
|
||||
onClick={() => {
|
||||
setRenamingSessionId(session.session_id);
|
||||
setDraftTitle(session.preview || session.session_id);
|
||||
setOpenMenuSessionId(null);
|
||||
}}
|
||||
>
|
||||
<PencilIcon className="size-4" />
|
||||
重命名
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-destructive outline-none hover:bg-accent focus:bg-accent"
|
||||
disabled={deletingSessionId === session.session_id}
|
||||
onClick={async () => {
|
||||
setDeletingSessionId(session.session_id);
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/claw/sessions/${session.session_id}`,
|
||||
{ method: "DELETE" },
|
||||
);
|
||||
if (!response.ok) return;
|
||||
setSessions((current) =>
|
||||
current.filter(
|
||||
(item) => item.session_id !== session.session_id,
|
||||
),
|
||||
);
|
||||
setOpenMenuSessionId(null);
|
||||
if (activeSessionId === session.session_id) {
|
||||
window.localStorage.removeItem(
|
||||
"claw.activeSessionId",
|
||||
);
|
||||
setActiveSessionId(null);
|
||||
window.dispatchEvent(
|
||||
new Event("claw-active-session-cleared"),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setDeletingSessionId(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Trash2Icon className="size-4" />
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user