feat(webui): add lightweight session messaging via mentions

This commit is contained in:
chengyongru
2026-08-19 01:15:56 +08:00
committed by chengyongru
parent 2bdb11eeba
commit 0e184965e8
76 changed files with 8297 additions and 658 deletions
+38 -12
View File
@@ -50,6 +50,7 @@ import {
} from "@/components/ui/tooltip";
import { MAX_WORKBENCH_PANES } from "@/components/workbench/workbench-model";
import { SIDEBAR_SELECTION_ITEM_CLASS } from "@/components/SidebarSelectionHighlight";
import { SessionHandleHighlight, sessionHandleColor } from "@/components/CliAppMentionText";
import { deriveTitle, relativeTime, visibleSessionPreview } from "@/lib/format";
import {
COLLAPSED_CHATS_VISIBLE_COUNT,
@@ -103,8 +104,10 @@ function SidebarItemTooltip({
function SidebarSelectionTrack({
active,
handle,
}: {
active: boolean;
handle: ChatSummary["handle"];
}) {
return (
<span
@@ -112,14 +115,31 @@ function SidebarSelectionTrack({
data-active={active ? "true" : "false"}
aria-hidden
className={cn(
"pointer-events-none absolute inset-x-0 bottom-0 h-0.5 origin-left rounded-full bg-current",
"pointer-events-none absolute inset-x-0 bottom-0 h-0.5 origin-left rounded-full",
"transition-transform duration-200 ease-out motion-reduce:transition-none",
active ? "scale-x-100" : "scale-x-0",
)}
style={{
backgroundColor: handle ? sessionHandleColor(handle.color_slot) : "currentColor",
}}
/>
);
}
function SidebarSessionHandle({ handle }: { handle: ChatSummary["handle"] }) {
if (!handle) return null;
return (
<span
data-sidebar-handle-handle
className="flex max-w-20 shrink-0 items-center overflow-hidden whitespace-nowrap text-[11px] font-medium leading-5"
>
<SessionHandleHighlight handle={handle}>
@{handle.name}
</SessionHandleHighlight>
</span>
);
}
function readCollapsedPaneGroups(): Set<string> {
try {
const value = JSON.parse(window.localStorage.getItem(
@@ -160,6 +180,7 @@ export interface SidebarPaneGroup {
key: string;
chatId: string;
title: string;
handle?: ChatSummary["handle"];
}>;
}
@@ -965,27 +986,29 @@ export const ChatList = memo(function ChatList({
partial={tabPartiallySelected}
/>
) : null}
<span className="min-w-0 flex-1 overflow-hidden">
{projectMode ? (
<span className="relative flex w-full min-w-0 items-baseline gap-2">
<span className="min-w-0 flex-1 truncate font-medium leading-5">
{title}
</span>
{isPinned ? <PinnedChatIndicator /> : null}
<span className="min-w-0 flex-1 overflow-hidden">
{projectMode ? (
<span className="relative flex w-full min-w-0 items-baseline gap-2">
<SidebarSessionHandle handle={s.handle} />
<span className="min-w-0 flex-1 truncate font-medium leading-5">
{title}
</span>
{isPinned ? <PinnedChatIndicator /> : null}
{timestamp ? (
<span className="shrink-0 text-[11.5px] font-medium text-muted-foreground/58">
{timestamp}
</span>
) : null}
<SidebarSelectionTrack active={topicActive} />
<SidebarSelectionTrack active={topicActive} handle={s.handle} />
</span>
) : (
<span className="relative flex w-full min-w-0 items-center gap-1.5">
<SidebarSessionHandle handle={s.handle} />
<span className="min-w-0 flex-1 truncate font-medium leading-5">
{title}
</span>
{isPinned ? <PinnedChatIndicator /> : null}
<SidebarSelectionTrack active={topicActive} />
<SidebarSelectionTrack active={topicActive} handle={s.handle} />
</span>
)}
{showPreview ? (
@@ -1405,7 +1428,9 @@ function ActivePaneRows({
&& "bg-sidebar-accent/55 text-sidebar-accent-foreground",
)}
>
<SidebarItemTooltip label={pane.title}>
<SidebarItemTooltip
label={pane.handle ? `@${pane.handle.name} · ${pane.title}` : pane.title}
>
<button
type="button"
onClick={(event) => {
@@ -1437,9 +1462,10 @@ function ActivePaneRows({
<SelectionIndicator checked={selected} partial={false} />
) : null}
<span className="relative flex min-w-0 flex-1 items-center gap-2 overflow-hidden">
<SidebarSessionHandle handle={pane.handle} />
<span className="min-w-0 flex-1 truncate">{pane.title}</span>
{isPinned ? <PinnedChatIndicator /> : null}
<SidebarSelectionTrack active={active} />
<SidebarSelectionTrack active={active} handle={pane.handle} />
</span>
</button>
</SidebarItemTooltip>