Skip to content

Commit

Permalink
[extension] Use dropdown menu for conversation switching (#8913)
Browse files Browse the repository at this point in the history
* Use dropdown menu for conversation switching

* show selected conversation

* add chevron
  • Loading branch information
tdraier authored Nov 26, 2024
1 parent 5406742 commit 7de778e
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 267 deletions.
37 changes: 14 additions & 23 deletions extension/app/src/components/conversation/ConversationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ export function ConversationContainer({
user,
}: ConversationContainerProps) {
const navigate = useNavigate();
const [activeConversationId, setActiveConversationId] =
useState(conversationId);

const [includeContent, setIncludeContent] = useState<boolean | undefined>();

Expand All @@ -55,17 +53,15 @@ export function ConversationContainer({
return;
}
void setConversationsContext({
[activeConversationId ?? "new"]: {
[conversationId ?? "new"]: {
includeCurrentPage: includeContent,
},
});
}, [includeContent]);
}, [includeContent, conversationId]);

useEffect(() => {
const doAsync = async () => {
const context = await getConversationContext(
activeConversationId ?? "new"
);
const context = await getConversationContext(conversationId ?? "new");
setIncludeContent(context.includeCurrentPage);
};
void doAsync();
Expand All @@ -88,20 +84,12 @@ export function ConversationContainer({
}
});

useEffect(() => {
if (activeConversationId) {
navigate(`/conversations/${activeConversationId}`, {
replace: true,
});
}
}, [activeConversationId, navigate]);

const handlePostMessage = async (
input: string,
mentions: AgentMentionType[],
files: UploadedFileWithKind[]
) => {
if (!activeConversationId) {
if (!conversationId) {
return null;
}
const messageData = { input, mentions };
Expand All @@ -115,7 +103,7 @@ export function ConversationContainer({
// Get the content fragment ID to supersede for a given file.
// Only for tab contents, we re-use the content fragment ID based on the URL and conversation ID.
const supersededContentFragmentId: string | undefined =
(await getFileContentFragmentId(activeConversationId, file)) ??
(await getFileContentFragmentId(conversationId, file)) ??
undefined;

contentFragmentFiles.push({
Expand All @@ -128,7 +116,7 @@ export function ConversationContainer({

const result = await postMessage({
dustAPI,
conversationId: activeConversationId,
conversationId,
messageData,
files: contentFragmentFiles,
});
Expand All @@ -138,7 +126,7 @@ export function ConversationContainer({

// Save content fragment IDs for tab contents to the local storage.
await saveFilesContentFragmentIds({
conversationId: activeConversationId,
conversationId,
uploadedFiles: files,
createdContentFragments: contentFragments,
});
Expand Down Expand Up @@ -237,10 +225,13 @@ export function ConversationContainer({
},
new: { includeCurrentPage: false },
});
setActiveConversationId(conversationRes.value.sId);

navigate(`/conversations/${conversationRes.value.sId}`, {
replace: true,
});
}
},
[owner, sendNotification, setActiveConversationId, includeContent]
[owner, sendNotification, includeContent]
)
);

Expand All @@ -256,13 +247,13 @@ export function ConversationContainer({
setGreeting(getRandomGreetingForName(user.firstName));
}, [user]);

if (activeConversationId) {
if (conversationId) {
return (
<GenerationContextProvider>
<div className="h-full flex flex-col">
<div className="flex-1">
<ConversationViewer
conversationId={activeConversationId}
conversationId={conversationId}
owner={owner}
user={user}
onStickyMentionsChange={onStickyMentionsChange}
Expand Down
132 changes: 122 additions & 10 deletions extension/app/src/components/conversation/ConversationsListButton.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,131 @@
import { Button, ChatBubbleLeftRightIcon } from "@dust-tt/sparkle";
import { useNavigate } from "react-router-dom";
import type { ConversationWithoutContentPublicType } from "@dust-tt/client";
import {
Button,
ChatBubbleLeftRightIcon,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
ScrollArea,
Spinner,
} from "@dust-tt/sparkle";
import { useConversations } from "@extension/components/conversation/useConversations";
import moment from "moment";
import { useNavigate, useParams } from "react-router-dom";

type GroupLabel =
| "Today"
| "Yesterday"
| "Last Week"
| "Last Month"
| "Last 12 Months"
| "Older";

const Content = () => {
const { conversations, isConversationsLoading } = useConversations();
const { conversationId } = useParams();

const navigate = useNavigate();

const groupConversationsByDate = (
conversations: ConversationWithoutContentPublicType[]
) => {
const today = moment().startOf("day");
const yesterday = moment().subtract(1, "days").startOf("day");
const lastWeek = moment().subtract(1, "weeks").startOf("day");
const lastMonth = moment().subtract(1, "months").startOf("day");
const lastYear = moment().subtract(1, "years").startOf("day");

type GroupLabel =
| "Today"
| "Yesterday"
| "Last Week"
| "Last Month"
| "Last 12 Months"
| "Older";

const groups: Record<GroupLabel, ConversationWithoutContentPublicType[]> = {
Today: [],
Yesterday: [],
"Last Week": [],
"Last Month": [],
"Last 12 Months": [],
Older: [],
};

conversations.forEach((conversation) => {
const createdDate = moment(conversation.created);
if (createdDate.isSameOrAfter(today)) {
groups["Today"].push(conversation);
} else if (createdDate.isSameOrAfter(yesterday)) {
groups["Yesterday"].push(conversation);
} else if (createdDate.isSameOrAfter(lastWeek)) {
groups["Last Week"].push(conversation);
} else if (createdDate.isSameOrAfter(lastMonth)) {
groups["Last Month"].push(conversation);
} else if (createdDate.isSameOrAfter(lastYear)) {
groups["Last 12 Months"].push(conversation);
} else {
groups["Older"].push(conversation);
}
});

return groups;
};

const conversationsByDate = conversations.length
? groupConversationsByDate(conversations)
: ({} as Record<GroupLabel, ConversationWithoutContentPublicType[]>);

return isConversationsLoading ? (
<div className="flex items-center justify-center m-4">
<Spinner variant="dark" size="xs" />
</div>
) : (
<ScrollArea className="h-[80vh]">
{Object.keys(conversationsByDate).map((dateLabel) => (
<>
<DropdownMenuLabel label={dateLabel} />
{conversationsByDate[dateLabel as GroupLabel].map((conversation) => (
<DropdownMenuItem
key={conversation.sId}
label={conversation.title || "Untitled Conversation"}
className={
conversationId === conversation.sId
? "bg-primary-50"
: undefined
}
onClick={() => {
navigate(`/conversations/${conversation.sId}`);
}}
/>
))}
</>
))}
</ScrollArea>
);
};

export const ConversationsListButton = ({
size = "sm",
}: {
size?: "sm" | "md";
}) => {
const navigate = useNavigate();
return (
<Button
tooltip="View conversations"
icon={ChatBubbleLeftRightIcon}
variant="ghost"
onClick={() => navigate("/conversations")}
size={size}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
tooltip="View conversations"
icon={ChatBubbleLeftRightIcon}
isSelect
variant="ghost"
size={size}
/>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[80vw]">
<Content />
</DropdownMenuContent>
</DropdownMenu>
);
};
31 changes: 12 additions & 19 deletions extension/app/src/pages/ConversationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,20 @@ import {
} from "@dust-tt/sparkle";
import type { ProtectedRouteChildrenProps } from "@extension/components/auth/ProtectedRoute";
import { ConversationContainer } from "@extension/components/conversation/ConversationContainer";
import { ConversationsListButton } from "@extension/components/conversation/ConversationsListButton";
import { FileDropProvider } from "@extension/components/conversation/FileUploaderContext";
import { usePublicConversation } from "@extension/components/conversation/usePublicConversation";
import { InputBarProvider } from "@extension/components/input_bar/InputBarContext";
import { useEffect, useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";

export const ConversationPage = ({
workspace,
user,
}: ProtectedRouteChildrenProps) => {
const navigate = useNavigate();
const location = useLocation();
const { conversationId } = useParams();

const [origin, setOrigin] = useState<string | null>(null);

useEffect(() => {
if (location.state?.origin) {
setOrigin(location.state.origin);
}
}, [location.state?.origin]);

const { conversation } = usePublicConversation({
const { conversation, isConversationLoading } = usePublicConversation({
conversationId: conversationId ?? null,
});

Expand All @@ -37,27 +28,29 @@ export const ConversationPage = ({
return;
}

const title = isConversationLoading
? "..."
: conversation?.title || "Conversation";

return (
<FileDropProvider>
<BarHeader
title={conversation?.title || "Conversation"}
tooltip={conversation?.title || "Conversation"}
title={title}
tooltip={title}
leftActions={
<Button
icon={ChevronLeftIcon}
variant="ghost"
onClick={() => {
if (origin === "conversations") {
navigate("/conversations");
} else {
navigate("/");
}
navigate("/");
}}
size="md"
/>
}
rightActions={
<div className="flex flex-row items-right">
<ConversationsListButton size="md" />

<Button
icon={ExternalLinkIcon}
variant="ghost"
Expand Down
Loading

0 comments on commit 7de778e

Please sign in to comment.