From 7d00f6fbe9b620bc3afd4c2558f05efbd1ef92e4 Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 6 Feb 2024 17:07:22 +0530 Subject: [PATCH 01/14] Add database entity for chat message feedback --- packages/server/src/Interface.ts | 15 ++++++++++ .../database/entities/ChatMessageFeedback.ts | 28 +++++++++++++++++++ .../server/src/database/entities/index.ts | 2 ++ 3 files changed, 45 insertions(+) create mode 100644 packages/server/src/database/entities/ChatMessageFeedback.ts diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 126aac38d..aa5852b2b 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -6,6 +6,11 @@ export enum chatType { INTERNAL = 'INTERNAL', EXTERNAL = 'EXTERNAL' } + +export enum ChatMessageRatingType { + THUMBS_UP = 'THUMBS_UP', + THUMBS_DOWN = 'THUMBS_DOWN' +} /** * Databases */ @@ -38,6 +43,16 @@ export interface IChatMessage { createdDate: Date } +export interface IChatMessageFeedback { + id: string + content?: string + chatflowid: string + chatId: string + messageId: string + rating: ChatMessageRatingType + createdDate: Date +} + export interface ITool { id: string name: string diff --git a/packages/server/src/database/entities/ChatMessageFeedback.ts b/packages/server/src/database/entities/ChatMessageFeedback.ts new file mode 100644 index 000000000..4011972db --- /dev/null +++ b/packages/server/src/database/entities/ChatMessageFeedback.ts @@ -0,0 +1,28 @@ +/* eslint-disable */ +import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn, Index } from 'typeorm' +import { ChatMessageRatingType, IChatMessageFeedback } from '../../Interface' + +@Entity() +export class ChatMessageFeedback implements IChatMessageFeedback { + @PrimaryGeneratedColumn('uuid') + id: string + + @Index() + @Column() + chatflowid: string + + @Column({ type: 'text' }) + content?: string + + @Column() + chatId: string + + @Column() + messageId: string + + @Column() + rating: ChatMessageRatingType + + @CreateDateColumn() + createdDate: Date +} diff --git a/packages/server/src/database/entities/index.ts b/packages/server/src/database/entities/index.ts index af5c559f5..6a0e2d220 100644 --- a/packages/server/src/database/entities/index.ts +++ b/packages/server/src/database/entities/index.ts @@ -1,5 +1,6 @@ import { ChatFlow } from './ChatFlow' import { ChatMessage } from './ChatMessage' +import { ChatMessageFeedback } from './ChatMessageFeedback' import { Credential } from './Credential' import { Tool } from './Tool' import { Assistant } from './Assistant' @@ -8,6 +9,7 @@ import { Variable } from './Variable' export const entities = { ChatFlow, ChatMessage, + ChatMessageFeedback, Credential, Tool, Assistant, From 336b4174dcc97ab67af0f2976d20af953ed20211 Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 6 Feb 2024 17:09:18 +0530 Subject: [PATCH 02/14] Add migrations --- .../mysql/1707213626553-AddFeedback.ts | 22 +++++++++++++++++++ .../src/database/migrations/mysql/index.ts | 4 +++- .../postgres/1707213601923-AddFeedback.ts | 22 +++++++++++++++++++ .../src/database/migrations/postgres/index.ts | 4 +++- .../sqlite/1707213619308-AddFeedback.ts | 13 +++++++++++ .../src/database/migrations/sqlite/index.ts | 4 +++- 6 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 packages/server/src/database/migrations/mysql/1707213626553-AddFeedback.ts create mode 100644 packages/server/src/database/migrations/postgres/1707213601923-AddFeedback.ts create mode 100644 packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts diff --git a/packages/server/src/database/migrations/mysql/1707213626553-AddFeedback.ts b/packages/server/src/database/migrations/mysql/1707213626553-AddFeedback.ts new file mode 100644 index 000000000..f2c86734a --- /dev/null +++ b/packages/server/src/database/migrations/mysql/1707213626553-AddFeedback.ts @@ -0,0 +1,22 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddFeedback1707213626553 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS \`chat_message_feedback\` ( + \`id\` varchar(36) NOT NULL, + \`chatflowid\` varchar(255) NOT NULL, + \`content\` text, + \`chatId\` varchar(255) NOT NULL, + \`messageId\` varchar(255) NOT NULL, + \`rating\` varchar(255) NOT NULL, + \`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + PRIMARY KEY (\`id\`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE chat_message_feedback`) + } +} diff --git a/packages/server/src/database/migrations/mysql/index.ts b/packages/server/src/database/migrations/mysql/index.ts index a5220ad88..6421365c7 100644 --- a/packages/server/src/database/migrations/mysql/index.ts +++ b/packages/server/src/database/migrations/mysql/index.ts @@ -11,6 +11,7 @@ import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedT import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' +import { AddFeedback1707213626553 } from './1707213626553-AddFeedback' export const mysqlMigrations = [ Init1693840429259, @@ -25,5 +26,6 @@ export const mysqlMigrations = [ AddUsedToolsToChatMessage1699481607341, AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237, - AddVariableEntity1699325775451 + AddVariableEntity1699325775451, + AddFeedback1707213626553 ] diff --git a/packages/server/src/database/migrations/postgres/1707213601923-AddFeedback.ts b/packages/server/src/database/migrations/postgres/1707213601923-AddFeedback.ts new file mode 100644 index 000000000..779de42e6 --- /dev/null +++ b/packages/server/src/database/migrations/postgres/1707213601923-AddFeedback.ts @@ -0,0 +1,22 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddFeedback1707213601923 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS chat_message_feedback ( + id uuid NOT NULL DEFAULT uuid_generate_v4(), + "chatflowid" varchar NOT NULL, + "content" text, + "chatId" varchar NOT NULL, + "messageId" varchar NOT NULL, + "rating" varchar NOT NULL, + "createdDate" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "PK_98419043dd704f54-9830ab78f8" PRIMARY KEY (id) + );` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE chat_message_feedback`) + } +} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index 3c3fa3966..9e28980e0 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -11,6 +11,7 @@ import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedT import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' +import { AddFeedback1707213601923 } from './1707213601923-AddFeedback' export const postgresMigrations = [ Init1693891895163, @@ -25,5 +26,6 @@ export const postgresMigrations = [ AddUsedToolsToChatMessage1699481607341, AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237, - AddVariableEntity1699325775451 + AddVariableEntity1699325775451, + AddFeedback1707213601923 ] diff --git a/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts b/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts new file mode 100644 index 000000000..b240ec1be --- /dev/null +++ b/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddFeedback1707213619308 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS "chat_message_feedback" ("id" varchar PRIMARY KEY NOT NULL, "chatflowid" varchar NOT NULL, "content" text, "chatId" varchar NOT NULL, "messageId" varchar NOT NULL, "rating" varchar NOT NULL, "createdDate" datetime NOT NULL DEFAULT (datetime('now')));` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE IF EXISTS "chat_message_feedback";`) + } +} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index c0ade080d..4e6dc72c4 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -11,6 +11,7 @@ import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedT import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' +import { AddFeedback1707213619308 } from './1707213619308-AddFeedback' export const sqliteMigrations = [ Init1693835579790, @@ -25,5 +26,6 @@ export const sqliteMigrations = [ AddUsedToolsToChatMessage1699481607341, AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237, - AddVariableEntity1699325775451 + AddVariableEntity1699325775451, + AddFeedback1707213619308 ] From 28b33f2c6b00da16ed03f7cfff9fa1d75060dfd7 Mon Sep 17 00:00:00 2001 From: Ilango Date: Mon, 12 Feb 2024 11:27:29 +0530 Subject: [PATCH 03/14] Add feedback endpoints --- .../src/database/entities/ChatMessage.ts | 7 ++- packages/server/src/index.ts | 45 +++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/server/src/database/entities/ChatMessage.ts b/packages/server/src/database/entities/ChatMessage.ts index 4054a26dd..f25f893c2 100644 --- a/packages/server/src/database/entities/ChatMessage.ts +++ b/packages/server/src/database/entities/ChatMessage.ts @@ -1,6 +1,7 @@ /* eslint-disable */ -import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn, Index } from 'typeorm' +import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn, Index, OneToOne, JoinColumn } from 'typeorm' import { IChatMessage, MessageType } from '../../Interface' +import { ChatMessageFeedback } from './ChatMessageFeedback' @Entity() export class ChatMessage implements IChatMessage { @@ -40,4 +41,8 @@ export class ChatMessage implements IChatMessage { @CreateDateColumn() createdDate: Date + + @OneToOne(() => ChatMessageFeedback) + @JoinColumn() + feedback?: ChatMessageFeedback } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index dbb5717df..f2f5fea7c 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -10,7 +10,7 @@ import logger from './utils/logger' import { expressRequestLogger } from './utils/logger' import { v4 as uuidv4 } from 'uuid' import OpenAI from 'openai' -import { Between, IsNull, FindOptionsWhere } from 'typeorm' +import { Between, IsNull, FindOptionsWhere, createQueryBuilder } from 'typeorm' import { IChatFlow, IncomingInput, @@ -20,6 +20,7 @@ import { ICredentialReturnResponse, chatType, IChatMessage, + IChatMessageFeedback, IDepthQueue, INodeDirectedGraph } from './Interface' @@ -54,6 +55,7 @@ import { getDataSource } from './DataSource' import { NodesPool } from './NodesPool' import { ChatFlow } from './database/entities/ChatFlow' import { ChatMessage } from './database/entities/ChatMessage' +import { ChatMessageFeedback } from './database/entities/ChatMessageFeedback' import { Credential } from './database/entities/Credential' import { Tool } from './database/entities/Tool' import { Assistant } from './database/entities/Assistant' @@ -512,6 +514,7 @@ export class App { const sessionId = req.query?.sessionId as string | undefined const startDate = req.query?.startDate as string | undefined const endDate = req.query?.endDate as string | undefined + const feedback = req.query?.feedback as boolean | undefined let chatTypeFilter = req.query?.chatType as chatType | undefined if (chatTypeFilter) { @@ -537,7 +540,8 @@ export class App { memoryType, sessionId, startDate, - endDate + endDate, + feedback ) return res.json(chatmessages) }) @@ -599,6 +603,24 @@ export class App { return res.json(results) }) + // ---------------------------------------- + // Chat Message Feedback + // ---------------------------------------- + this.app.get('/api/v1/feedback/:id', async (req: Request, res: Response) => {}) + + this.app.post('/api/v1/feedback/:id', async (req: Request, res: Response) => { + const body = req.body + const results = await this.addChatMessageFeedback(body) + return res.json(results) + }) + + this.app.put('/api/v1/feedback/:id', async (req: Request, res: Response) => { + const chatflowid = req.params.id + const body = req.body + const results = await this.addChatMessageFeedback(body) + return res.json(results) + }) + // ---------------------------------------- // Credentials // ---------------------------------------- @@ -1422,6 +1444,7 @@ export class App { * @param {string} sessionId * @param {string} startDate * @param {string} endDate + * @param {boolean} feedback */ async getChatMessage( chatflowid: string, @@ -1431,7 +1454,8 @@ export class App { memoryType?: string, sessionId?: string, startDate?: string, - endDate?: string + endDate?: string, + feedback?: boolean ): Promise { let fromDate if (startDate) fromDate = new Date(startDate) @@ -1450,6 +1474,9 @@ export class App { }, order: { createdDate: sortOrder === 'DESC' ? 'DESC' : 'ASC' + }, + relations: { + feedback } }) } @@ -1466,6 +1493,18 @@ export class App { return await this.AppDataSource.getRepository(ChatMessage).save(chatmessage) } + /** + * Method that adds feedback for a chat message. + * @param {Partial} chatMessageFeedback + */ + async addChatMessageFeedback(chatMessageFeedback: Partial): Promise { + const newFeedback = new ChatMessageFeedback() + Object.assign(newFeedback, chatMessageFeedback) + + const feedback = this.AppDataSource.getRepository(ChatMessageFeedback).create(newFeedback) + return await this.AppDataSource.getRepository(ChatMessageFeedback).save(feedback) + } + async upsertVector(req: Request, res: Response, isInternal: boolean = false) { try { const chatflowid = req.params.id From 26d5d6d6a2cfa67b5844b8ec647240826bc40641 Mon Sep 17 00:00:00 2001 From: Ilango Date: Thu, 15 Feb 2024 15:13:47 +0530 Subject: [PATCH 04/14] Save feedback id to message and update feedback --- packages/server/src/Interface.ts | 2 +- .../src/database/entities/ChatMessage.ts | 8 ++- .../database/entities/ChatMessageFeedback.ts | 3 -- .../sqlite/1707213619308-AddFeedback.ts | 2 +- .../1707986407818-AddFeedbackToChatMessage.ts | 20 +++++++ .../src/database/migrations/sqlite/index.ts | 4 +- packages/server/src/index.ts | 52 +++++++++++++++---- 7 files changed, 70 insertions(+), 21 deletions(-) create mode 100644 packages/server/src/database/migrations/sqlite/1707986407818-AddFeedbackToChatMessage.ts diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index aa5852b2b..c705cf79a 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -41,6 +41,7 @@ export interface IChatMessage { memoryType?: string sessionId?: string createdDate: Date + feedbackId?: string } export interface IChatMessageFeedback { @@ -48,7 +49,6 @@ export interface IChatMessageFeedback { content?: string chatflowid: string chatId: string - messageId: string rating: ChatMessageRatingType createdDate: Date } diff --git a/packages/server/src/database/entities/ChatMessage.ts b/packages/server/src/database/entities/ChatMessage.ts index f25f893c2..55a1c0e5c 100644 --- a/packages/server/src/database/entities/ChatMessage.ts +++ b/packages/server/src/database/entities/ChatMessage.ts @@ -1,7 +1,6 @@ /* eslint-disable */ -import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn, Index, OneToOne, JoinColumn } from 'typeorm' +import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn, Index } from 'typeorm' import { IChatMessage, MessageType } from '../../Interface' -import { ChatMessageFeedback } from './ChatMessageFeedback' @Entity() export class ChatMessage implements IChatMessage { @@ -42,7 +41,6 @@ export class ChatMessage implements IChatMessage { @CreateDateColumn() createdDate: Date - @OneToOne(() => ChatMessageFeedback) - @JoinColumn() - feedback?: ChatMessageFeedback + @Column({ nullable: true }) + feedbackId?: string } diff --git a/packages/server/src/database/entities/ChatMessageFeedback.ts b/packages/server/src/database/entities/ChatMessageFeedback.ts index 4011972db..972994f9e 100644 --- a/packages/server/src/database/entities/ChatMessageFeedback.ts +++ b/packages/server/src/database/entities/ChatMessageFeedback.ts @@ -17,9 +17,6 @@ export class ChatMessageFeedback implements IChatMessageFeedback { @Column() chatId: string - @Column() - messageId: string - @Column() rating: ChatMessageRatingType diff --git a/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts b/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts index b240ec1be..29ac76bcd 100644 --- a/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts +++ b/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts @@ -3,7 +3,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class AddFeedback1707213619308 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( - `CREATE TABLE IF NOT EXISTS "chat_message_feedback" ("id" varchar PRIMARY KEY NOT NULL, "chatflowid" varchar NOT NULL, "content" text, "chatId" varchar NOT NULL, "messageId" varchar NOT NULL, "rating" varchar NOT NULL, "createdDate" datetime NOT NULL DEFAULT (datetime('now')));` + `CREATE TABLE IF NOT EXISTS "chat_message_feedback" ("id" varchar PRIMARY KEY NOT NULL, "chatflowid" varchar NOT NULL, "content" text, "chatId" varchar NOT NULL, "rating" varchar NOT NULL, "createdDate" datetime NOT NULL DEFAULT (datetime('now')));` ) } diff --git a/packages/server/src/database/migrations/sqlite/1707986407818-AddFeedbackToChatMessage.ts b/packages/server/src/database/migrations/sqlite/1707986407818-AddFeedbackToChatMessage.ts new file mode 100644 index 000000000..24af4ada3 --- /dev/null +++ b/packages/server/src/database/migrations/sqlite/1707986407818-AddFeedbackToChatMessage.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddFeedbackToChatMessage1707986407818 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "temp_chat_message" ("id" varchar PRIMARY KEY NOT NULL, "role" varchar NOT NULL, "chatflowid" varchar NOT NULL, "content" text NOT NULL, "sourceDocuments" text, "usedTools" text, "fileAnnotations" text, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "chatType" VARCHAR NOT NULL DEFAULT 'INTERNAL', "chatId" VARCHAR NOT NULL, "memoryType" VARCHAR, "sessionId" VARCHAR, "feedbackId" varchar);` + ) + await queryRunner.query( + `INSERT INTO "temp_chat_message" ("id", "role", "chatflowid", "content", "sourceDocuments", "usedTools", "fileAnnotations", "createdDate", "chatType", "chatId", "memoryType", "sessionId") SELECT "id", "role", "chatflowid", "content", "sourceDocuments", "usedTools", "fileAnnotations", "createdDate", "chatType", "chatId", "memoryType", "sessionId" FROM "chat_message";` + ) + await queryRunner.query(`DROP TABLE "chat_message";`) + await queryRunner.query(`ALTER TABLE "temp_chat_message" RENAME TO "chat_message";`) + await queryRunner.query(`CREATE INDEX "IDX_e574527322272fd838f4f0f3d3" ON "chat_message" ("chatflowid") ;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE IF EXISTS "temp_chat_message";`) + await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "feedbackId";`) + } +} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index 4e6dc72c4..5a54a5cd2 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -12,6 +12,7 @@ import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryT import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' import { AddFeedback1707213619308 } from './1707213619308-AddFeedback' +import { AddFeedbackToChatMessage1707986407818 } from './1707986407818-AddFeedbackToChatMessage' export const sqliteMigrations = [ Init1693835579790, @@ -27,5 +28,6 @@ export const sqliteMigrations = [ AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237, AddVariableEntity1699325775451, - AddFeedback1707213619308 + AddFeedback1707213619308, + AddFeedbackToChatMessage1707986407818 ] diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index d63b82a9c..331b4fd0c 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -10,7 +10,7 @@ import logger from './utils/logger' import { expressRequestLogger } from './utils/logger' import { v4 as uuidv4 } from 'uuid' import OpenAI from 'openai' -import { Between, IsNull, FindOptionsWhere, createQueryBuilder } from 'typeorm' +import { Between, IsNull, FindOptionsWhere } from 'typeorm' import { IChatFlow, IncomingInput, @@ -606,18 +606,31 @@ export class App { // ---------------------------------------- // Chat Message Feedback // ---------------------------------------- - this.app.get('/api/v1/feedback/:id', async (req: Request, res: Response) => {}) + // Create new feedback this.app.post('/api/v1/feedback/:id', async (req: Request, res: Response) => { const body = req.body const results = await this.addChatMessageFeedback(body) return res.json(results) }) + // Update feedback this.app.put('/api/v1/feedback/:id', async (req: Request, res: Response) => { - const chatflowid = req.params.id const body = req.body - const results = await this.addChatMessageFeedback(body) + const chatMessageFeedback = await this.AppDataSource.getRepository(ChatMessageFeedback).findOneBy({ + id: req.params.id + }) + + if (!chatMessageFeedback) { + res.status(404).send(`Feedback ${req.params.id} not found`) + return + } + + const newChatMessageFeedback = new ChatMessageFeedback() + Object.assign(newChatMessageFeedback, body) + + this.AppDataSource.getRepository(ChatMessageFeedback).merge(chatMessageFeedback, newChatMessageFeedback) + const results = await this.AppDataSource.getRepository(ChatMessageFeedback).save(chatMessageFeedback) return res.json(results) }) @@ -1476,9 +1489,6 @@ export class App { }, order: { createdDate: sortOrder === 'DESC' ? 'DESC' : 'ASC' - }, - relations: { - feedback } }) } @@ -1495,18 +1505,40 @@ export class App { return await this.AppDataSource.getRepository(ChatMessage).save(chatmessage) } + async updateChatMessage(id: string, update: Partial) { + const chatMessage = await this.AppDataSource.getRepository(ChatMessage).findOneBy({ + id + }) + + if (!chatMessage) return + + const newChatMessage = new ChatMessage() + Object.assign(newChatMessage, update) + + this.AppDataSource.getRepository(ChatMessage).merge(chatMessage, newChatMessage) + return await this.AppDataSource.getRepository(ChatMessage).save(chatMessage) + } + /** - * Method that adds feedback for a chat message. + * Method that adds feedback for a chat message and updates the chat message with the feedback id. * @param {Partial} chatMessageFeedback */ - async addChatMessageFeedback(chatMessageFeedback: Partial): Promise { + async addChatMessageFeedback(chatMessageFeedback: Partial & { messageId: string }): Promise { + const messageId = chatMessageFeedback.messageId const newFeedback = new ChatMessageFeedback() Object.assign(newFeedback, chatMessageFeedback) const feedback = this.AppDataSource.getRepository(ChatMessageFeedback).create(newFeedback) - return await this.AppDataSource.getRepository(ChatMessageFeedback).save(feedback) + const results = await this.AppDataSource.getRepository(ChatMessageFeedback).save(feedback) + + // use the message id to update the chat message with feedback id + await this.updateChatMessage(messageId, { feedbackId: results.id }) + + return results } + async updateChatMessageFeedback(id: string, update: Partial) {} + async upsertVector(req: Request, res: Response, isInternal: boolean = false) { try { const chatflowid = req.params.id From 3bb2b398964942a81d9bbb8df034328799ab62f6 Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 20 Feb 2024 14:38:36 +0530 Subject: [PATCH 05/14] Fix merge conflict and update how feedback is saved/retrieved --- packages/server/src/Interface.ts | 2 +- .../src/database/entities/ChatMessage.ts | 3 - .../database/entities/ChatMessageFeedback.ts | 15 +- .../sqlite/1707213619308-AddFeedback.ts | 4 +- .../1707986407818-AddFeedbackToChatMessage.ts | 20 --- .../src/database/migrations/sqlite/index.ts | 4 +- packages/server/src/index.ts | 160 +++++++++++++----- packages/ui/src/api/chatmessage.js | 5 +- packages/ui/src/api/feedback.js | 7 + .../ui/src/ui-component/cards/StatsCard.js | 29 ++++ .../ui-component/dialog/ViewMessagesDialog.js | 46 ++++- .../ui/src/ui-component/extended/Feedback.js | 71 ++++++++ 12 files changed, 289 insertions(+), 77 deletions(-) delete mode 100644 packages/server/src/database/migrations/sqlite/1707986407818-AddFeedbackToChatMessage.ts create mode 100644 packages/ui/src/api/feedback.js create mode 100644 packages/ui/src/ui-component/cards/StatsCard.js create mode 100644 packages/ui/src/ui-component/extended/Feedback.js diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index c705cf79a..aa5852b2b 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -41,7 +41,6 @@ export interface IChatMessage { memoryType?: string sessionId?: string createdDate: Date - feedbackId?: string } export interface IChatMessageFeedback { @@ -49,6 +48,7 @@ export interface IChatMessageFeedback { content?: string chatflowid: string chatId: string + messageId: string rating: ChatMessageRatingType createdDate: Date } diff --git a/packages/server/src/database/entities/ChatMessage.ts b/packages/server/src/database/entities/ChatMessage.ts index 55a1c0e5c..4054a26dd 100644 --- a/packages/server/src/database/entities/ChatMessage.ts +++ b/packages/server/src/database/entities/ChatMessage.ts @@ -40,7 +40,4 @@ export class ChatMessage implements IChatMessage { @CreateDateColumn() createdDate: Date - - @Column({ nullable: true }) - feedbackId?: string } diff --git a/packages/server/src/database/entities/ChatMessageFeedback.ts b/packages/server/src/database/entities/ChatMessageFeedback.ts index 972994f9e..811f3104d 100644 --- a/packages/server/src/database/entities/ChatMessageFeedback.ts +++ b/packages/server/src/database/entities/ChatMessageFeedback.ts @@ -1,8 +1,9 @@ /* eslint-disable */ -import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn, Index } from 'typeorm' -import { ChatMessageRatingType, IChatMessageFeedback } from '../../Interface' +import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn, Index, Unique } from 'typeorm' +import { IChatMessageFeedback, ChatMessageRatingType } from '../../Interface' @Entity() +@Unique(['messageId']) export class ChatMessageFeedback implements IChatMessageFeedback { @PrimaryGeneratedColumn('uuid') id: string @@ -11,15 +12,19 @@ export class ChatMessageFeedback implements IChatMessageFeedback { @Column() chatflowid: string - @Column({ type: 'text' }) - content?: string - + @Index() @Column() chatId: string @Column() + messageId: string + + @Column({ nullable: true }) rating: ChatMessageRatingType + @Column({ nullable: true, type: 'text' }) + content?: string + @CreateDateColumn() createdDate: Date } diff --git a/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts b/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts index 29ac76bcd..b9002697d 100644 --- a/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts +++ b/packages/server/src/database/migrations/sqlite/1707213619308-AddFeedback.ts @@ -3,8 +3,10 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class AddFeedback1707213619308 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( - `CREATE TABLE IF NOT EXISTS "chat_message_feedback" ("id" varchar PRIMARY KEY NOT NULL, "chatflowid" varchar NOT NULL, "content" text, "chatId" varchar NOT NULL, "rating" varchar NOT NULL, "createdDate" datetime NOT NULL DEFAULT (datetime('now')));` + `CREATE TABLE IF NOT EXISTS "chat_message_feedback" ("id" varchar PRIMARY KEY NOT NULL, "chatflowid" varchar NOT NULL, "chatId" varchar NOT NULL, "messageId" varchar NOT NULL, "rating" varchar NOT NULL, "content" text, "createdDate" datetime NOT NULL DEFAULT (datetime('now')));` ) + await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_e574527322272fd838f4f0f3d3" ON "chat_message_feedback" ("chatflowid") ;`) + await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_e574527322272fd838f4f0f3d3" ON "chat_message_feedback" ("chatId") ;`) } public async down(queryRunner: QueryRunner): Promise { diff --git a/packages/server/src/database/migrations/sqlite/1707986407818-AddFeedbackToChatMessage.ts b/packages/server/src/database/migrations/sqlite/1707986407818-AddFeedbackToChatMessage.ts deleted file mode 100644 index 24af4ada3..000000000 --- a/packages/server/src/database/migrations/sqlite/1707986407818-AddFeedbackToChatMessage.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm' - -export class AddFeedbackToChatMessage1707986407818 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "temp_chat_message" ("id" varchar PRIMARY KEY NOT NULL, "role" varchar NOT NULL, "chatflowid" varchar NOT NULL, "content" text NOT NULL, "sourceDocuments" text, "usedTools" text, "fileAnnotations" text, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "chatType" VARCHAR NOT NULL DEFAULT 'INTERNAL', "chatId" VARCHAR NOT NULL, "memoryType" VARCHAR, "sessionId" VARCHAR, "feedbackId" varchar);` - ) - await queryRunner.query( - `INSERT INTO "temp_chat_message" ("id", "role", "chatflowid", "content", "sourceDocuments", "usedTools", "fileAnnotations", "createdDate", "chatType", "chatId", "memoryType", "sessionId") SELECT "id", "role", "chatflowid", "content", "sourceDocuments", "usedTools", "fileAnnotations", "createdDate", "chatType", "chatId", "memoryType", "sessionId" FROM "chat_message";` - ) - await queryRunner.query(`DROP TABLE "chat_message";`) - await queryRunner.query(`ALTER TABLE "temp_chat_message" RENAME TO "chat_message";`) - await queryRunner.query(`CREATE INDEX "IDX_e574527322272fd838f4f0f3d3" ON "chat_message" ("chatflowid") ;`) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP TABLE IF EXISTS "temp_chat_message";`) - await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "feedbackId";`) - } -} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index 5a54a5cd2..4e6dc72c4 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -12,7 +12,6 @@ import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryT import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' import { AddFeedback1707213619308 } from './1707213619308-AddFeedback' -import { AddFeedbackToChatMessage1707986407818 } from './1707986407818-AddFeedbackToChatMessage' export const sqliteMigrations = [ Init1693835579790, @@ -28,6 +27,5 @@ export const sqliteMigrations = [ AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237, AddVariableEntity1699325775451, - AddFeedback1707213619308, - AddFeedbackToChatMessage1707986407818 + AddFeedback1707213619308 ] diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 498b2e2ce..ab998a15c 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -10,7 +10,7 @@ import logger from './utils/logger' import { expressRequestLogger } from './utils/logger' import { v4 as uuidv4 } from 'uuid' import OpenAI from 'openai' -import { FindOptionsWhere, MoreThanOrEqual, LessThanOrEqual } from 'typeorm' +import { FindOptionsWhere, MoreThanOrEqual, LessThanOrEqual, IsNull, Between } from 'typeorm' import { IChatFlow, IncomingInput, @@ -22,7 +22,8 @@ import { IChatMessage, IChatMessageFeedback, IDepthQueue, - INodeDirectedGraph + INodeDirectedGraph, + ChatMessageRatingType } from './Interface' import { getNodeModulesPackagePath, @@ -170,7 +171,8 @@ export class App { '/api/v1/components-credentials-icon/', '/api/v1/chatflows-streaming', '/api/v1/openai-assistants-file', - '/api/v1/ip' + '/api/v1/ip', + '/api/v1/feedback' ] this.app.use((req, res, next) => { if (req.url.includes('/api/v1/')) { @@ -610,31 +612,62 @@ export class App { // Chat Message Feedback // ---------------------------------------- - // Create new feedback + // Get all chatmessage feedback from chatflowid + this.app.get('/api/v1/feedback/:id', async (req: Request, res: Response) => { + const chatflowid = req.params.id + const chatId = req.query?.chatId as string | undefined + const sortOrder = req.query?.order as string | undefined + const startDate = req.query?.startDate as string | undefined + const endDate = req.query?.endDate as string | undefined + + const feedback = await this.getChatMessageFeedback(chatflowid, chatId, sortOrder, startDate, endDate) + + return res.json(feedback) + }) + + // Add chatmessage feedback for chatflowid this.app.post('/api/v1/feedback/:id', async (req: Request, res: Response) => { const body = req.body const results = await this.addChatMessageFeedback(body) return res.json(results) }) - // Update feedback + // Update chatmessage feedback for id this.app.put('/api/v1/feedback/:id', async (req: Request, res: Response) => { + const id = req.params.id const body = req.body - const chatMessageFeedback = await this.AppDataSource.getRepository(ChatMessageFeedback).findOneBy({ - id: req.params.id + await this.updateChatMessageFeedback(id, body) + return res.json({ status: 'OK' }) + }) + + // ---------------------------------------- + // stats + // ---------------------------------------- + // + // get stats for showing in chatflow + this.app.get('/api/v1/stats/:id', async (req: Request, res: Response) => { + const chatflowid = req.params.id + const chatTypeFilter = chatType.EXTERNAL + + const totalMessages = await this.AppDataSource.getRepository(ChatMessage).count({ + where: { + chatflowid, + chatType: chatTypeFilter + } }) - if (!chatMessageFeedback) { - res.status(404).send(`Feedback ${req.params.id} not found`) - return + const chatMessageFeedbackRepo = this.AppDataSource.getRepository(ChatMessageFeedback) + + const totalFeedback = await chatMessageFeedbackRepo.count() + const positiveFeedback = await chatMessageFeedbackRepo.countBy({ rating: ChatMessageRatingType.THUMBS_UP }) + + const results = { + totalMessages, + totalFeedback, + positiveFeedback } - const newChatMessageFeedback = new ChatMessageFeedback() - Object.assign(newChatMessageFeedback, body) - - this.AppDataSource.getRepository(ChatMessageFeedback).merge(chatMessageFeedback, newChatMessageFeedback) - const results = await this.AppDataSource.getRepository(ChatMessageFeedback).save(chatMessageFeedback) - return res.json(results) + res.json(results) }) // ---------------------------------------- @@ -1497,6 +1530,28 @@ export class App { let toDate if (endDate) toDate = setDateToStartOrEndOfDay(endDate, 'end') + if (feedback) { + const messages = await this.AppDataSource.getRepository(ChatMessage) + .createQueryBuilder('chat_message') + .leftJoinAndMapOne('chat_message.feedback', ChatMessageFeedback, 'feedback', 'feedback.messageId = chat_message.id') + .where('chat_message.chatflowid = :chatflowid', { chatflowid }) + .andWhere(chatType ? 'chat_message.chatType = :chatType' : 'TRUE', { chatType }) + .andWhere(chatId ? 'chat_message.chatId = :chatId' : 'TRUE', { chatId }) + .andWhere(memoryType ? 'chat_message.memoryType = :memoryType' : 'TRUE', { + memoryType: memoryType ?? (chatId ? IsNull() : undefined) + }) + .andWhere(sessionId ? 'chat_message.sessionId = :sessionId' : 'TRUE', { + sessionId: sessionId ?? (chatId ? IsNull() : undefined) + }) + .andWhere(fromDate && toDate ? 'chat_message.createdDate = :createdDate' : 'TRUE', { + createdDate: toDate && fromDate ? Between(fromDate, toDate) : undefined + }) + .orderBy('chat_message.createdDate', sortOrder === 'DESC' ? 'DESC' : 'ASC') + .getMany() + + return messages + } + return await this.AppDataSource.getRepository(ChatMessage).find({ where: { chatflowid, @@ -1526,39 +1581,61 @@ export class App { return await this.AppDataSource.getRepository(ChatMessage).save(chatmessage) } - async updateChatMessage(id: string, update: Partial) { - const chatMessage = await this.AppDataSource.getRepository(ChatMessage).findOneBy({ - id + /** + * Method that get chat messages. + * @param {string} chatflowid + * @param {string} sortOrder + * @param {string} chatId + * @param {string} startDate + * @param {string} endDate + */ + async getChatMessageFeedback( + chatflowid: string, + chatId?: string, + sortOrder: string = 'ASC', + startDate?: string, + endDate?: string + ): Promise { + let fromDate + if (startDate) fromDate = new Date(startDate) + + let toDate + if (endDate) toDate = new Date(endDate) + return await this.AppDataSource.getRepository(ChatMessageFeedback).find({ + where: { + chatflowid, + chatId, + createdDate: toDate && fromDate ? Between(fromDate, toDate) : undefined + }, + order: { + createdDate: sortOrder === 'DESC' ? 'DESC' : 'ASC' + } }) - - if (!chatMessage) return - - const newChatMessage = new ChatMessage() - Object.assign(newChatMessage, update) - - this.AppDataSource.getRepository(ChatMessage).merge(chatMessage, newChatMessage) - return await this.AppDataSource.getRepository(ChatMessage).save(chatMessage) } /** - * Method that adds feedback for a chat message and updates the chat message with the feedback id. + * Method that add chat message feedback. * @param {Partial} chatMessageFeedback */ - async addChatMessageFeedback(chatMessageFeedback: Partial & { messageId: string }): Promise { - const messageId = chatMessageFeedback.messageId - const newFeedback = new ChatMessageFeedback() - Object.assign(newFeedback, chatMessageFeedback) + async addChatMessageFeedback(chatMessageFeedback: Partial): Promise { + const newChatMessageFeedback = new ChatMessageFeedback() + Object.assign(newChatMessageFeedback, chatMessageFeedback) - const feedback = this.AppDataSource.getRepository(ChatMessageFeedback).create(newFeedback) - const results = await this.AppDataSource.getRepository(ChatMessageFeedback).save(feedback) - - // use the message id to update the chat message with feedback id - await this.updateChatMessage(messageId, { feedbackId: results.id }) - - return results + const feedback = this.AppDataSource.getRepository(ChatMessageFeedback).create(newChatMessageFeedback) + return await this.AppDataSource.getRepository(ChatMessageFeedback).save(feedback) } - async updateChatMessageFeedback(id: string, update: Partial) {} + /** + * Method that updates chat message feedback. + * @param {string} id + * @param {Partial} chatMessageFeedback + */ + async updateChatMessageFeedback(id: string, chatMessageFeedback: Partial) { + const newChatMessageFeedback = new ChatMessageFeedback() + Object.assign(newChatMessageFeedback, chatMessageFeedback) + + await this.AppDataSource.getRepository(ChatMessageFeedback).update({ id }, chatMessageFeedback) + } async upsertVector(req: Request, res: Response, isInternal: boolean = false) { try { @@ -1941,7 +2018,7 @@ export class App { sessionId, createdDate: userMessageDateTime } - await this.addChatMessage(userMessage) + const newMessage = await this.addChatMessage(userMessage) let resultText = '' if (result.text) resultText = result.text @@ -1974,6 +2051,7 @@ export class App { // Prepare response result.chatId = chatId + if (newMessage.id && !isInternal) result.messageId = newMessage.id if (sessionId) result.sessionId = sessionId if (memoryType) result.memoryType = memoryType diff --git a/packages/ui/src/api/chatmessage.js b/packages/ui/src/api/chatmessage.js index 5f1a4badb..7b084c464 100644 --- a/packages/ui/src/api/chatmessage.js +++ b/packages/ui/src/api/chatmessage.js @@ -1,8 +1,9 @@ import client from './client' const getInternalChatmessageFromChatflow = (id) => client.get(`/internal-chatmessage/${id}`) -const getAllChatmessageFromChatflow = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'DESC', ...params } }) -const getChatmessageFromPK = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'ASC', ...params } }) +const getAllChatmessageFromChatflow = (id, params = {}) => + client.get(`/chatmessage/${id}`, { params: { order: 'DESC', feedback: true, ...params } }) +const getChatmessageFromPK = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'ASC', feedback: true, ...params } }) const deleteChatmessage = (id, params = {}) => client.delete(`/chatmessage/${id}`, { params: { ...params } }) export default { diff --git a/packages/ui/src/api/feedback.js b/packages/ui/src/api/feedback.js new file mode 100644 index 000000000..5b32ca26d --- /dev/null +++ b/packages/ui/src/api/feedback.js @@ -0,0 +1,7 @@ +import client from './client' + +const getStatsFromChatflow = (id) => client.get(`/stats/${id}`) + +export default { + getStatsFromChatflow +} diff --git a/packages/ui/src/ui-component/cards/StatsCard.js b/packages/ui/src/ui-component/cards/StatsCard.js new file mode 100644 index 000000000..694335059 --- /dev/null +++ b/packages/ui/src/ui-component/cards/StatsCard.js @@ -0,0 +1,29 @@ +import PropTypes from 'prop-types' + +import { useSelector } from 'react-redux' +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import Typography from '@mui/material/Typography' + +const StatsCard = ({ title, stat }) => { + const customization = useSelector((state) => state.customization) + return ( + + + + {title} + + + {stat} + + + + ) +} + +StatsCard.propTypes = { + title: PropTypes.string, + stat: PropTypes.string +} + +export default StatsCard diff --git a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js index cadd4abd9..f52723b2f 100644 --- a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js +++ b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js @@ -37,12 +37,15 @@ import { CodeBlock } from 'ui-component/markdown/CodeBlock' import SourceDocDialog from 'ui-component/dialog/SourceDocDialog' import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown' import { StyledButton } from 'ui-component/button/StyledButton' +import StatsCard from 'ui-component/cards/StatsCard' +import Feedback from 'ui-component/extended/Feedback' // store import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' // API import chatmessageApi from 'api/chatmessage' +import feedbackApi from 'api/feedback' import useApi from 'hooks/useApi' import useConfirm from 'hooks/useConfirm' @@ -83,6 +86,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { const [chatlogs, setChatLogs] = useState([]) const [allChatlogs, setAllChatLogs] = useState([]) const [chatMessages, setChatMessages] = useState([]) + const [stats, setStats] = useState([]) const [selectedMessageIndex, setSelectedMessageIndex] = useState(0) const [sourceDialogOpen, setSourceDialogOpen] = useState(false) const [sourceDialogProps, setSourceDialogProps] = useState({}) @@ -92,6 +96,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { const getChatmessageApi = useApi(chatmessageApi.getAllChatmessageFromChatflow) const getChatmessageFromPKApi = useApi(chatmessageApi.getChatmessageFromPK) + const getStatsApi = useApi(feedbackApi.getStatsFromChatflow) const onStartDateSelected = (date) => { setStartDate(date) @@ -366,9 +371,16 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [getChatmessageApi.data]) + useEffect(() => { + if (getStatsApi.data) { + setStats(getStatsApi.data) + } + }, [getStatsApi.data]) + useEffect(() => { if (dialogProps.chatflow) { getChatmessageApi.request(dialogProps.chatflow.id) + getStatsApi.request(dialogProps.chatflow.id) } return () => { @@ -410,7 +422,33 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { <> -
+
+ + + +
+
From Date { })}
)} + {message.type === 'apiMessage' && message.feedback ? ( + + ) : null}
) diff --git a/packages/ui/src/ui-component/extended/Feedback.js b/packages/ui/src/ui-component/extended/Feedback.js new file mode 100644 index 000000000..5a943ec57 --- /dev/null +++ b/packages/ui/src/ui-component/extended/Feedback.js @@ -0,0 +1,71 @@ +import { Alert, IconButton } from '@mui/material' +import { useTheme } from '@mui/material/styles' +import PropTypes from 'prop-types' + +const ThumbsUpIcon = () => { + return ( + + + + + ) +} + +const ThumbsDownIcon = () => { + return ( + + + + + ) +} + +const Feedback = ({ content, rating }) => { + const theme = useTheme() + + return ( +
+ {content ? ( + : } + severity={rating === 'THUMBS_UP' ? 'success' : 'error'} + style={{ marginBottom: 14 }} + variant='outlined' + > + {content ? {content} : null} + + ) : ( + + {rating === 'THUMBS_UP' ? : } + + )} +
+ ) +} + +Feedback.propTypes = { + rating: PropTypes.oneOf(['THUMBS_UP', 'THUMBS_DOWN']), + content: PropTypes.string +} + +export default Feedback From 78677d9ee51b4427fd793b4b0233125d99698e0d Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 20 Feb 2024 15:23:15 +0530 Subject: [PATCH 06/14] Add dialog for controlling chat feedback settings --- packages/ui/src/menu-items/settings.js | 20 ++- .../ui-component/dialog/ChatFeedbackDialog.js | 137 ++++++++++++++++++ packages/ui/src/ui-component/switch/Switch.js | 17 ++- packages/ui/src/views/canvas/CanvasHeader.js | 15 ++ 4 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 packages/ui/src/ui-component/dialog/ChatFeedbackDialog.js diff --git a/packages/ui/src/menu-items/settings.js b/packages/ui/src/menu-items/settings.js index 1e0f58ddb..5003392c9 100644 --- a/packages/ui/src/menu-items/settings.js +++ b/packages/ui/src/menu-items/settings.js @@ -1,8 +1,17 @@ // assets -import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff } from '@tabler/icons' +import { + IconTrash, + IconFileUpload, + IconFileExport, + IconCopy, + IconSearch, + IconMessage, + IconPictureInPictureOff, + IconThumbUp +} from '@tabler/icons' // constant -const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff } +const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff, IconThumbUp } // ==============================|| SETTINGS MENU ITEMS ||============================== // @@ -25,6 +34,13 @@ const settings = { url: '', icon: icons.IconMessage }, + { + id: 'chatFeedback', + title: 'Chat Feedback', + type: 'item', + url: '', + icon: icons.IconThumbUp + }, { id: 'duplicateChatflow', title: 'Duplicate Chatflow', diff --git a/packages/ui/src/ui-component/dialog/ChatFeedbackDialog.js b/packages/ui/src/ui-component/dialog/ChatFeedbackDialog.js new file mode 100644 index 000000000..a88c6f352 --- /dev/null +++ b/packages/ui/src/ui-component/dialog/ChatFeedbackDialog.js @@ -0,0 +1,137 @@ +import { createPortal } from 'react-dom' +import { useDispatch } from 'react-redux' +import { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions' + +// material-ui +import { Button, Dialog, DialogContent, DialogTitle, DialogActions, Box } from '@mui/material' +import { IconX } from '@tabler/icons' + +// Project import +import { StyledButton } from 'ui-component/button/StyledButton' +import { SwitchInput } from 'ui-component/switch/Switch' + +// store +import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' +import useNotifier from 'utils/useNotifier' + +// API +import chatflowsApi from 'api/chatflows' + +const ChatFeedbackDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const portalElement = document.getElementById('portal') + const dispatch = useDispatch() + + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false) + const [chatbotConfig, setChatbotConfig] = useState({}) + + const handleChange = (value) => { + setChatFeedbackStatus(value) + } + + const onSave = async () => { + try { + let value = { + chatFeedback: { + status: chatFeedbackStatus + } + } + chatbotConfig.chatFeedback = value.chatFeedback + const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, { + chatbotConfig: JSON.stringify(chatbotConfig) + }) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Chat Feedback Settings Saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data }) + } + onConfirm() + } catch (error) { + const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` + enqueueSnackbar({ + message: `Failed to save Chat Feedback Settings: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + + useEffect(() => { + if (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) { + let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig) + setChatbotConfig(chatbotConfig || {}) + if (chatbotConfig.chatFeedback) { + setChatFeedbackStatus(chatbotConfig.chatFeedback.status) + } + } + + return () => {} + }, [dialogProps]) + + useEffect(() => { + if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) + else dispatch({ type: HIDE_CANVAS_DIALOG }) + return () => dispatch({ type: HIDE_CANVAS_DIALOG }) + }, [show, dispatch]) + + const component = show ? ( + + + {dialogProps.title || 'Chat Feedback'} + + + + + + + + + + Save + + + + ) : null + + return createPortal(component, portalElement) +} + +ChatFeedbackDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default ChatFeedbackDialog diff --git a/packages/ui/src/ui-component/switch/Switch.js b/packages/ui/src/ui-component/switch/Switch.js index 16a923f1c..50ace1197 100644 --- a/packages/ui/src/ui-component/switch/Switch.js +++ b/packages/ui/src/ui-component/switch/Switch.js @@ -1,13 +1,21 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import PropTypes from 'prop-types' -import { FormControl, Switch } from '@mui/material' +import { FormControl, Switch, Typography } from '@mui/material' -export const SwitchInput = ({ value, onChange, disabled = false }) => { +export const SwitchInput = ({ label, value, onChange, disabled = false }) => { const [myValue, setMyValue] = useState(!!value ?? false) + useEffect(() => { + setMyValue(value) + }, [value]) + return ( <> - + + {label && {label}} { } SwitchInput.propTypes = { + label: PropTypes.string, value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), onChange: PropTypes.func, disabled: PropTypes.bool diff --git a/packages/ui/src/views/canvas/CanvasHeader.js b/packages/ui/src/views/canvas/CanvasHeader.js index 85408cd8a..af9d5e2f7 100644 --- a/packages/ui/src/views/canvas/CanvasHeader.js +++ b/packages/ui/src/views/canvas/CanvasHeader.js @@ -17,6 +17,7 @@ import APICodeDialog from 'views/chatflows/APICodeDialog' import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog' import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog' import StarterPromptsDialog from 'ui-component/dialog/StarterPromptsDialog' +import ChatFeedbackDialog from 'ui-component/dialog/ChatFeedbackDialog' // API import chatflowsApi from 'api/chatflows' @@ -50,6 +51,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({}) const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false) const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({}) + const [chatFeedbackDialogOpen, setChatFeedbackDialogOpen] = useState(false) + const [chatFeedbackDialogProps, setChatFeedbackDialogProps] = useState({}) const updateChatflowApi = useApi(chatflowsApi.updateChatflow) const canvas = useSelector((state) => state.canvas) @@ -65,6 +68,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl chatflow: chatflow }) setConversationStartersDialogOpen(true) + } else if (setting === 'chatFeedback') { + setChatFeedbackDialogProps({ + title: `Chat Feedback - ${chatflow.name}`, + chatflow: chatflow + }) + setChatFeedbackDialogOpen(true) } else if (setting === 'analyseChatflow') { setAnalyseDialogProps({ title: 'Analyse Chatflow', @@ -391,6 +400,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl onConfirm={() => setConversationStartersDialogOpen(false)} onCancel={() => setConversationStartersDialogOpen(false)} /> + setChatFeedbackDialogOpen(false)} + onCancel={() => setChatFeedbackDialogOpen(false)} + /> Date: Wed, 21 Feb 2024 14:22:50 +0530 Subject: [PATCH 07/14] Fix text color in feedback stats --- packages/ui/src/ui-component/cards/StatsCard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/ui-component/cards/StatsCard.js b/packages/ui/src/ui-component/cards/StatsCard.js index 694335059..57e8205df 100644 --- a/packages/ui/src/ui-component/cards/StatsCard.js +++ b/packages/ui/src/ui-component/cards/StatsCard.js @@ -10,10 +10,10 @@ const StatsCard = ({ title, stat }) => { return ( - + {title} - + {stat} From 68d56be7c0a1485d7cc0127452ab9c865e8cd70e Mon Sep 17 00:00:00 2001 From: Ilango Date: Thu, 22 Feb 2024 16:13:46 +0530 Subject: [PATCH 08/14] Remove related feedback and update stats when clearing chat in view messages --- packages/server/src/index.ts | 4 ++++ packages/ui/src/ui-component/dialog/ViewMessagesDialog.js | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index ab998a15c..abfc66ad3 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -604,6 +604,10 @@ export class App { if (sessionId) deleteOptions.sessionId = sessionId if (chatType) deleteOptions.chatType = chatType + // remove all related feedback records + const feedbackDeleteOptions: FindOptionsWhere = { chatId } + await this.AppDataSource.getRepository(ChatMessageFeedback).delete(feedbackDeleteOptions) + const results = await this.AppDataSource.getRepository(ChatMessage).delete(deleteOptions) return res.json(results) }) diff --git a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js index f52723b2f..a9490d273 100644 --- a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js +++ b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js @@ -214,6 +214,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { } }) getChatmessageApi.request(chatflowid) + getStatsApi.request(chatflowid) // update stats } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ From c5c396a0ed2dbd6aa9469e9e5eb20b3d033e8f79 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 27 Feb 2024 01:00:24 +0800 Subject: [PATCH 09/14] add feedback to exported chat --- packages/ui/src/ui-component/dialog/ViewMessagesDialog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js index a9490d273..10b9f2768 100644 --- a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js +++ b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js @@ -138,6 +138,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { if (chatmsg.sourceDocuments) msg.sourceDocuments = JSON.parse(chatmsg.sourceDocuments) if (chatmsg.usedTools) msg.usedTools = JSON.parse(chatmsg.usedTools) if (chatmsg.fileAnnotations) msg.fileAnnotations = JSON.parse(chatmsg.fileAnnotations) + if (chatmsg.feedback) msg.feedback = chatmsg.feedback?.content if (!Object.prototype.hasOwnProperty.call(obj, chatPK)) { obj[chatPK] = { From 256e3250869a8aeb2f46e0edbc477551cac10e80 Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 27 Feb 2024 13:11:52 +0530 Subject: [PATCH 10/14] Fix incorrect stats --- packages/server/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index abfc66ad3..6f1f03a93 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -662,8 +662,8 @@ export class App { const chatMessageFeedbackRepo = this.AppDataSource.getRepository(ChatMessageFeedback) - const totalFeedback = await chatMessageFeedbackRepo.count() - const positiveFeedback = await chatMessageFeedbackRepo.countBy({ rating: ChatMessageRatingType.THUMBS_UP }) + const totalFeedback = await chatMessageFeedbackRepo.count({ where: { chatflowid } }) + const positiveFeedback = await chatMessageFeedbackRepo.countBy({ chatflowid, rating: ChatMessageRatingType.THUMBS_UP }) const results = { totalMessages, From 5698a626186e56827afea4f86df4b3d6d40ca8d8 Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 27 Feb 2024 15:35:49 +0530 Subject: [PATCH 11/14] Fix date filters not working --- packages/server/src/index.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index f57f6c001..893f50542 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1542,14 +1542,11 @@ export class App { .where('chat_message.chatflowid = :chatflowid', { chatflowid }) .andWhere(chatType ? 'chat_message.chatType = :chatType' : 'TRUE', { chatType }) .andWhere(chatId ? 'chat_message.chatId = :chatId' : 'TRUE', { chatId }) - .andWhere(memoryType ? 'chat_message.memoryType = :memoryType' : 'TRUE', { - memoryType: memoryType ?? (chatId ? IsNull() : undefined) - }) - .andWhere(sessionId ? 'chat_message.sessionId = :sessionId' : 'TRUE', { - sessionId: sessionId ?? (chatId ? IsNull() : undefined) - }) - .andWhere(fromDate && toDate ? 'chat_message.createdDate = :createdDate' : 'TRUE', { - createdDate: toDate && fromDate ? Between(fromDate, toDate) : undefined + .andWhere(memoryType ? 'chat_message.memoryType = :memoryType' : 'TRUE', { memoryType }) + .andWhere(sessionId ? 'chat_message.sessionId = :sessionId' : 'TRUE', { sessionId }) + .andWhere('chat_message.createdDate BETWEEN :fromDate AND :toDate', { + fromDate: fromDate ?? new Date().setMonth(new Date().getMonth() - 1), + toDate: toDate ?? new Date() }) .orderBy('chat_message.createdDate', sortOrder === 'DESC' ? 'DESC' : 'ASC') .getMany() From c684cec59648394f38ab8524dd1ed7f9f5f01a60 Mon Sep 17 00:00:00 2001 From: Ilango Date: Tue, 27 Feb 2024 16:58:13 +0530 Subject: [PATCH 12/14] Update how chat message feedback are queried for view messages dialog --- packages/server/src/index.ts | 39 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 893f50542..4fed5fe1f 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -1536,21 +1536,36 @@ export class App { if (endDate) toDate = setDateToStartOrEndOfDay(endDate, 'end') if (feedback) { - const messages = await this.AppDataSource.getRepository(ChatMessage) - .createQueryBuilder('chat_message') + const query = this.AppDataSource.getRepository(ChatMessage).createQueryBuilder('chat_message') + + // do the join with chat message feedback based on messageId for each chat message in the chatflow + query .leftJoinAndMapOne('chat_message.feedback', ChatMessageFeedback, 'feedback', 'feedback.messageId = chat_message.id') .where('chat_message.chatflowid = :chatflowid', { chatflowid }) - .andWhere(chatType ? 'chat_message.chatType = :chatType' : 'TRUE', { chatType }) - .andWhere(chatId ? 'chat_message.chatId = :chatId' : 'TRUE', { chatId }) - .andWhere(memoryType ? 'chat_message.memoryType = :memoryType' : 'TRUE', { memoryType }) - .andWhere(sessionId ? 'chat_message.sessionId = :sessionId' : 'TRUE', { sessionId }) - .andWhere('chat_message.createdDate BETWEEN :fromDate AND :toDate', { - fromDate: fromDate ?? new Date().setMonth(new Date().getMonth() - 1), - toDate: toDate ?? new Date() - }) - .orderBy('chat_message.createdDate', sortOrder === 'DESC' ? 'DESC' : 'ASC') - .getMany() + // based on which parameters are available add `andWhere` clauses to the query + if (chatType) { + query.andWhere('chat_message.chatType = :chatType', { chatType }) + } + if (chatId) { + query.andWhere('chat_message.chatId = :chatId', { chatId }) + } + if (memoryType) { + query.andWhere('chat_message.memoryType = :memoryType', { memoryType }) + } + if (sessionId) { + query.andWhere('chat_message.sessionId = :sessionId', { sessionId }) + } + + // set date range + query.andWhere('chat_message.createdDate BETWEEN :fromDate AND :toDate', { + fromDate: fromDate ?? new Date().setMonth(new Date().getMonth() - 1), + toDate: toDate ?? new Date() + }) + // sort + query.orderBy('chat_message.createdDate', sortOrder === 'DESC' ? 'DESC' : 'ASC') + + const messages = await query.getMany() return messages } From f0d129d22d84ea015d81ebfd05a5e6d4efc88818 Mon Sep 17 00:00:00 2001 From: Ilango Date: Wed, 28 Feb 2024 22:16:13 +0530 Subject: [PATCH 13/14] Pin mui versions since they were causing styling issues --- packages/ui/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index df32d7fac..872077a57 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -14,10 +14,10 @@ "@emotion/cache": "^11.4.0", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", - "@mui/icons-material": "^5.0.3", - "@mui/lab": "^5.0.0-alpha.156", - "@mui/material": "^5.15.0", - "@mui/x-data-grid": "^6.8.0", + "@mui/icons-material": "5.0.3", + "@mui/lab": "5.0.0-alpha.156", + "@mui/material": "5.15.0", + "@mui/x-data-grid": "6.8.0", "@tabler/icons": "^1.39.1", "@uiw/codemirror-theme-sublime": "^4.21.21", "@uiw/codemirror-theme-vscode": "^4.21.21", From 9c10822546c84b54f40c97d294800c289a8990c7 Mon Sep 17 00:00:00 2001 From: Ilango Date: Thu, 29 Feb 2024 05:10:52 +0530 Subject: [PATCH 14/14] Update stats card text in view messages dialog --- packages/ui/src/ui-component/dialog/ViewMessagesDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js index cf1842712..a0b3e396e 100644 --- a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js +++ b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.js @@ -473,7 +473,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { marginRight: 8 }} > - +