Skip to content

Commit

Permalink
Extension: post a conversation (#8217)
Browse files Browse the repository at this point in the history
  • Loading branch information
PopDaph authored Oct 24, 2024
1 parent 5e491a4 commit abfd44f
Show file tree
Hide file tree
Showing 15 changed files with 434 additions and 123 deletions.
17 changes: 3 additions & 14 deletions front/components/assistant/conversation/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
InternalPostConversationsRequestBodySchema,
MentionType,
Result,
SubmitMessageError,
UserMessageWithRankType,
UserType,
WorkspaceType,
Expand All @@ -25,16 +26,6 @@ export const CONVERSATION_PARENT_SCROLL_DIV_ID = {
page: "main-content",
};

export type ConversationErrorType = {
type:
| "attachment_upload_error"
| "message_send_error"
| "plan_limit_reached_error"
| "content_too_large";
title: string;
message: string;
};

export type ContentFragmentInput = {
title: string;
content: string;
Expand Down Expand Up @@ -91,9 +82,7 @@ export async function submitMessage({
mentions: MentionType[];
contentFragments: UploadedContentFragment[];
};
}): Promise<
Result<{ message: UserMessageWithRankType }, ConversationErrorType>
> {
}): Promise<Result<{ message: UserMessageWithRankType }, SubmitMessageError>> {
const { input, mentions, contentFragments } = messageData;
// Create a new content fragment.
if (contentFragments.length > 0) {
Expand Down Expand Up @@ -219,7 +208,7 @@ export async function createConversationWithMessage({
};
visibility?: ConversationVisibility;
title?: string;
}): Promise<Result<ConversationType, ConversationErrorType>> {
}): Promise<Result<ConversationType, SubmitMessageError>> {
const { input, mentions, contentFragments } = messageData;

const body: t.TypeOf<typeof InternalPostConversationsRequestBodySchema> = {
Expand Down
138 changes: 61 additions & 77 deletions front/extension/app/src/components/assistants/AssistantPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { filterAndSortAgents } from "@app/extension/app/src/lib/utils";
import { DropdownMenu, Item, RobotIcon, Searchbar } from "@dust-tt/sparkle";
import {
Button,
Item,
PopoverContent,
PopoverRoot,
PopoverTrigger,
RobotIcon,
ScrollArea,
Searchbar,
} from "@dust-tt/sparkle";
import type {
LightAgentConfigurationType,
WorkspaceType,
Expand All @@ -16,7 +25,7 @@ export function AssistantPicker({
assistants: LightAgentConfigurationType[];
onItemClick: (assistant: LightAgentConfigurationType) => void;
pickerButton?: React.ReactNode;
size?: "sm" | "md";
size?: "xs" | "sm" | "md";
}) {
const [searchText, setSearchText] = useState("");
const [searchedAssistants, setSearchedAssistants] = useState<
Expand All @@ -29,89 +38,64 @@ export function AssistantPicker({

const searchbarRef = (element: HTMLInputElement) => {
if (element) {
// it turned out that the events are not properly propagated, leading
// to a conflict with the InputBarContainer a hack around it is
// adding a small timeout
setTimeout(() => {
element.focus();
}, 200);
element.focus();
}
};

return (
// TODO(2024-10-09 jules): use Popover when new Button has been released
<DropdownMenu>
{({ close }) => (
<PopoverRoot>
<PopoverTrigger>
<>
<div onClick={() => setSearchText("")} className="flex">
{pickerButton ? (
<DropdownMenu.Button size={size}>
{pickerButton}
</DropdownMenu.Button>
) : (
<DropdownMenu.Button
icon={RobotIcon}
size={size}
{pickerButton ? (
pickerButton
) : (
<Button
icon={RobotIcon}
variant="ghost"
isSelect
size={size}
tooltip="Pick an assistant"
/>
)}
</>
</PopoverTrigger>
<PopoverContent className="mr-2 p-2">
<Searchbar
ref={searchbarRef}
placeholder="Search"
name="input"
size="xs"
value={searchText}
onChange={setSearchText}
onKeyDown={(e) => {
if (e.key === "Enter" && searchedAssistants.length > 0) {
onItemClick(searchedAssistants[0]);
setSearchText("");
close();
}
}}
/>
<ScrollArea className="mt-2 h-[300px]">
{searchedAssistants.map((c) => (
<div
key={`assistant-picker-container-${c.sId}`}
className="flex flex-row items-center justify-between px-2"
>
<Item.Avatar
key={`assistant-picker-${c.sId}`}
label={c.name}
visual={c.pictureUrl}
hasAction={false}
onClick={() => {
onItemClick(c);
setSearchText("");
}}
tooltip="Pick an assistant"
tooltipPosition="top"
className="truncate"
/>
)}
</div>
<DropdownMenu.Items
variant="no-padding"
origin="auto"
width={280}
topBar={
<>
{assistants.length > 7 && (
<div className="flex flex-grow flex-row border-b border-structure-50 p-2">
<Searchbar
ref={searchbarRef}
placeholder="Search"
name="input"
size="xs"
value={searchText}
onChange={setSearchText}
onKeyDown={(e) => {
if (
e.key === "Enter" &&
searchedAssistants.length > 0
) {
onItemClick(searchedAssistants[0]);
setSearchText("");
close();
}
}}
/>
</div>
)}
</>
}
>
{searchedAssistants.map((c) => (
<div
key={`assistant-picker-container-${c.sId}`}
className="flex flex-row items-center justify-between px-4"
>
<Item.Avatar
key={`assistant-picker-${c.sId}`}
label={c.name}
visual={c.pictureUrl}
hasAction={false}
onClick={() => {
onItemClick(c);
setSearchText("");
}}
className="truncate"
/>
</div>
))}
</DropdownMenu.Items>
</>
)}
</DropdownMenu>
</div>
))}
</ScrollArea>
</PopoverContent>
</PopoverRoot>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { ReachedLimitPopup } from "@app/extension/app/src/components/conversation/ReachedLimitPopup";
import { AssistantInputBar } from "@app/extension/app/src/components/input_bar/InputBar";
import { InputBarContext } from "@app/extension/app/src/components/input_bar/InputBarContext";
import { useSubmitFunction } from "@app/extension/app/src/components/utils/useSubmitFunction";
import {
postConversation,
postMessage,
} from "@app/extension/app/src/lib/conversation";
import type { MentionType, WorkspaceType } from "@dust-tt/types";
import { useCallback, useContext, useEffect, useState } from "react";

interface ConversationContainerProps {
conversationId: string | null;
owner: WorkspaceType;
}

export function ConversationContainer({
conversationId,
owner,
}: ConversationContainerProps) {
const [activeConversationId, setActiveConversationId] =
useState(conversationId);
const [planLimitReached, setPlanLimitReached] = useState(false);

const { animate, setAnimate } = useContext(InputBarContext);

// TODO use notification once they are in Sparkle.
// const sendNotification = useContext(SendNotificationsContext);
const sendNotification = console.log;

useEffect(() => {
if (animate) {
setTimeout(() => setAnimate(false), 500);
}
});

const handlePostMessage = async (input: string, mentions: MentionType[]) => {
if (!activeConversationId) {
return null;
}
const messageData = { input, mentions, contentFragments: [] };
const result = await postMessage({
owner,
conversationId: activeConversationId,
messageData,
});

if (result.isErr()) {
if (result.error.type === "plan_limit_reached_error") {
setPlanLimitReached(true);
} else {
sendNotification({
title: result.error.title,
description: result.error.message,
type: "error",
});
}
} else {
// TODO (Ext): Handle the message being posted.
}
};

const { submit: handlePostConversation } = useSubmitFunction(
useCallback(
async (input: string, mentions: MentionType[]) => {
const conversationRes = await postConversation({
owner,
messageData: {
input,
mentions,
},
});
if (conversationRes.isErr()) {
if (conversationRes.error.type === "plan_limit_reached_error") {
setPlanLimitReached(true);
} else {
sendNotification({
title: conversationRes.error.title,
description: conversationRes.error.message,
type: "error",
});
}
} else {
setActiveConversationId(conversationRes.value.sId);
// Probably here we want to navigate to /conversations/id
// navigate(`/conversations/${conversationRes.value.sId}`);
}
},
[owner, sendNotification, setActiveConversationId]
)
);

return (
<>
{activeConversationId && <p>Congrats you just posted a conversation</p>}
<AssistantInputBar
owner={owner}
onSubmit={
activeConversationId ? handlePostMessage : handlePostConversation
}
stickyMentions={[]} //TODO(Ext) do we need this.
/>
<ReachedLimitPopup
isOpened={planLimitReached}
onClose={() => setPlanLimitReached(false)}
isTrialing={false} // TODO(Ext): Properly handle this from loading the subscription.
/>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Dialog, Page } from "@dust-tt/sparkle";

export function ReachedLimitPopup({
isOpened,
onClose,
isTrialing,
}: {
isOpened: boolean;
onClose: () => void;
isTrialing: boolean;
}) {
// TODO(ext): put a link to subscription page.
if (isTrialing) {
return (
<Dialog
title="Fair usage limit reached"
isOpen={isOpened}
onValidate={onClose}
onCancel={onClose}
validateLabel="Ok"
>
<Page.P>
We limit usage of Dust during the trial. You've reached your limit for
today.
</Page.P>
<p className="text-sm font-normal text-element-800">
Come back tomorrow for a fresh start or{" "}
<span className="font-bold">
end your trial and start paying now (using our website).
</span>
</p>
</Dialog>
);
}

// TODO(ext): put a link to fair use policy (modal).
return (
<Dialog
title="Message quota exceeded"
isOpen={isOpened}
onValidate={onClose}
onCancel={onClose}
validateLabel="Ok"
>
<p className="text-sm font-normal text-element-800">
We've paused messaging for your workspace due to our fair usage policy.
Your workspace has reached its shared limit of 100 messages per user for
the past 24 hours. This total limit is collectively shared by all users
in the workspace. Check our Fair Use policy on our website to learn
more.
</p>
</Dialog>
);
}
4 changes: 2 additions & 2 deletions front/extension/app/src/components/input_bar/InputBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function AssistantInputBar({
onSubmit: (input: string, mentions: MentionType[]) => void;
stickyMentions?: AgentMention[];
additionalAgentConfiguration?: LightAgentConfigurationType;
disableAutoFocus: boolean;
disableAutoFocus?: boolean;
isFloating?: boolean;
isFloatingWithoutMargin?: boolean;
}) {
Expand Down Expand Up @@ -138,7 +138,7 @@ export function AssistantInputBar({
"border-struture-200 border-t bg-white/90 backdrop-blur focus-within:border-structure-300",
"transition-all",
isFloating
? "sm:rounded-3xl sm:border-b sm:border-l sm:border-r sm:border-element-500 sm:focus-within:border-action-300 sm:focus-within:shadow-md sm:focus-within:ring-1"
? "sm:rounded-2xl sm:border-b sm:border-l sm:border-r sm:border-element-500 sm:focus-within:border-action-300 sm:focus-within:shadow-md sm:focus-within:ring-1"
: "",
isAnimating ? "duration-600 animate-shake" : "duration-300"
)}
Expand Down
Loading

0 comments on commit abfd44f

Please sign in to comment.