Skip to content

Commit

Permalink
Feedbacks for builders (#9110)
Browse files Browse the repository at this point in the history
* Add info/feedback tab

* Committing performance preview

* Skeleton is here, need to bring the configurations history endpoint

* Now fetching config history, added conv button need a way to get the conv url

* Can get conversationsId in the feedback card

* Fixed link, reordered feedbacks

* Applied Pr comments

* Auth done in endpoint

* Modified feedback popover to be more transpoarent

* Using fetchByModelId

* Use spinner instead of ? avatar

* Now auto-scrolling to messages, fixed tab resetting to info

* Fixed sparkle referencing local repo in package.json

* Conversation will be shared: warning

---------

Co-authored-by: Lucas <[email protected]>
Co-authored-by: Lucas Massemin <[email protected]>
  • Loading branch information
3 people authored Dec 10, 2024
1 parent c41d0c8 commit f726c72
Show file tree
Hide file tree
Showing 13 changed files with 684 additions and 33 deletions.
237 changes: 227 additions & 10 deletions front/components/assistant/AssistantDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
import {
Avatar,
Button,
ContentMessage,
ElementModal,
HandThumbDownIcon,
HandThumbUpIcon,
InformationCircleIcon,
Page,
Spinner,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@dust-tt/sparkle";
import type { AgentConfigurationScope, WorkspaceType } from "@dust-tt/types";
import { useCallback, useState } from "react";
import type {
AgentConfigurationScope,
LightAgentConfigurationType,
LightWorkspaceType,
WorkspaceType,
} from "@dust-tt/types";
import { ExternalLinkIcon } from "lucide-react";
import { useCallback, useMemo, useState } from "react";

import { AssistantDetailsButtonBar } from "@app/components/assistant/AssistantDetailsButtonBar";
import { AssistantActionsSection } from "@app/components/assistant/details/AssistantActionsSection";
import { AssistantUsageSection } from "@app/components/assistant/details/AssistantUsageSection";
import { ReadOnlyTextArea } from "@app/components/assistant/ReadOnlyTextArea";
import { SharingDropdown } from "@app/components/assistant_builder/Sharing";
import type { AgentMessageFeedbackType } from "@app/lib/api/assistant/feedback";
import {
useAgentConfiguration,
useAgentConfigurationFeedbacks,
useAgentConfigurationHistory,
useUpdateAgentScope,
} from "@app/lib/swr/assistants";
import { classNames } from "@app/lib/utils";
import { useFeedbackConversationContext } from "@app/lib/swr/feedbacks";
import { useUserDetails } from "@app/lib/swr/user";
import { classNames, timeAgoFrom } from "@app/lib/utils";

type AssistantDetailsProps = {
owner: WorkspaceType;
Expand All @@ -31,6 +50,7 @@ export function AssistantDetails({
owner,
}: AssistantDetailsProps) {
const [isUpdatingScope, setIsUpdatingScope] = useState(false);
const [activeTab, setActiveTab] = useState("info");

const { agentConfiguration } = useAgentConfiguration({
workspaceId: owner.sId,
Expand All @@ -55,7 +75,7 @@ export function AssistantDetails({
return <></>;
}

const DescriptionSection = () => (
const TopSection = () => (
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-3 sm:flex-row">
<Avatar
Expand All @@ -82,6 +102,30 @@ export function AssistantDetails({
)}
</div>
</div>
</div>
);

const TabsSection = () => (
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="info" label="Info" icon={InformationCircleIcon} />
<TabsTrigger
value="performance"
label="Performance"
icon={HandThumbUpIcon}
/>
</TabsList>
<TabsContent value="info">
<InfoSection />
</TabsContent>
<TabsContent value="performance">
<FeedbacksSection />
</TabsContent>
</Tabs>
);

const InfoSection = () => (
<div className="mt-2 flex flex-col gap-5">
{agentConfiguration.status === "active" && (
<AssistantDetailsButtonBar
owner={owner}
Expand All @@ -108,6 +152,11 @@ export function AssistantDetails({
owner={owner}
/>
<Page.Separator />
<AssistantActionsSection
agentConfiguration={agentConfiguration}
owner={owner}
/>
<InstructionsSection />
</div>
);

Expand All @@ -121,6 +170,70 @@ export function AssistantDetails({
"This assistant has no instructions."
);

const FeedbacksSection = () => {
const {
agentConfigurationFeedbacks,
isAgentConfigurationFeedbacksLoading,
} = useAgentConfigurationFeedbacks({
workspaceId: owner.sId,
agentConfigurationId: assistantId ?? "",
});

const sortedFeedbacks = useMemo(() => {
if (!agentConfigurationFeedbacks) {
return null;
}
return agentConfigurationFeedbacks.sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
}, [agentConfigurationFeedbacks]);

const { agentConfigurationHistory, isAgentConfigurationHistoryLoading } =
useAgentConfigurationHistory({
workspaceId: owner.sId,
agentConfigurationId: assistantId || "",
disabled: !assistantId,
});

return isAgentConfigurationFeedbacksLoading ||
isAgentConfigurationHistoryLoading ? (
<Spinner />
) : (
<div>
{!sortedFeedbacks || sortedFeedbacks.length === 0 || !assistantId ? (
<div className="mt-3 text-sm text-element-900">No feedbacks.</div>
) : (
<div className="mt-3">
<AgentConfigurationVersionHeader
agentConfiguration={agentConfiguration}
agentConfigurationVersion={agentConfiguration.version}
isLatestVersion={true}
/>
{sortedFeedbacks.map((feedback, index) => (
<div key={feedback.id}>
{index > 0 &&
feedback.agentConfigurationVersion !==
sortedFeedbacks[index - 1].agentConfigurationVersion && (
<AgentConfigurationVersionHeader
agentConfiguration={agentConfigurationHistory?.find(
(c) => c.version === feedback.agentConfigurationVersion
)}
agentConfigurationVersion={
feedback.agentConfigurationVersion
}
isLatestVersion={false}
/>
)}
<FeedbackCard owner={owner} feedback={feedback} />
</div>
))}
</div>
)}
</div>
);
};

return (
<ElementModal
openOnElement={agentConfiguration}
Expand All @@ -130,13 +243,117 @@ export function AssistantDetails({
variant="side-sm"
>
<div className="flex flex-col gap-5 pt-6 text-sm text-foreground">
<DescriptionSection />
<AssistantActionsSection
agentConfiguration={agentConfiguration}
owner={owner}
/>
<InstructionsSection />
<TopSection />
<TabsSection />
</div>
</ElementModal>
);
}

function AgentConfigurationVersionHeader({
agentConfigurationVersion,
agentConfiguration,
isLatestVersion,
}: {
agentConfigurationVersion: number;
agentConfiguration: LightAgentConfigurationType | undefined;
isLatestVersion: boolean;
}) {
const getAgentConfigurationVersionString = useCallback(
(config: LightAgentConfigurationType) => {
return isLatestVersion
? "Latest Version"
: !config.versionCreatedAt
? `v${config.version}`
: new Date(config.versionCreatedAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
},
[isLatestVersion]
);

return (
<div className="flex items-center gap-2">
<Page.H variant="h6">
{agentConfiguration
? getAgentConfigurationVersionString(agentConfiguration)
: `v${agentConfigurationVersion}`}
</Page.H>
</div>
);
}

function FeedbackCard({
owner,
feedback,
}: {
owner: LightWorkspaceType;
feedback: AgentMessageFeedbackType;
}) {
const { userDetails } = useUserDetails(feedback.userId);
const { conversationContext } = useFeedbackConversationContext({
workspaceId: owner.sId,
feedbackId: feedback.id.toString(),
});
const conversationUrl = conversationContext
? `${process.env.NEXT_PUBLIC_DUST_CLIENT_FACING_URL}/w/${owner.sId}/assistant/${conversationContext.conversationId}#${conversationContext.messageId}`
: null;

return (
<ContentMessage variant="slate" className="my-2">
<div className="justify-content-around mb-3 flex items-center gap-2">
<div className="flex w-full items-center gap-2">
{userDetails?.image ? (
<Avatar
size="xs"
visual={userDetails?.image}
name={userDetails?.firstName || "?"}
/>
) : (
<Spinner size="xs" />
)}
{userDetails?.firstName} {userDetails?.lastName}
</div>
<div className="flex-shrink-0 text-xs text-muted-foreground">
{timeAgoFrom(
feedback.createdAt instanceof Date
? feedback.createdAt.getTime()
: new Date(feedback.createdAt).getTime(),
{
useLongFormat: true,
}
)}{" "}
ago
</div>
</div>
<div className="flex items-center gap-2">
<div className="flex-grow">{feedback.content}</div>
<div className="flex-shrink-0">
{feedback.thumbDirection === "up" ? (
<button className="rounded bg-sky-200 p-2">
<HandThumbUpIcon />
</button>
) : (
<button className="rounded bg-warning-200 p-2">
<HandThumbDownIcon />
</button>
)}
</div>
</div>
{conversationContext && conversationUrl && (
<div className="mt-2">
<Button
variant="outline"
size="xs"
href={conversationUrl}
label="Conversation"
icon={ExternalLinkIcon}
target="_blank"
/>
</div>
)}
</ContentMessage>
);
}
35 changes: 21 additions & 14 deletions front/components/assistant/conversation/AgentMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,20 +126,27 @@ export const FeedbackSelectorPopoverContent = ({

return (
agentLastAuthor && (
<div className="itemcenter mt-4 flex gap-2">
{agentLastAuthor?.image && (
<img
src={agentLastAuthor?.image}
alt={agentLastAuthor?.firstName}
className="h-8 w-8 rounded-full"
/>
)}
<Page.P variant="secondary">
Your feedback will be sent to:
<br />
{agentLastAuthor?.firstName} {agentLastAuthor?.lastName}
</Page.P>
</div>
<>
<div className="itemcenter mt-4 flex gap-2">
{agentLastAuthor?.image && (
<img
src={agentLastAuthor?.image}
alt={agentLastAuthor?.firstName}
className="h-8 w-8 rounded-full"
/>
)}
<Page.P variant="secondary">
Your feedback will be sent to:
<br />
{agentLastAuthor?.firstName} {agentLastAuthor?.lastName}
</Page.P>
</div>
<div className="mt-2 text-xs text-muted-foreground">
<Page.P variant="secondary">
Your full conversation with the assistant will be shared.
</Page.P>
</div>
</>
)
);
};
Expand Down
Loading

0 comments on commit f726c72

Please sign in to comment.