mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Scope thread activity pills.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
c7b698a0fe
commit
18dfbff379
@@ -102,6 +102,7 @@ type ChannelPaneProps = {
|
||||
openThreadHeadId: string | null;
|
||||
openAgentSessionPubkey: string | null;
|
||||
threadHeadMessage: TimelineMessage | null;
|
||||
threadBotTypingPubkeys: string[];
|
||||
threadMessages: MainTimelineEntry[];
|
||||
threadTypingPubkeys: string[];
|
||||
threadReplyTargetId: string | null;
|
||||
@@ -148,6 +149,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
openAgentSessionPubkey,
|
||||
targetMessageId,
|
||||
threadHeadMessage,
|
||||
threadBotTypingPubkeys,
|
||||
threadMessages,
|
||||
threadScrollTargetId,
|
||||
threadTypingPubkeys,
|
||||
@@ -373,6 +375,17 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
replyTargetId={threadReplyTargetId}
|
||||
replyTargetMessage={threadReplyTargetMessage}
|
||||
scrollTargetId={threadScrollTargetId}
|
||||
threadActivity={
|
||||
threadBotTypingPubkeys.length > 0 ? (
|
||||
<BotActivityBar
|
||||
agents={agentSessionAgents}
|
||||
onOpenAgentSession={onOpenAgentSession}
|
||||
openAgentSessionPubkey={openAgentSessionPubkey}
|
||||
profiles={profiles}
|
||||
typingBotPubkeys={threadBotTypingPubkeys}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
canResetWidth={canResetThreadPanelWidth}
|
||||
onResetWidth={handleThreadPanelWidthReset}
|
||||
onResizeStart={handleThreadPanelResizeStart}
|
||||
|
||||
@@ -140,7 +140,7 @@ export function ChannelScreen({
|
||||
.map((entry) => entry.pubkey),
|
||||
[typingEntries],
|
||||
);
|
||||
const threadTypingPubkeys = React.useMemo(
|
||||
const allThreadTypingPubkeys = React.useMemo(
|
||||
() =>
|
||||
typingEntries
|
||||
.filter((entry) => entry.threadHeadId === openThreadHeadId)
|
||||
@@ -161,7 +161,12 @@ export function ChannelScreen({
|
||||
});
|
||||
const managedAgentsQuery = useManagedAgentsQuery();
|
||||
useManagedAgentObserverBridge(managedAgentsQuery.data ?? []);
|
||||
const { humanTypingPubkeys, botTypingPubkeys } = React.useMemo(() => {
|
||||
const {
|
||||
humanTypingPubkeys,
|
||||
botTypingPubkeys,
|
||||
threadTypingPubkeys,
|
||||
threadBotTypingPubkeys,
|
||||
} = React.useMemo(() => {
|
||||
const localAgentSet = new Set(
|
||||
(managedAgentsQuery.data ?? [])
|
||||
.filter((agent) => agent.backend.type === "local")
|
||||
@@ -173,13 +178,21 @@ export function ChannelScreen({
|
||||
),
|
||||
botTypingPubkeys: [
|
||||
...new Set(
|
||||
typingEntries
|
||||
.map((entry) => entry.pubkey)
|
||||
.filter((pk) => localAgentSet.has(pk.toLowerCase())),
|
||||
mainTypingPubkeys.filter((pk) => localAgentSet.has(pk.toLowerCase())),
|
||||
),
|
||||
],
|
||||
threadTypingPubkeys: allThreadTypingPubkeys.filter(
|
||||
(pk) => !localAgentSet.has(pk.toLowerCase()),
|
||||
),
|
||||
threadBotTypingPubkeys: [
|
||||
...new Set(
|
||||
allThreadTypingPubkeys.filter((pk) =>
|
||||
localAgentSet.has(pk.toLowerCase()),
|
||||
),
|
||||
),
|
||||
],
|
||||
};
|
||||
}, [mainTypingPubkeys, managedAgentsQuery.data, typingEntries]);
|
||||
}, [allThreadTypingPubkeys, mainTypingPubkeys, managedAgentsQuery.data]);
|
||||
const messageProfiles = React.useMemo(() => {
|
||||
const base =
|
||||
mergeCurrentProfileIntoLookup(
|
||||
@@ -471,6 +484,7 @@ export function ChannelScreen({
|
||||
profiles={messageProfiles}
|
||||
targetMessageId={targetMessageId}
|
||||
threadHeadMessage={openThreadHeadMessage}
|
||||
threadBotTypingPubkeys={threadBotTypingPubkeys}
|
||||
threadMessages={threadMessages}
|
||||
threadTypingPubkeys={threadTypingPubkeys}
|
||||
threadReplyTargetId={threadReplyTargetId}
|
||||
|
||||
@@ -54,12 +54,20 @@ type MessageThreadPanelProps = {
|
||||
replyTargetId: string | null;
|
||||
replyTargetMessage: TimelineMessage | null;
|
||||
scrollTargetId: string | null;
|
||||
threadActivity?: React.ReactNode;
|
||||
threadHead: TimelineMessage | null;
|
||||
threadReplies: MainTimelineEntry[];
|
||||
threadTypingPubkeys: string[];
|
||||
widthPx: number;
|
||||
};
|
||||
|
||||
const THREAD_TEXT_COLUMN_OFFSET_PX = 8;
|
||||
|
||||
function getThreadControlOffsetPx(depth = 0): number {
|
||||
const visibleDepth = Math.min(Math.max(depth, 0), 6);
|
||||
return visibleDepth * 28 + THREAD_TEXT_COLUMN_OFFSET_PX;
|
||||
}
|
||||
|
||||
function canManageMessage(
|
||||
message: TimelineMessage,
|
||||
currentPubkey: string | undefined,
|
||||
@@ -97,6 +105,7 @@ export function MessageThreadPanel({
|
||||
replyTargetId,
|
||||
replyTargetMessage,
|
||||
scrollTargetId,
|
||||
threadActivity,
|
||||
threadHead,
|
||||
threadReplies,
|
||||
threadTypingPubkeys,
|
||||
@@ -223,6 +232,11 @@ export function MessageThreadPanel({
|
||||
const nextDepth =
|
||||
threadReplies[index + 1]?.message.depth ?? -1;
|
||||
const isExpanded = nextDepth > entry.message.depth;
|
||||
const isLastEntry = index === threadReplies.length - 1;
|
||||
const showSummary = Boolean(entry.summary && !isExpanded);
|
||||
const showThreadActivity = Boolean(
|
||||
threadActivity && isLastEntry,
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={entry.message.id}>
|
||||
@@ -246,13 +260,23 @@ export function MessageThreadPanel({
|
||||
onToggleReaction={onToggleReaction}
|
||||
profiles={profiles}
|
||||
/>
|
||||
{entry.summary && !isExpanded ? (
|
||||
<MessageThreadSummaryRow
|
||||
depth={entry.message.depth}
|
||||
message={entry.message}
|
||||
onOpenThread={onExpandReplies}
|
||||
summary={entry.summary}
|
||||
/>
|
||||
{showSummary || showThreadActivity ? (
|
||||
<div
|
||||
className="mt-1 flex min-w-0 items-start gap-1.5"
|
||||
style={{
|
||||
marginLeft: `${getThreadControlOffsetPx(entry.message.depth)}px`,
|
||||
}}
|
||||
>
|
||||
{entry.summary && showSummary ? (
|
||||
<MessageThreadSummaryRow
|
||||
alignWithText={false}
|
||||
message={entry.message}
|
||||
onOpenThread={onExpandReplies}
|
||||
summary={entry.summary}
|
||||
/>
|
||||
) : null}
|
||||
{showThreadActivity ? threadActivity : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
@@ -268,6 +292,15 @@ export function MessageThreadPanel({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{threadActivity && threadReplies.length === 0 ? (
|
||||
<div
|
||||
className="mt-2 flex min-w-0 justify-start"
|
||||
data-testid="message-thread-activity"
|
||||
style={{ marginLeft: `${THREAD_TEXT_COLUMN_OFFSET_PX}px` }}
|
||||
>
|
||||
{threadActivity}
|
||||
</div>
|
||||
) : null}
|
||||
<div aria-hidden className="h-px" ref={bottomAnchorRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,16 +34,17 @@ export function MessageThreadSummaryRow({
|
||||
message,
|
||||
onOpenThread,
|
||||
summary,
|
||||
textColumnOffsetPx = 60,
|
||||
}: {
|
||||
alignWithText?: boolean;
|
||||
depth?: number;
|
||||
message: TimelineMessage;
|
||||
onOpenThread: (message: TimelineMessage) => void;
|
||||
summary: TimelineThreadSummary;
|
||||
textColumnOffsetPx?: number;
|
||||
}) {
|
||||
const visibleDepth = Math.min(Math.max(depth, 0), 6);
|
||||
const messageTextOffsetPx = 60;
|
||||
const marginLeftPx = visibleDepth * 28 + messageTextOffsetPx;
|
||||
const marginLeftPx = visibleDepth * 28 + textColumnOffsetPx;
|
||||
const depthGuideOffsets = Array.from(
|
||||
{ length: visibleDepth },
|
||||
(_, index) => 14 + index * 28,
|
||||
|
||||
Reference in New Issue
Block a user