Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 86 additions & 22 deletions desktop/src/app/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { useAppShellDesktopNotifications } from "@/app/useAppShellDesktopNotific
import { useThreadActivityFeedItems } from "@/app/useThreadActivityFeedItems";
import { useTauriWindowDrag } from "@/app/useTauriWindowDrag";
import { useWebviewZoomShortcuts } from "@/app/useWebviewZoomShortcuts";
import { AgentConversationScreen } from "@/features/agents/ui/AgentConversationScreen";
import { useAgentConversationShellState } from "@/features/agents/useAgentConversationShellState";
import {
channelsQueryKey,
useChannelsQuery,
Expand Down Expand Up @@ -133,14 +135,13 @@ export function AppShell() {
const startupReady = useDeferredStartup();

const identityQuery = useIdentityQuery();
const { mutedChannelIds, muteChannel, unmuteChannel } = useChannelMutes(
identityQuery.data?.pubkey,
);
const { starredChannelIds, starChannel, unstarChannel } = useChannelStars(
identityQuery.data?.pubkey,
);
usePersonaSync(identityQuery.data?.pubkey);
useAgentsDataRefresh();
const currentPubkey = identityQuery.data?.pubkey;
const { mutedChannelIds, muteChannel, unmuteChannel } =
useChannelMutes(currentPubkey);
const { starredChannelIds, starChannel, unstarChannel } =
useChannelStars(currentPubkey);
usePersonaSync(currentPubkey);
const profileQuery = useProfileQuery();
const deferredPubkey = startupReady ? identityQuery.data?.pubkey : undefined;
useRelayAutoHeal();
Expand Down Expand Up @@ -196,6 +197,26 @@ export function AppShell() {
? (channels.find((channel) => channel.id === targetChannelId) ?? null)
: null;
}, [channels, managedChannelId, selectedChannelId]);
const {
agentConversations,
backToAgentConversationThread: handleBackToAgentConversationThread,
clearSelectedAgentConversation,
hideAgentConversation: handleHideAgentConversation,
openAgentConversation: handleOpenAgentConversation,
selectAgentConversation: handleSelectAgentConversation,
selectedAgentConversation,
selectedAgentConversationChannel,
selectedAgentConversationId,
updateAgentConversationTitle: handleUpdateAgentConversationTitle,
visibleAgentConversations,
} = useAgentConversationShellState({
channels,
currentPubkey,
goAgents,
goChannel,
selectedView,
workspaceScope: workspacesHook.activeWorkspace?.relayUrl ?? null,
});

const {
handleChannelNotification,
Expand Down Expand Up @@ -421,9 +442,17 @@ export function AppShell() {

const handleOpenSearchResult = React.useCallback(
(hit: SearchHit) => {
clearSelectedAgentConversation();
void openSearchHit(hit);
},
[openSearchHit],
[clearSelectedAgentConversation, openSearchHit],
);
const handleSelectChannel = React.useCallback(
(channelId: string) => {
clearSelectedAgentConversation();
void goChannel(channelId);
},
[clearSelectedAgentConversation, goChannel],
);

// Prevent webview file:/// navigation on file drop outside the composer.
Expand Down Expand Up @@ -569,12 +598,12 @@ export function AppShell() {
<ChannelNavigationProvider channels={channels}>
<AppShellProvider
value={{
agentConversations: [],
agentConversations,
markAllChannelsRead,
markChannelRead,
markChannelUnread,
openAgentConversation: () => {},
updateAgentConversationTitle: () => {},
openAgentConversation: handleOpenAgentConversation,
updateAgentConversationTitle: handleUpdateAgentConversationTitle,
openCreateChannel: handleOpenCreateChannel,
openChannelManagement: (channelId?: string) => {
setManagedChannelId(
Expand Down Expand Up @@ -666,6 +695,7 @@ export function AppShell() {
<div className="flex min-h-0 flex-1 overflow-hidden">
<AppSidebar
activeWorkspace={workspacesHook.activeWorkspace}
agentConversations={visibleAgentConversations}
channels={sidebarChannels}
currentPubkey={identityQuery.data?.pubkey}
errorMessage={channelsErrorMessage}
Expand All @@ -685,6 +715,7 @@ export function AppShell() {
}}
onAddWorkspaceOpenChange={setIsAddWorkspaceOpen}
onNewDmOpenChange={setIsNewDmOpen}
onHideAgentConversation={handleHideAgentConversation}
onCreateChannelOpenChange={setIsCreateChannelOpen}
onOpenAddWorkspace={() => setIsAddWorkspaceOpen(true)}
onUpdateWorkspace={workspacesHook.updateWorkspace}
Expand Down Expand Up @@ -716,6 +747,7 @@ export function AppShell() {
createdChannel.id,
name,
);
clearSelectedAgentConversation();
await goChannel(createdChannel.id);
void applyAgents(templateId, createdChannel.id);
}}
Expand All @@ -740,6 +772,7 @@ export function AppShell() {
createdForum.id,
name,
);
clearSelectedAgentConversation();
await goChannel(createdForum.id);
void applyAgents(templateId, createdForum.id);
}}
Expand All @@ -753,20 +786,37 @@ export function AppShell() {
await openDmMutation.mutateAsync({
pubkeys,
});
clearSelectedAgentConversation();
await goChannel(directMessage.id);
}}
onSelectAgents={() => void goAgents()}
onSelectChannel={(channelId) =>
void goChannel(channelId)
onSelectAgentConversation={
handleSelectAgentConversation
}
onSelectAgents={() => {
clearSelectedAgentConversation();
void goAgents();
}}
onSelectChannel={handleSelectChannel}
onOpenSearchResult={handleOpenSearchResult}
searchChannels={channels}
searchFocusRequest={searchFocusRequest}
onSelectHome={() => void goHome()}
onSelectProjects={() => void goProjects()}
onSelectPulse={() => void goPulse()}
onSelectHome={() => {
clearSelectedAgentConversation();
void goHome();
}}
onSelectProjects={() => {
clearSelectedAgentConversation();
void goProjects();
}}
onSelectPulse={() => {
clearSelectedAgentConversation();
void goPulse();
}}
onSelectSettings={handleOpenSettings}
onSelectWorkflows={() => void goWorkflows()}
onSelectWorkflows={() => {
clearSelectedAgentConversation();
void goWorkflows();
}}
onSetPresenceStatus={(status) =>
presenceSession.setStatus(status)
}
Expand All @@ -788,6 +838,9 @@ export function AppShell() {
: undefined
}
selectedChannelId={selectedChannelId}
selectedAgentConversationId={
selectedAgentConversationId
}
selectedView={selectedView}
unreadChannelIds={unreadChannelIds}
unreadChannelCounts={unreadChannelCounts}
Expand All @@ -808,7 +861,19 @@ export function AppShell() {
<ConnectionBanner
errorMessage={channelsErrorMessage}
/>
<Outlet />
{selectedAgentConversation ? (
<AgentConversationScreen
channel={selectedAgentConversationChannel}
conversation={selectedAgentConversation}
currentIdentity={identityQuery.data}
currentProfile={profileQuery.data}
onBackToThread={
handleBackToAgentConversationThread
}
/>
) : (
<Outlet />
)}
</div>
</SidebarInset>
</MainInsetProvider>
Expand All @@ -831,11 +896,10 @@ export function AppShell() {
onDeleteActiveChannel={() => {
setIsChannelManagementOpen(false);
setManagedChannelId(null);
clearSelectedAgentConversation();
void goHome({ replace: true });
}}
onSelectChannel={(channelId) => {
void goChannel(channelId);
}}
onSelectChannel={handleSelectChannel}
/>
</SidebarProvider>
</div>
Expand Down
61 changes: 60 additions & 1 deletion desktop/src/features/channels/ui/ChannelScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { cacheSearchHitEvent } from "@/app/navigation/searchHitEventCache";
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
import { useActiveChannelHeader } from "@/features/channels/useActiveChannelHeader";
import { useChannelPaneHandlers } from "@/features/channels/useChannelPaneHandlers";
import {
buildAgentConversationMarkers,
getHiddenAgentConversationMessageIds,
} from "@/features/agents/agentConversations";
import {
useChannelMembersQuery,
useJoinChannelMutation,
Expand All @@ -18,6 +22,10 @@ import {
ChannelPane,
ForumView,
} from "@/features/channels/ui/ChannelScreenLazyViews";
import {
getDmAutoRouteAgentPubkeys,
getThreadAutoRouteAgentPubkeys,
} from "@/features/channels/ui/ChannelPane.helpers";
import { MembersSidebar } from "@/features/channels/ui/MembersSidebar";
import {
useManagedAgentsQuery,
Expand Down Expand Up @@ -437,6 +445,23 @@ export function ChannelScreen({
: [...currentEvents, event],
);
}, []);
const agentConversationMarkers = React.useMemo(
() => buildAgentConversationMarkers(resolvedMessages),
[resolvedMessages],
);
const unreadTimelineMessages = React.useMemo(() => {
const hiddenMessageIds = getHiddenAgentConversationMessageIds(
timelineMessages,
agentConversationMarkers,
);
if (hiddenMessageIds.size === 0) {
return timelineMessages;
}

return timelineMessages.filter(
(message) => !hiddenMessageIds.has(message.id),
);
}, [agentConversationMarkers, timelineMessages]);
const channelFind = useChannelFind({
channelId: activeChannelId,
messages: timelineMessages,
Expand All @@ -459,7 +484,7 @@ export function ChannelScreen({
unreadCount,
} = useChannelUnreadState({
activeChannelId,
timelineMessages,
timelineMessages: unreadTimelineMessages,
currentPubkey,
openThreadHeadId,
threadReplyTargetId,
Expand All @@ -476,6 +501,37 @@ export function ChannelScreen({
timelineMessages.find((message) => message.id === editTargetId) ?? null,
[editTargetId, timelineMessages],
);
const routingAgentPubkeys = React.useMemo(() => {
const pubkeys = new Set(agentPubkeys);
for (const [pubkey, profile] of Object.entries(messageProfiles)) {
if (profile?.isAgent) {
pubkeys.add(normalizePubkey(pubkey));
}
}
return pubkeys;
}, [agentPubkeys, messageProfiles]);
const messageAutoRouteAgentPubkeys = React.useMemo(
() =>
getDmAutoRouteAgentPubkeys({
channel: activeChannel,
currentPubkey,
knownAgentPubkeys: routingAgentPubkeys,
}),
[activeChannel, currentPubkey, routingAgentPubkeys],
);
const threadAutoRouteAgentPubkeys = React.useMemo(() => {
if (!openThreadHeadMessage) {
return [];
}

return getThreadAutoRouteAgentPubkeys({
knownAgentPubkeys: routingAgentPubkeys,
messages: [
openThreadHeadMessage,
...threadMessages.map((entry) => entry.message),
],
});
}, [openThreadHeadMessage, routingAgentPubkeys, threadMessages]);
const {
handleCancelEdit,
handleCancelThreadReply,
Expand All @@ -493,6 +549,7 @@ export function ChannelScreen({
deleteMessageMutation,
editMessageMutation,
editTargetId,
messageAutoRouteAgentPubkeys,
expandedThreadReplyIds,
getFirstReplyIdForMessage,
getReplyDescendantIdsForMessage,
Expand All @@ -505,6 +562,7 @@ export function ChannelScreen({
setOpenThreadHeadId,
setThreadReplyTargetId,
setThreadScrollTargetId,
threadAutoRouteAgentPubkeys,
threadReplyTargetId,
toggleReactionMutation,
});
Expand Down Expand Up @@ -841,6 +899,7 @@ export function ChannelScreen({
<ChannelPane
activeChannel={activeChannel}
activityAgents={channelAgentSessionAgents}
agentConversationMarkers={agentConversationMarkers}
agentPubkeys={agentPubkeys}
agentPubkeysPending={agentPubkeysPending}
agentSessionAgents={agentSessionAgents}
Expand Down
Loading
Loading