From f726c723cc6ee24276d92f7ef0c8f778bd8ff628 Mon Sep 17 00:00:00 2001 From: Alban Dumouilla Date: Tue, 10 Dec 2024 11:54:02 +0100 Subject: [PATCH] Feedbacks for builders (#9110) * 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 Co-authored-by: Lucas Massemin --- .../components/assistant/AssistantDetails.tsx | 237 +++++++++++++++++- .../assistant/conversation/AgentMessage.tsx | 35 +-- .../assistant/conversation/MessageItem.tsx | 51 +++- front/lib/api/assistant/feedback.ts | 26 ++ front/lib/models/assistant/conversation.ts | 2 + .../agent_message_feedback_resource.ts | 65 ++++- front/lib/swr/assistants.ts | 27 ++ front/lib/swr/feedbacks.ts | 27 ++ front/lib/swr/user.ts | 15 ++ front/pages/api/user/[uId]/details/index.ts | 70 ++++++ .../agent_configurations/[aId]/feedbacks.ts | 50 ++++ .../feedbacks/[fId]/conversation-context.ts | 108 ++++++++ types/src/front/lib/error.ts | 4 +- 13 files changed, 684 insertions(+), 33 deletions(-) create mode 100644 front/lib/swr/feedbacks.ts create mode 100644 front/pages/api/user/[uId]/details/index.ts create mode 100644 front/pages/api/w/[wId]/assistant/agent_configurations/[aId]/feedbacks.ts create mode 100644 front/pages/api/w/[wId]/assistant/feedbacks/[fId]/conversation-context.ts diff --git a/front/components/assistant/AssistantDetails.tsx b/front/components/assistant/AssistantDetails.tsx index b853a294cf53..a282d0240d02 100644 --- a/front/components/assistant/AssistantDetails.tsx +++ b/front/components/assistant/AssistantDetails.tsx @@ -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; @@ -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, @@ -55,7 +75,7 @@ export function AssistantDetails({ return <>; } - const DescriptionSection = () => ( + const TopSection = () => (
+
+ ); + + const TabsSection = () => ( + + + + + + + + + + + + + ); + + const InfoSection = () => ( +
{agentConfiguration.status === "active" && ( + +
); @@ -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 ? ( + + ) : ( +
+ {!sortedFeedbacks || sortedFeedbacks.length === 0 || !assistantId ? ( +
No feedbacks.
+ ) : ( +
+ + {sortedFeedbacks.map((feedback, index) => ( +
+ {index > 0 && + feedback.agentConfigurationVersion !== + sortedFeedbacks[index - 1].agentConfigurationVersion && ( + c.version === feedback.agentConfigurationVersion + )} + agentConfigurationVersion={ + feedback.agentConfigurationVersion + } + isLatestVersion={false} + /> + )} + +
+ ))} +
+ )} +
+ ); + }; + return (
- - - + +
); } + +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 ( +
+ + {agentConfiguration + ? getAgentConfigurationVersionString(agentConfiguration) + : `v${agentConfigurationVersion}`} + +
+ ); +} + +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 ( + +
+
+ {userDetails?.image ? ( + + ) : ( + + )} + {userDetails?.firstName} {userDetails?.lastName} +
+
+ {timeAgoFrom( + feedback.createdAt instanceof Date + ? feedback.createdAt.getTime() + : new Date(feedback.createdAt).getTime(), + { + useLongFormat: true, + } + )}{" "} + ago +
+
+
+
{feedback.content}
+
+ {feedback.thumbDirection === "up" ? ( + + ) : ( + + )} +
+
+ {conversationContext && conversationUrl && ( +
+
+ )} +
+ ); +} diff --git a/front/components/assistant/conversation/AgentMessage.tsx b/front/components/assistant/conversation/AgentMessage.tsx index 3c969244a3e0..6511a13f6219 100644 --- a/front/components/assistant/conversation/AgentMessage.tsx +++ b/front/components/assistant/conversation/AgentMessage.tsx @@ -126,20 +126,27 @@ export const FeedbackSelectorPopoverContent = ({ return ( agentLastAuthor && ( -
- {agentLastAuthor?.image && ( - {agentLastAuthor?.firstName} - )} - - Your feedback will be sent to: -
- {agentLastAuthor?.firstName} {agentLastAuthor?.lastName} -
-
+ <> +
+ {agentLastAuthor?.image && ( + {agentLastAuthor?.firstName} + )} + + Your feedback will be sent to: +
+ {agentLastAuthor?.firstName} {agentLastAuthor?.lastName} +
+
+
+ + Your full conversation with the assistant will be shared. + +
+ ) ); }; diff --git a/front/components/assistant/conversation/MessageItem.tsx b/front/components/assistant/conversation/MessageItem.tsx index 14172841d871..73bf6defb59e 100644 --- a/front/components/assistant/conversation/MessageItem.tsx +++ b/front/components/assistant/conversation/MessageItem.tsx @@ -15,7 +15,8 @@ import type { UserType, WorkspaceType, } from "@dust-tt/types"; -import React from "react"; +import { useRouter } from "next/router"; +import React, { useEffect, useState } from "react"; import { useSWRConfig } from "swr"; import { AgentMessage } from "@app/components/assistant/conversation/AgentMessage"; @@ -90,10 +91,6 @@ const MessageItem = React.forwardRef( } ); - if (message.visibility === "deleted") { - return null; - } - const messageFeedbackWithSubmit: FeedbackSelectorProps = { feedback: messageFeedback ? { @@ -105,6 +102,36 @@ const MessageItem = React.forwardRef( isSubmittingThumb, }; + const router = useRouter(); + const [hasScrolledToMessage, setHasScrolledToMessage] = useState(false); + const [highlighted, setHighlighted] = useState(false); + + // Effect: scroll to the message and temporarily highlight if it is the anchor's target + useEffect(() => { + if (!router.asPath.includes("#")) { + return; + } + const messageIdToScrollTo = router.asPath.split("#")[1]; + if (messageIdToScrollTo === sId && !hasScrolledToMessage) { + setTimeout(() => { + setHasScrolledToMessage(true); + document + .getElementById(`message-id-${sId}`) + ?.scrollIntoView({ behavior: "smooth", block: "center" }); + setHighlighted(true); + + // Highlight the message for a short time + setTimeout(() => { + setHighlighted(false); + }, 2000); + }, 100); + } + }, [hasScrolledToMessage, router.asPath, sId, ref]); + + if (message.visibility === "deleted") { + return null; + } + switch (type) { case "user_message": const citations = message.contenFragments @@ -153,7 +180,12 @@ const MessageItem = React.forwardRef( : undefined; return ( -
+
( case "agent_message": return ( -
+
> { + const feedbacksRes = + await AgentMessageFeedbackResource.fetchByAgentConfigurationId( + agentConfigurationId + ); + + const feedbacks = feedbacksRes.map( + (feedback) => + ({ + id: feedback.id, + userId: feedback.userId, + thumbDirection: feedback.thumbDirection, + content: feedback.content, + agentConfigurationVersion: feedback.agentConfigurationVersion, + agentConfigurationId: feedback.agentConfigurationId, + createdAt: feedback.createdAt, + }) as AgentMessageFeedbackType + ); + return new Ok(feedbacks); +} + export async function getConversationFeedbacksForUser( auth: Authenticator, conversation: ConversationType | ConversationWithoutContentType diff --git a/front/lib/models/assistant/conversation.ts b/front/lib/models/assistant/conversation.ts index cae554ffc2ba..3bfa6c811213 100644 --- a/front/lib/models/assistant/conversation.ts +++ b/front/lib/models/assistant/conversation.ts @@ -366,6 +366,8 @@ AgentMessage.hasMany(AgentMessageFeedback, { UserModel.hasMany(AgentMessageFeedback, { onDelete: "SET NULL", }); +AgentMessageFeedback.belongsTo(UserModel); +AgentMessageFeedback.belongsTo(AgentMessage); export class Message extends BaseModel { declare createdAt: CreationOptional; diff --git a/front/lib/resources/agent_message_feedback_resource.ts b/front/lib/resources/agent_message_feedback_resource.ts index 8d47d2d712d1..ff3dd2783aa7 100644 --- a/front/lib/resources/agent_message_feedback_resource.ts +++ b/front/lib/resources/agent_message_feedback_resource.ts @@ -7,7 +7,12 @@ import { Op } from "sequelize"; import type { AgentMessageFeedbackDirection } from "@app/lib/api/assistant/conversation/feedbacks"; import type { Authenticator } from "@app/lib/auth"; import type { AgentMessage } from "@app/lib/models/assistant/conversation"; -import { AgentMessageFeedback } from "@app/lib/models/assistant/conversation"; +import { + AgentMessage as AgentMessageModel, + AgentMessageFeedback, + Conversation, + Message, +} from "@app/lib/models/assistant/conversation"; import type { Workspace } from "@app/lib/models/workspace"; import { BaseResource } from "@app/lib/resources/base_resource"; import type { ReadonlyAttributesType } from "@app/lib/resources/storage/types"; @@ -102,6 +107,21 @@ export class AgentMessageFeedbackResource extends BaseResource { + const agentMessageFeedback = await AgentMessageFeedback.findAll({ + where: { + agentConfigurationId, + }, + order: [["agentConfigurationVersion", "DESC"]], + }); + + return agentMessageFeedback.map( + (feedback) => new this(this.model, feedback.get()) + ); + } + static async listByWorkspaceAndDateRange({ workspace, startDate, @@ -125,6 +145,49 @@ export class AgentMessageFeedbackResource extends BaseResource { + const agentMessageFeedback = await AgentMessageFeedback.findByPk( + agentMessageFeedbackId, + { + // Feedback -> AgentMessage -> Message -> Conversation + include: [ + { + model: AgentMessageModel, + attributes: ["id"], + include: [ + { + model: Message, + as: "agentMessage", + attributes: ["id", "sId"], + include: [ + { + model: Conversation, + as: "conversation", + attributes: ["sId"], + }, + ], + }, + ], + }, + ], + } + ); + + if (!agentMessageFeedback) { + return null; + } + + return { + // @ts-expect-error: sequelize cannot handle include easily + messageId: agentMessageFeedback.agent_message.agentMessage.sId, + conversationId: + // @ts-expect-error: sequelize cannot handle include easily + agentMessageFeedback.agent_message.agentMessage.conversation.sId, + }; + } + async fetchUser(): Promise { const users = await UserResource.fetchByModelIds([this.userId]); return users[0] ?? null; diff --git a/front/lib/swr/assistants.ts b/front/lib/swr/assistants.ts index dc80fcded342..ddb5c52fa63b 100644 --- a/front/lib/swr/assistants.ts +++ b/front/lib/swr/assistants.ts @@ -11,6 +11,7 @@ import { useCallback, useMemo, useState } from "react"; import type { Fetcher } from "swr"; import { useSWRConfig } from "swr"; +import type { AgentMessageFeedbackType } from "@app/lib/api/assistant/feedback"; import { fetcher, getErrorFromResponse, @@ -242,6 +243,32 @@ export function useAgentConfiguration({ }; } +export function useAgentConfigurationFeedbacks({ + workspaceId, + agentConfigurationId, +}: { + workspaceId: string; + agentConfigurationId: string | null; +}) { + const agentConfigurationFeedbacksFetcher: Fetcher<{ + feedbacks: AgentMessageFeedbackType[]; + }> = fetcher; + + const { data, error, mutate } = useSWRWithDefaults( + agentConfigurationId + ? `/api/w/${workspaceId}/assistant/agent_configurations/${agentConfigurationId}/feedbacks` + : null, + agentConfigurationFeedbacksFetcher + ); + + return { + agentConfigurationFeedbacks: data ? data.feedbacks : null, + isAgentConfigurationFeedbacksLoading: !error && !data, + isAgentConfigurationFeedbacksError: error, + mutateAgentConfigurationFeedbacks: mutate, + }; +} + export function useAgentConfigurationHistory({ workspaceId, agentConfigurationId, diff --git a/front/lib/swr/feedbacks.ts b/front/lib/swr/feedbacks.ts new file mode 100644 index 000000000000..c238793fde64 --- /dev/null +++ b/front/lib/swr/feedbacks.ts @@ -0,0 +1,27 @@ +import type { Fetcher } from "swr"; + +import { fetcher, useSWRWithDefaults } from "@app/lib/swr/swr"; + +export function useFeedbackConversationContext({ + workspaceId, + feedbackId, +}: { + feedbackId: string; + workspaceId: string; +}) { + const feedbackFetcher: Fetcher<{ + conversationId: string; + messageId: string; + }> = fetcher; + + const { data, error } = useSWRWithDefaults( + `/api/w/${workspaceId}/assistant/feedbacks/${feedbackId}/conversation-context`, + feedbackFetcher + ); + + return { + conversationContext: data ? data : null, + isLoading: !error && !data, + isError: error, + }; +} diff --git a/front/lib/swr/user.ts b/front/lib/swr/user.ts index b661938d7bbd..635d3cfb0d43 100644 --- a/front/lib/swr/user.ts +++ b/front/lib/swr/user.ts @@ -2,6 +2,7 @@ import type { Fetcher } from "swr"; import { fetcher, useSWRWithDefaults } from "@app/lib/swr/swr"; import type { GetUserResponseBody } from "@app/pages/api/user"; +import type { GetUserDetailsResponseBody } from "@app/pages/api/user/[uId]/details"; import type { GetUserMetadataResponseBody } from "@app/pages/api/user/metadata/[key]"; export function useUser() { @@ -30,3 +31,17 @@ export function useUserMetadata(key: string) { mutateMetadata: mutate, }; } + +export function useUserDetails(userId: number) { + const userFetcher: Fetcher = fetcher; + const { data, error } = useSWRWithDefaults( + `/api/user/${userId}/details`, + userFetcher + ); + + return { + userDetails: data, + isUserDetailsLoading: !error && !data, + isUserDetailsError: error, + }; +} diff --git a/front/pages/api/user/[uId]/details/index.ts b/front/pages/api/user/[uId]/details/index.ts new file mode 100644 index 000000000000..d0cb2872d6a7 --- /dev/null +++ b/front/pages/api/user/[uId]/details/index.ts @@ -0,0 +1,70 @@ +import type { UserType, WithAPIErrorResponse } from "@dust-tt/types"; +import type { NextApiRequest, NextApiResponse } from "next"; + +import { withSessionAuthentication } from "@app/lib/api/auth_wrappers"; +import { UserResource } from "@app/lib/resources/user_resource"; +import { apiError } from "@app/logger/withlogging"; + +export type GetUserDetailsResponseBody = Pick< + UserType, + "firstName" | "lastName" | "image" +>; + +async function handler( + req: NextApiRequest, + res: NextApiResponse> +): Promise { + switch (req.method) { + case "GET": + const userId = req.query.uId; + if (typeof userId !== "string" || userId === "") { + return apiError(req, res, { + status_code: 400, + api_error: { + type: "invalid_request_error", + message: "The query parameter `uId` is not a string or is empty.", + }, + }); + } + + try { + parseInt(userId); + } catch (e) { + return apiError(req, res, { + status_code: 400, + api_error: { + type: "invalid_request_error", + message: "The query parameter `uId` is not a number.", + }, + }); + } + + const user = await UserResource.fetchByModelId(parseInt(userId)); + if (!user) { + return apiError(req, res, { + status_code: 404, + api_error: { + type: "user_not_found", + message: "The user was not found.", + }, + }); + } + + return res.status(200).json({ + firstName: user.firstName, + lastName: user.lastName, + image: user.imageUrl, + }); + + default: + return apiError(req, res, { + status_code: 405, + api_error: { + type: "method_not_supported_error", + message: "The method passed is not supported, GET is expected.", + }, + }); + } +} + +export default withSessionAuthentication(handler); diff --git a/front/pages/api/w/[wId]/assistant/agent_configurations/[aId]/feedbacks.ts b/front/pages/api/w/[wId]/assistant/agent_configurations/[aId]/feedbacks.ts new file mode 100644 index 000000000000..cc647de587f4 --- /dev/null +++ b/front/pages/api/w/[wId]/assistant/agent_configurations/[aId]/feedbacks.ts @@ -0,0 +1,50 @@ +import type { WithAPIErrorResponse } from "@dust-tt/types"; +import type { NextApiRequest, NextApiResponse } from "next"; + +import { apiErrorForConversation } from "@app/lib/api/assistant/conversation/helper"; +import type { AgentMessageFeedbackType } from "@app/lib/api/assistant/feedback"; +import { getAgentConfigurationFeedbacks } from "@app/lib/api/assistant/feedback"; +import { withSessionAuthenticationForWorkspace } from "@app/lib/api/auth_wrappers"; +import { apiError } from "@app/logger/withlogging"; + +async function handler( + req: NextApiRequest, + res: NextApiResponse< + WithAPIErrorResponse<{ feedbacks: AgentMessageFeedbackType[] }> + > +): Promise { + if (!(typeof req.query.aId === "string")) { + return apiError(req, res, { + status_code: 400, + api_error: { + type: "invalid_request_error", + message: "Invalid query parameters, `aId` (string) is required.", + }, + }); + } + + switch (req.method) { + case "GET": + const feedbacksRes = await getAgentConfigurationFeedbacks(req.query.aId); + + if (feedbacksRes.isErr()) { + return apiErrorForConversation(req, res, feedbacksRes.error); + } + + const feedbacks = feedbacksRes.value; + + res.status(200).json({ feedbacks }); + return; + + default: + return apiError(req, res, { + status_code: 405, + api_error: { + type: "method_not_supported_error", + message: "The method passed is not supported, GET is expected.", + }, + }); + } +} + +export default withSessionAuthenticationForWorkspace(handler); diff --git a/front/pages/api/w/[wId]/assistant/feedbacks/[fId]/conversation-context.ts b/front/pages/api/w/[wId]/assistant/feedbacks/[fId]/conversation-context.ts new file mode 100644 index 000000000000..50620b25c083 --- /dev/null +++ b/front/pages/api/w/[wId]/assistant/feedbacks/[fId]/conversation-context.ts @@ -0,0 +1,108 @@ +import type { WithAPIErrorResponse } from "@dust-tt/types"; +import type { NextApiRequest, NextApiResponse } from "next"; + +import { getAgentConfiguration } from "@app/lib/api/assistant/configuration"; +import { withSessionAuthenticationForWorkspace } from "@app/lib/api/auth_wrappers"; +import { Authenticator } from "@app/lib/auth"; +import { AgentMessageFeedbackResource } from "@app/lib/resources/agent_message_feedback_resource"; +import { apiError } from "@app/logger/withlogging"; + +export type GetAgentConfigurationsResponseBody = { + conversationId: string; + messageId: string; +}; + +async function handler( + req: NextApiRequest, + res: NextApiResponse< + WithAPIErrorResponse + >, + auth: Authenticator +): Promise { + switch (req.method) { + case "GET": + const feedbackId = req.query.fId; + if (typeof feedbackId !== "string" || feedbackId === "") { + return apiError(req, res, { + status_code: 400, + api_error: { + type: "invalid_request_error", + message: "Invalid query parameters, `fId` (string) is required.", + }, + }); + } + + // Make sure that user is one of the authors + const feedback = + await AgentMessageFeedbackResource.fetchByModelId(feedbackId); + if (!feedback) { + return apiError(req, res, { + status_code: 404, + api_error: { + type: "feedback_not_found", + message: `Feedback not found for id ${feedbackId}`, + }, + }); + } + const agent = await getAgentConfiguration( + auth, + feedback.agentConfigurationId + ); + if (!agent) { + return apiError(req, res, { + status_code: 404, + api_error: { + type: "agent_configuration_not_found", + message: `Agent configuration not found for id ${feedback.agentConfigurationId}`, + }, + }); + } + if ( + !auth.canRead( + Authenticator.createResourcePermissionsFromGroupIds( + agent.requestedGroupIds + ) + ) + ) { + return apiError(req, res, { + status_code: 404, + api_error: { + type: "feedback_not_found", + message: "Feedback not found.", + }, + }); + } + + const messageAndConversation = + await AgentMessageFeedbackResource.fetchMessageAndConversationId( + feedbackId + ); + + if (!messageAndConversation) { + return apiError(req, res, { + status_code: 404, + api_error: { + type: "conversation_not_found", + message: `Conversation not found for feedback ${feedbackId}`, + }, + }); + } + + const { conversationId, messageId } = messageAndConversation; + + return res.status(200).json({ + conversationId, + messageId, + }); + default: + return apiError(req, res, { + status_code: 405, + api_error: { + type: "method_not_supported_error", + message: "The method passed is not supported, GET is expected.", + }, + }); + } +} + +export default withSessionAuthenticationForWorkspace(handler); diff --git a/types/src/front/lib/error.ts b/types/src/front/lib/error.ts index d6e439478d80..b5c9d423855c 100644 --- a/types/src/front/lib/error.ts +++ b/types/src/front/lib/error.ts @@ -103,7 +103,9 @@ export type APIErrorType = | ConversationErrorType // Plugins: | "plugin_not_found" - | "plugin_execution_failed"; + | "plugin_execution_failed" + // feedbacks + | "feedback_not_found"; export type APIError = { type: APIErrorType;