From 489a2a819db917a379f3b4808f536a3b66a8dfee Mon Sep 17 00:00:00 2001 From: Lucas Massemin Date: Mon, 30 Dec 2024 19:29:58 +0100 Subject: [PATCH] Revert "New resources for feedback loop (#9607)" (#9662) This reverts commit 3b2def6ca441318e5d61a42d58d7381b9fa9feee. --- front/lib/api/assistant/feedback.ts | 270 ++++++----- front/lib/models/assistant/conversation.ts | 20 +- .../agent_message_feedback_resource.ts | 438 ++++-------------- front/lib/workspace_usage.ts | 28 +- .../agent_configurations/[aId]/feedbacks.ts | 94 ---- .../[cId]/messages/[mId]/feedbacks/index.ts | 4 +- 6 files changed, 260 insertions(+), 594 deletions(-) delete mode 100644 front/pages/api/w/[wId]/assistant/agent_configurations/[aId]/feedbacks.ts diff --git a/front/lib/api/assistant/feedback.ts b/front/lib/api/assistant/feedback.ts index 49873e8da27c..1ef36f06df72 100644 --- a/front/lib/api/assistant/feedback.ts +++ b/front/lib/api/assistant/feedback.ts @@ -4,13 +4,15 @@ import type { Result, } from "@dust-tt/types"; import type { UserType } from "@dust-tt/types"; -import { ConversationError, Err, Ok } from "@dust-tt/types"; +import { ConversationError, Err, GLOBAL_AGENTS_SID, Ok } from "@dust-tt/types"; +import { Op } from "sequelize"; -import { getAgentConfiguration } from "@app/lib/api/assistant/configuration"; import { canAccessConversation } from "@app/lib/api/assistant/conversation/auth"; import type { AgentMessageFeedbackDirection } from "@app/lib/api/assistant/conversation/feedbacks"; -import type { PaginationParams } from "@app/lib/api/pagination"; import type { Authenticator } from "@app/lib/auth"; +import { AgentConfiguration } from "@app/lib/models/assistant/agent"; +import { AgentMessage } from "@app/lib/models/assistant/conversation"; +import { Message } from "@app/lib/models/assistant/conversation"; import { AgentMessageFeedbackResource } from "@app/lib/resources/agent_message_feedback_resource"; /** @@ -24,41 +26,69 @@ export type AgentMessageFeedbackType = { userId: number; thumbDirection: AgentMessageFeedbackDirection; content: string | null; - createdAt: Date; - agentConfigurationId: string; - agentConfigurationVersion: number; - isConversationShared: boolean; -}; - -export type AgentMessageFeedbackWithMetadataType = AgentMessageFeedbackType & { - conversationId: string | null; - userName: string; - userEmail: string; - userImageUrl: string | null; }; export async function getConversationFeedbacksForUser( auth: Authenticator, conversation: ConversationType | ConversationWithoutContentType -): Promise> { - if (!canAccessConversation(auth, conversation)) { +): Promise> { + const owner = auth.workspace(); + if (!owner) { + throw new Error("Unexpected `auth` without `workspace`."); + } + const user = auth.user(); + if (!canAccessConversation(auth, conversation) || !user) { return new Err(new ConversationError("conversation_access_restricted")); } + const messages = await Message.findAll({ + where: { + conversationId: conversation.id, + agentMessageId: { + [Op.ne]: null, + }, + }, + attributes: ["sId", "agentMessageId"], + }); + + const agentMessages = await AgentMessage.findAll({ + where: { + id: { + [Op.in]: messages + .map((m) => m.agentMessageId) + .filter((id): id is number => id !== null), + }, + }, + }); + const feedbacks = - await AgentMessageFeedbackResource.getConversationFeedbacksForUser( - auth, - conversation + await AgentMessageFeedbackResource.fetchByUserAndAgentMessages( + user, + agentMessages ); - return feedbacks; + const feedbacksByMessageId = feedbacks.map( + (feedback) => + ({ + id: feedback.id, + messageId: messages.find( + (m) => m.agentMessageId === feedback.agentMessageId + )!.sId, + agentMessageId: feedback.agentMessageId, + userId: feedback.userId, + thumbDirection: feedback.thumbDirection, + content: feedback.content, + }) as AgentMessageFeedbackType + ); + + return new Ok(feedbacksByMessageId); } /** * We create a feedback for a single message. * As user can be null (user from Slack), we also store the user context, as we do for messages. */ -export async function upsertMessageFeedback( +export async function createOrUpdateMessageFeedback( auth: Authenticator, { messageId, @@ -66,64 +96,94 @@ export async function upsertMessageFeedback( user, thumbDirection, content, - isConversationShared, }: { messageId: string; conversation: ConversationType | ConversationWithoutContentType; user: UserType; thumbDirection: AgentMessageFeedbackDirection; content?: string; - isConversationShared?: boolean; } -) { - const feedbackWithConversationContext = - await AgentMessageFeedbackResource.getFeedbackWithConversationContext({ - auth, - messageId, - conversation, - user, - }); +): Promise { + const owner = auth.workspace(); + if (!owner) { + throw new Error("Unexpected `auth` without `workspace`."); + } + + const message = await Message.findOne({ + where: { + sId: messageId, + conversationId: conversation.id, + }, + }); - if (feedbackWithConversationContext.isErr()) { - return feedbackWithConversationContext; + if (!message || !message.agentMessageId) { + return null; } - const { agentMessage, feedback, agentConfiguration, isGlobalAgent } = - feedbackWithConversationContext.value; + const agentMessage = await AgentMessage.findOne({ + where: { + id: message.agentMessageId, + }, + }); - if (feedback) { - await feedback.updateFields({ - content, - thumbDirection, - isConversationShared, + if (!agentMessage) { + return null; + } + + let isGlobalAgent = false; + let agentConfigurationId = agentMessage.agentConfigurationId; + if ( + Object.values(GLOBAL_AGENTS_SID).includes( + agentMessage.agentConfigurationId as GLOBAL_AGENTS_SID + ) + ) { + isGlobalAgent = true; + } + + if (!isGlobalAgent) { + const agentConfiguration = await AgentConfiguration.findOne({ + where: { + sId: agentMessage.agentConfigurationId, + }, }); - return new Ok(undefined); + + if (!agentConfiguration) { + return null; + } + agentConfigurationId = agentConfiguration.sId; } - try { - await AgentMessageFeedbackResource.makeNew({ - workspaceId: auth.getNonNullableWorkspace().id, - // If the agent is global, we use the agent configuration id from the agent message - // Otherwise, we use the agent configuration id from the agent configuration - agentConfigurationId: isGlobalAgent - ? agentMessage.agentConfigurationId - : agentConfiguration.sId, + const feedback = + await AgentMessageFeedbackResource.fetchByUserAndAgentMessage({ + user, + agentMessage, + }); + + if (feedback) { + const updatedFeedback = await feedback.updateContentAndThumbDirection( + content ?? "", + thumbDirection + ); + + return updatedFeedback.isOk(); + } else { + const newFeedback = await AgentMessageFeedbackResource.makeNew({ + workspaceId: owner.id, + agentConfigurationId: agentConfigurationId, agentConfigurationVersion: agentMessage.agentConfigurationVersion, agentMessageId: agentMessage.id, userId: user.id, thumbDirection, content, - isConversationShared: isConversationShared ?? false, + isConversationShared: false, }); - } catch (e) { - return new Err(e as Error); + return newFeedback !== null; } - return new Ok(undefined); } /** - * The id of a feedback is not exposed on the API so we need to find it from the message id and the user context. - * We destroy feedbacks, no point in soft-deleting them. + * The id of a reaction is not exposed on the API so we need to find it from the message id and the user context. + * We destroy reactions, no point in soft-deleting them. */ export async function deleteMessageFeedback( auth: Authenticator, @@ -136,87 +196,45 @@ export async function deleteMessageFeedback( conversation: ConversationType | ConversationWithoutContentType; user: UserType; } -) { - if (!canAccessConversation(auth, conversation)) { - return new Err({ - type: "conversation_access_restricted", - message: "You don't have access to this conversation.", - }); - } - - const feedbackWithContext = - await AgentMessageFeedbackResource.getFeedbackWithConversationContext({ - auth, - messageId, - conversation, - user, - }); - - if (feedbackWithContext.isErr()) { - return feedbackWithContext; +): Promise { + const owner = auth.workspace(); + if (!owner) { + throw new Error("Unexpected `auth` without `workspace`."); } - const { feedback } = feedbackWithContext.value; + const message = await Message.findOne({ + where: { + sId: messageId, + conversationId: conversation.id, + }, + attributes: ["agentMessageId"], + }); - if (!feedback) { - return new Ok(undefined); + if (!message || !message.agentMessageId) { + return null; } - const deleteRes = await feedback.delete(auth, {}); + const agentMessage = await AgentMessage.findOne({ + where: { + id: message.agentMessageId, + }, + }); - if (deleteRes.isErr()) { - return deleteRes; + if (!agentMessage) { + return null; } - return new Ok(undefined); -} + const feedback = + await AgentMessageFeedbackResource.fetchByUserAndAgentMessage({ + user, + agentMessage, + }); -export async function getAgentFeedbacks({ - auth, - agentConfigurationId, - withMetadata, - paginationParams, -}: { - auth: Authenticator; - withMetadata: boolean; - agentConfigurationId: string; - paginationParams: PaginationParams; -}): Promise< - Result< - (AgentMessageFeedbackType | AgentMessageFeedbackWithMetadataType)[], - Error - > -> { - const owner = auth.getNonNullableWorkspace(); - - // Make sure the user has access to the agent - const agentConfiguration = await getAgentConfiguration( - auth, - agentConfigurationId - ); - if (!agentConfiguration) { - return new Err(new Error("agent_configuration_not_found")); + if (!feedback) { + return null; } - const feedbacksRes = await AgentMessageFeedbackResource.fetch({ - workspace: owner, - agentConfiguration, - paginationParams, - withMetadata, - }); - - if (!withMetadata) { - return new Ok(feedbacksRes); - } + const deletedFeedback = await feedback.delete(auth); - const feedbacks = ( - feedbacksRes as AgentMessageFeedbackWithMetadataType[] - ).map((feedback) => ({ - ...feedback, - // Only display conversationId if the feedback was shared - conversationId: feedback.isConversationShared - ? feedback.conversationId - : null, - })); - return new Ok(feedbacks); + return deletedFeedback.isOk(); } diff --git a/front/lib/models/assistant/conversation.ts b/front/lib/models/assistant/conversation.ts index 5ee6ba3e5af6..1d943ae114c7 100644 --- a/front/lib/models/assistant/conversation.ts +++ b/front/lib/models/assistant/conversation.ts @@ -244,8 +244,6 @@ export class AgentMessage extends BaseModel { declare agentConfigurationVersion: number; declare agentMessageContents?: NonAttribute; - declare message?: NonAttribute; - declare feedbacks?: NonAttribute; } AgentMessage.init( @@ -306,9 +304,6 @@ export class AgentMessageFeedback extends BaseModel { declare thumbDirection: AgentMessageFeedbackDirection; declare content: string | null; - - declare agentMessage: NonAttribute; - declare user: NonAttribute; } AgentMessageFeedback.init( @@ -372,18 +367,11 @@ Workspace.hasMany(AgentMessageFeedback, { onDelete: "RESTRICT", }); AgentMessage.hasMany(AgentMessageFeedback, { - as: "feedbacks", onDelete: "RESTRICT", }); UserModel.hasMany(AgentMessageFeedback, { onDelete: "SET NULL", }); -AgentMessageFeedback.belongsTo(UserModel, { - as: "user", -}); -AgentMessageFeedback.belongsTo(AgentMessage, { - as: "agentMessage", -}); export class Message extends BaseModel { declare createdAt: CreationOptional; @@ -406,8 +394,6 @@ export class Message extends BaseModel { declare agentMessage?: NonAttribute; declare contentFragment?: NonAttribute; declare reactions?: NonAttribute; - - declare conversation?: NonAttribute; } Message.init( @@ -497,7 +483,7 @@ Message.belongsTo(Conversation, { }); UserMessage.hasOne(Message, { - as: "message", + as: "userMessage", foreignKey: { name: "userMessageId", allowNull: true }, }); Message.belongsTo(UserMessage, { @@ -506,7 +492,7 @@ Message.belongsTo(UserMessage, { }); AgentMessage.hasOne(Message, { - as: "message", + as: "agentMessage", foreignKey: { name: "agentMessageId", allowNull: true }, }); Message.belongsTo(AgentMessage, { @@ -518,7 +504,7 @@ Message.belongsTo(Message, { foreignKey: { name: "parentId", allowNull: true }, }); ContentFragmentModel.hasOne(Message, { - as: "message", + as: "contentFragment", foreignKey: { name: "contentFragmentId", allowNull: true }, }); Message.belongsTo(ContentFragmentModel, { diff --git a/front/lib/resources/agent_message_feedback_resource.ts b/front/lib/resources/agent_message_feedback_resource.ts index b9ba0bc44eab..3071e8890b92 100644 --- a/front/lib/resources/agent_message_feedback_resource.ts +++ b/front/lib/resources/agent_message_feedback_resource.ts @@ -1,34 +1,18 @@ import type { AgentConfigurationType, - AgentMessageType, - ConversationError, - ConversationType, - ConversationWithoutContentType, - MessageType, Result, UserType, WorkspaceType, } from "@dust-tt/types"; -import { GLOBAL_AGENTS_SID } from "@dust-tt/types"; import { Err, Ok } from "@dust-tt/types"; -import type { Attributes, ModelStatic, WhereOptions } from "sequelize"; +import type { Attributes, ModelStatic } from "sequelize"; import type { CreationAttributes, Transaction } from "sequelize"; import { Op } from "sequelize"; -import type { - AgentMessageFeedbackType, - AgentMessageFeedbackWithMetadataType, -} from "@app/lib/api/assistant/feedback"; -import type { PaginationParams } from "@app/lib/api/pagination"; +import type { AgentMessageFeedbackDirection } from "@app/lib/api/assistant/conversation/feedbacks"; import type { Authenticator } from "@app/lib/auth"; -import { AgentConfiguration } from "@app/lib/models/assistant/agent"; -import { AgentMessage } from "@app/lib/models/assistant/conversation"; -import { - AgentMessage as AgentMessageModel, - AgentMessageFeedback, - Conversation, - Message, -} from "@app/lib/models/assistant/conversation"; +import type { AgentMessage } from "@app/lib/models/assistant/conversation"; +import { AgentMessageFeedback } from "@app/lib/models/assistant/conversation"; import { BaseResource } from "@app/lib/resources/base_resource"; import type { ReadonlyAttributesType } from "@app/lib/resources/storage/types"; import { UserResource } from "@app/lib/resources/user_resource"; @@ -63,376 +47,130 @@ export class AgentMessageFeedbackResource extends BaseResource> { - await this.model.destroy({ + static async listByAgentConfigurationId({ + agentConfiguration, + }: { + agentConfiguration: AgentConfigurationType; + }): Promise { + const agentMessageFeedback = await AgentMessageFeedback.findAll({ where: { - id: this.id, + agentConfigurationId: agentConfiguration.sId, }, - transaction, + order: [["id", "DESC"]], }); - return new Ok(undefined); - } - async updateFields( - blob: Partial< - Pick< - AgentMessageFeedback, - "content" | "thumbDirection" | "isConversationShared" - > - > - ) { - return this.update({ - content: blob.content, - thumbDirection: blob.thumbDirection, - isConversationShared: blob.isConversationShared, - }); + return agentMessageFeedback.map( + (feedback) => new this(this.model, feedback.get()) + ); } - static async fetch({ - workspace, - withMetadata, - agentConfiguration, - paginationParams, + static async fetchByUserAndAgentMessage({ + user, + agentMessage, }: { - workspace: WorkspaceType; - withMetadata: boolean; - agentConfiguration?: AgentConfigurationType; - paginationParams: PaginationParams; - }): Promise< - (AgentMessageFeedbackType | AgentMessageFeedbackWithMetadataType)[] - > { - const where: WhereOptions = { - // IMPORTANT: Necessary for global models who share ids across workspaces. - workspaceId: workspace.id, - }; + user: UserType; + agentMessage: AgentMessage; + }): Promise { + const agentMessageFeedback = await AgentMessageFeedback.findOne({ + where: { + userId: user.id, + agentMessageId: agentMessage.id, + }, + }); - if (paginationParams.lastValue) { - const op = paginationParams.orderDirection === "desc" ? Op.lt : Op.gt; - where[paginationParams.orderColumn as any] = { - [op]: paginationParams.lastValue, - }; - } - if (agentConfiguration) { - where.agentConfigurationId = agentConfiguration.sId.toString(); + if (!agentMessageFeedback) { + return null; } + return new AgentMessageFeedbackResource( + AgentMessageFeedback, + agentMessageFeedback.get() + ); + } + + static async fetchByUserAndAgentMessages( + user: UserType, + agentMessages: AgentMessage[] + ): Promise { const agentMessageFeedback = await AgentMessageFeedback.findAll({ - where, - include: [ - { - model: AgentMessageModel, - attributes: ["id"], - as: "agentMessage", - include: [ - { - model: Message, - as: "message", - attributes: ["id", "sId"], - include: [ - { - model: Conversation, - as: "conversation", - attributes: ["id", "sId"], - }, - ], - }, - ], - }, - { - model: UserResource.model, - as: "user", - attributes: ["name", "imageUrl", "email"], + where: { + userId: user.id, + agentMessageId: { + [Op.in]: agentMessages.map((m) => m.id), }, - ], - order: [ - [ - paginationParams.orderColumn, - paginationParams.orderDirection === "desc" ? "DESC" : "ASC", - ], - ], - limit: paginationParams.limit, + }, }); - return ( - agentMessageFeedback - // Typeguard needed because of TypeScript limitations - .filter( - ( - feedback - ): feedback is AgentMessageFeedback & { - agentMessage: { - message: Message & { conversation: Conversation }; - }; - } => !!feedback.agentMessage?.message?.conversation - ) - .map((feedback) => { - return { - id: feedback.id, - messageId: feedback.agentMessage.message.sId, - agentMessageId: feedback.agentMessageId, - userId: feedback.userId, - thumbDirection: feedback.thumbDirection, - content: feedback.content - ? feedback.content.replace(/\r?\n/g, "\\n") - : null, - isConversationShared: feedback.isConversationShared, - createdAt: feedback.createdAt, - agentConfigurationId: feedback.agentConfigurationId, - agentConfigurationVersion: feedback.agentConfigurationVersion, - - ...(withMetadata && { - // This field is sensitive, it allows accessing the conversation - conversationId: feedback.isConversationShared - ? feedback.agentMessage.message.conversation.sId - : null, - userName: feedback.user.name, - userEmail: feedback.user.email, - userImageUrl: feedback.user.imageUrl, - }), - }; - }) + return agentMessageFeedback.map( + (feedback) => new this(this.model, feedback.get()) ); } - static async getFeedbackUsageDataForWorkspace({ + static async listByWorkspaceAndDateRange({ + workspace, startDate, endDate, - workspace, }: { + workspace: WorkspaceType; startDate: Date; endDate: Date; - workspace: WorkspaceType; - }) { - const agentMessageFeedback = await AgentMessageFeedback.findAll({ + }): Promise { + const feedbacks = await AgentMessageFeedback.findAll({ where: { - // IMPORTANT: Necessary for global models who share ids across workspaces. workspaceId: workspace.id, createdAt: { - [Op.and]: [{ [Op.lt]: endDate }, { [Op.gt]: startDate }], + [Op.and]: [{ [Op.gte]: startDate }, { [Op.lte]: endDate }], }, }, + }).then((feedbacks) => + feedbacks.map((feedback) => new this(this.model, feedback.get())) + ); - include: [ - { - model: AgentMessageModel, - attributes: ["id"], - as: "agentMessage", - }, - { - model: UserResource.model, - as: "user", - attributes: ["name", "email"], - }, - ], - order: [["id", "ASC"]], - }); - - return agentMessageFeedback.map((feedback) => { - return { - id: feedback.id, - createdAt: feedback.createdAt, - userName: feedback.user.name, - userEmail: feedback.user.email, - agentConfigurationId: feedback.agentConfigurationId, - agentConfigurationVersion: feedback.agentConfigurationVersion, - thumb: feedback.thumbDirection, - content: feedback.content?.replace(/\r?\n/g, "\\n") || null, - }; - }); + return feedbacks; } - static async getConversationFeedbacksForUser( - auth: Authenticator, - conversation: ConversationType | ConversationWithoutContentType - ): Promise> { - const user = auth.getNonNullableUser(); - - const feedbackForMessages = await Message.findAll({ - where: { - conversationId: conversation.id, - agentMessageId: { - [Op.ne]: null, - }, - }, - attributes: ["id", "sId", "agentMessageId"], - include: [ - { - model: AgentMessage, - as: "agentMessage", - include: [ - { - model: AgentMessageFeedbackResource.model, - as: "feedbacks", - where: { - userId: user.id, - }, - }, - ], - }, - ], - }); - - const feedbacksWithMessageId = feedbackForMessages - // typeguard needed because of TypeScript limitations - .filter( - ( - message - ): message is Message & { - agentMessage: { feedbacks: AgentMessageFeedbackResource[] }; - } => - !!message.agentMessage?.feedbacks && - message.agentMessage.feedbacks.length > 0 - ) - .map((message) => { - // Only one feedback can be associated with a message - const feedback = message.agentMessage.feedbacks[0]; - return { - id: feedback.id, - messageId: message.sId, - agentMessageId: feedback.agentMessageId, - userId: feedback.userId, - thumbDirection: feedback.thumbDirection, - content: feedback.content, - isConversationShared: feedback.isConversationShared, - createdAt: feedback.createdAt, - agentConfigurationId: feedback.agentConfigurationId, - agentConfigurationVersion: feedback.agentConfigurationVersion, - } as AgentMessageFeedbackType; - }); - - return new Ok(feedbacksWithMessageId); + async fetchUser(): Promise { + const users = await UserResource.fetchByModelIds([this.userId]); + return users[0] ?? null; } - static async getFeedbackWithConversationContext({ - auth, - messageId, - conversation, - user, - }: { - auth: Authenticator; - messageId: string; - conversation: ConversationType | ConversationWithoutContentType; - user: UserType; - }): Promise< - Result< - { - message: Pick; - agentMessage: Pick & { - agentConfigurationId: string; - agentConfigurationVersion: number; - }; - feedback: AgentMessageFeedbackResource | null; - agentConfiguration: Pick< - AgentConfigurationType, - "id" | "sId" | "version" - >; - isGlobalAgent: boolean; - }, - Error - > - > { - const message = await Message.findOne({ - attributes: ["id", "sId"], - where: { - sId: messageId, - conversationId: conversation.id, - }, - include: [ + async updateContentAndThumbDirection( + content: string, + thumbDirection: AgentMessageFeedbackDirection + ): Promise> { + try { + await this.model.update( { - model: AgentMessage, - as: "agentMessage", - attributes: [ - "id", - "sId", - "agentConfigurationId", - "agentConfigurationVersion", - ], + content, + thumbDirection, }, - ], - }); - - if (!message || !message.agentMessageId) { - return new Err( - new Error("Message not found or not associated with an agent message") + { + where: { + id: this.id, + }, + } ); - } - if (!message.agentMessage) { - return new Err(new Error("Agent message not found")); + return new Ok(undefined); + } catch (error) { + return new Err(error as Error); } + } - const agentMessageFeedback = await AgentMessageFeedback.findOne({ - where: { - userId: user.id, - agentMessageId: message.agentMessage.id, - workspaceId: auth.getNonNullableWorkspace().id, - }, - }); - const agentMessageFeedbackResource = agentMessageFeedback - ? new AgentMessageFeedbackResource( - AgentMessageFeedback, - agentMessageFeedback.get() - ) - : null; - - const isGlobalAgent = Object.values(GLOBAL_AGENTS_SID).includes( - message.agentMessage.agentConfigurationId as GLOBAL_AGENTS_SID - ); - - if (isGlobalAgent) { - return new Ok({ - message: { - id: message.id, - sId: message.sId, - }, - agentMessage: { - id: message.agentMessage.id, - agentConfigurationId: message.agentMessage.agentConfigurationId, - agentConfigurationVersion: - message.agentMessage.agentConfigurationVersion, - }, - feedback: agentMessageFeedbackResource, - agentConfiguration: { - id: -1, - sId: message.agentMessage.agentConfigurationId, - version: message.agentMessage.agentConfigurationVersion, + async delete( + auth: Authenticator, + { transaction }: { transaction?: Transaction } = {} + ): Promise> { + try { + await this.model.destroy({ + where: { + id: this.id, }, - isGlobalAgent: true, + transaction, }); + return new Ok(undefined); + } catch (err) { + return new Err(err as Error); } - - const agentConfiguration = await AgentConfiguration.findOne({ - where: { - sId: message.agentMessage.agentConfigurationId, - }, - attributes: ["id", "sId", "version"], - }); - - if (!agentConfiguration) { - return new Err(new Error("Agent configuration not found")); - } - - return new Ok({ - message: { - id: message.id, - sId: message.sId, - }, - agentMessage: { - id: message.agentMessage.id, - agentConfigurationId: message.agentMessage.agentConfigurationId, - agentConfigurationVersion: - message.agentMessage.agentConfigurationVersion, - }, - feedback: agentMessageFeedbackResource, - agentConfiguration: { - id: agentConfiguration.id, - sId: agentConfiguration.sId, - version: agentConfiguration.version, - }, - isGlobalAgent, - }); } } diff --git a/front/lib/workspace_usage.ts b/front/lib/workspace_usage.ts index 428c46511a3f..bb6ca175a258 100644 --- a/front/lib/workspace_usage.ts +++ b/front/lib/workspace_usage.ts @@ -74,7 +74,7 @@ interface AgentUsageQueryResult { interface FeedbackQueryResult { id: ModelId; - createdAt: Date; + created_at: Date; userName: string; userEmail: string; agentConfigurationId: string; @@ -441,15 +441,33 @@ export async function getFeedbacksUsageData( workspace: WorkspaceType ): Promise { const feedbacks = - (await AgentMessageFeedbackResource.getFeedbackUsageDataForWorkspace({ + await AgentMessageFeedbackResource.listByWorkspaceAndDateRange({ + workspace: workspace, startDate, endDate, - workspace, - })) as FeedbackQueryResult[]; + }); + if (!feedbacks.length) { return "No data available for the selected period."; } - return generateCsvFromQueryResult(feedbacks); + + const feedbackResults: FeedbackQueryResult[] = await Promise.all( + feedbacks.map(async (feedback) => { + const user = await feedback.fetchUser(); + return { + id: feedback.id, + created_at: feedback.createdAt, + userName: user?.fullName() || "", + userEmail: user?.email || "", + agentConfigurationId: feedback.agentConfigurationId, + agentConfigurationVersion: feedback.agentConfigurationVersion, + thumb: feedback.thumbDirection, + content: feedback.content?.replace(/\r?\n/g, "\\n") || null, + }; + }) + ); + + return generateCsvFromQueryResult(feedbackResults); } function generateCsvFromQueryResult( 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 deleted file mode 100644 index 332f812296ee..000000000000 --- a/front/pages/api/w/[wId]/assistant/agent_configurations/[aId]/feedbacks.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { WithAPIErrorResponse } from "@dust-tt/types"; -import type { NextApiRequest, NextApiResponse } from "next"; - -import { getAgentConfiguration } from "@app/lib/api/assistant/configuration"; -import { apiErrorForConversation } from "@app/lib/api/assistant/conversation/helper"; -import type { AgentMessageFeedbackType } from "@app/lib/api/assistant/feedback"; -import { getAgentFeedbacks } from "@app/lib/api/assistant/feedback"; -import { withSessionAuthenticationForWorkspace } from "@app/lib/api/auth_wrappers"; -import { getPaginationParams } from "@app/lib/api/pagination"; -import type { Authenticator } from "@app/lib/auth"; -import { apiError } from "@app/logger/withlogging"; - -async function handler( - req: NextApiRequest, - res: NextApiResponse< - WithAPIErrorResponse<{ feedbacks: AgentMessageFeedbackType[] }> - >, - auth: Authenticator -): Promise { - const { aId } = req.query; - - if (typeof aId !== "string") { - return apiError(req, res, { - status_code: 400, - api_error: { - type: "invalid_request_error", - message: "Invalid query parameters, `aId` (string) is required.", - }, - }); - } - - // IMPORTANT: make sure the agent configuration is accessible by the user. - const agentConfiguration = await getAgentConfiguration(auth, aId); - if (!agentConfiguration) { - return apiError(req, res, { - status_code: 404, - api_error: { - type: "agent_configuration_not_found", - message: "The agent configuration was not found.", - }, - }); - } - - switch (req.method) { - case "GET": - // asc id is equivalent to desc createdAt - const paginationRes = getPaginationParams(req, { - defaultLimit: 50, - defaultOrderColumn: "id", - defaultOrderDirection: "asc", - supportedOrderColumn: ["id"], - }); - if (paginationRes.isErr()) { - return apiError( - req, - res, - { - status_code: 400, - api_error: { - type: "invalid_pagination_parameters", - message: "Invalid pagination parameters", - }, - }, - paginationRes.error - ); - } - const feedbacksRes = await getAgentFeedbacks({ - auth, - agentConfigurationId: aId, - withMetadata: req.query.withMetadata === "true", - paginationParams: paginationRes.value, - }); - - 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/conversations/[cId]/messages/[mId]/feedbacks/index.ts b/front/pages/api/w/[wId]/assistant/conversations/[cId]/messages/[mId]/feedbacks/index.ts index 43c456f8b1ff..784ec1603ddf 100644 --- a/front/pages/api/w/[wId]/assistant/conversations/[cId]/messages/[mId]/feedbacks/index.ts +++ b/front/pages/api/w/[wId]/assistant/conversations/[cId]/messages/[mId]/feedbacks/index.ts @@ -8,8 +8,8 @@ import type { AgentMessageFeedbackDirection } from "@app/lib/api/assistant/conve import { apiErrorForConversation } from "@app/lib/api/assistant/conversation/helper"; import { getConversationWithoutContent } from "@app/lib/api/assistant/conversation/without_content"; import { + createOrUpdateMessageFeedback, deleteMessageFeedback, - upsertMessageFeedback, } from "@app/lib/api/assistant/feedback"; import { withSessionAuthenticationForWorkspace } from "@app/lib/api/auth_wrappers"; import type { Authenticator } from "@app/lib/auth"; @@ -79,7 +79,7 @@ async function handler( }); } - const created = await upsertMessageFeedback(auth, { + const created = await createOrUpdateMessageFeedback(auth, { messageId, conversation, user,