({
projectSlug,
agentSlug,
sessions,
activeSessionId,
}: Props)
| 98 | } |
| 99 | |
| 100 | export function ThreadSelector({ |
| 101 | projectSlug, |
| 102 | agentSlug, |
| 103 | sessions, |
| 104 | activeSessionId, |
| 105 | }: Props) { |
| 106 | const router = useRouter(); |
| 107 | const [pending, start] = useTransition(); |
| 108 | const active = sessions.find((s) => s.sessionId === activeSessionId); |
| 109 | |
| 110 | function go(sessionId: string) { |
| 111 | if (sessionId === activeSessionId) return; |
| 112 | start(() => |
| 113 | router.push(projectHref(projectSlug, `/agents/${agentSlug}/chat/${sessionId}`)), |
| 114 | ); |
| 115 | } |
| 116 | |
| 117 | function newThread() { |
| 118 | const id = crypto.randomUUID(); |
| 119 | start(() => |
| 120 | router.push(projectHref(projectSlug, `/agents/${agentSlug}/chat/${id}`)), |
| 121 | ); |
| 122 | } |
| 123 | |
| 124 | return ( |
| 125 | <DropdownMenu> |
| 126 | <DropdownMenuTrigger asChild> |
| 127 | <Button |
| 128 | variant="outline" |
| 129 | size="sm" |
| 130 | className="min-w-[200px] justify-between" |
| 131 | disabled={pending} |
| 132 | > |
| 133 | <span className="truncate text-left"> |
| 134 | {active ? displayTitle(active) : "Pick a thread"} |
| 135 | </span> |
| 136 | <ChevronsUpDown className="ml-2 size-3.5 shrink-0 text-muted-foreground" /> |
| 137 | </Button> |
| 138 | </DropdownMenuTrigger> |
| 139 | <DropdownMenuContent align="end" className="w-72"> |
| 140 | <DropdownMenuLabel className="text-xs text-muted-foreground"> |
| 141 | Threads ({sessions.length}) · from OpenClaw |
| 142 | </DropdownMenuLabel> |
| 143 | <DropdownMenuSeparator /> |
| 144 | {sessions.length === 0 && ( |
| 145 | <DropdownMenuItem disabled>No threads yet</DropdownMenuItem> |
| 146 | )} |
| 147 | {(() => { |
| 148 | const groups = groupBySection(sessions); |
| 149 | const present = SECTION_ORDER.filter((k) => (groups.get(k)?.length ?? 0) > 0); |
| 150 | return present.map((key, idx) => { |
| 151 | const rows = groups.get(key)!; |
| 152 | return ( |
| 153 | <div key={key}> |
| 154 | {idx > 0 && <DropdownMenuSeparator />} |
| 155 | <DropdownMenuLabel className="text-[10px] uppercase tracking-wide text-muted-foreground"> |
| 156 | {SECTION_TITLES[key]} ({rows.length}) |
| 157 | </DropdownMenuLabel> |
nothing calls this directly
no test coverage detected