From d699f5ddfa0b94efeee98c7016318d472b2e3b2b Mon Sep 17 00:00:00 2001 From: Ajay Lamba Date: Sun, 24 Dec 2023 18:48:56 +0530 Subject: [PATCH 1/3] Enhancement: Add user feedback for responses Added basic feedback mechanism for responses generated by the chatbot. The feedbacks are stored in DynamoDB which can be queried to do analysis as required by admin users. In future we can add a UI page to display the feedbacks, but for now these are being stored and manual analysis would be required. The feedbacks are not adding to the learning of the chatbot. --- .../chatbot-dynamodb-tables/index.ts | 22 +++++ .../functions/api-handler/index.py | 2 + .../api-handler/routes/user_feedback.py | 35 +++++++ lib/chatbot-api/index.ts | 6 ++ lib/chatbot-api/rest-api.ts | 5 + .../python/genai_core/user_feedback.py | 36 ++++++++ .../react-app/package-lock.json | 92 +++++++++++++++++++ lib/user-interface/react-app/package.json | 4 + .../src/common/api-client/api-client.ts | 10 ++ .../common/api-client/user-feedback-client.ts | 29 ++++++ .../src/components/chatbot/chat-message.tsx | 24 +++++ .../react-app/src/components/chatbot/chat.tsx | 15 +++ .../src/components/chatbot/multi-chat.tsx | 15 +++ .../react-app/src/styles/chat.module.scss | 34 +++++++ 14 files changed, 329 insertions(+) create mode 100644 lib/chatbot-api/functions/api-handler/routes/user_feedback.py create mode 100644 lib/shared/layers/python-sdk/python/genai_core/user_feedback.py create mode 100644 lib/user-interface/react-app/src/common/api-client/user-feedback-client.ts diff --git a/lib/chatbot-api/chatbot-dynamodb-tables/index.ts b/lib/chatbot-api/chatbot-dynamodb-tables/index.ts index 28f6cefb..ca1e9b31 100644 --- a/lib/chatbot-api/chatbot-dynamodb-tables/index.ts +++ b/lib/chatbot-api/chatbot-dynamodb-tables/index.ts @@ -4,7 +4,9 @@ import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; export class ChatBotDynamoDBTables extends Construct { public readonly sessionsTable: dynamodb.Table; + public readonly userFeedbackTable: dynamodb.Table; public readonly byUserIdIndex: string = "byUserId"; + public readonly bySessionIdIndex: string = "bySessionId"; constructor(scope: Construct, id: string) { super(scope, id); @@ -28,6 +30,26 @@ export class ChatBotDynamoDBTables extends Construct { partitionKey: { name: "UserId", type: dynamodb.AttributeType.STRING }, }); + const userFeedbackTable = new dynamodb.Table(this, "UserFeedbackTable", { + partitionKey: { + name: "FeedbackId", + type: dynamodb.AttributeType.STRING, + }, + sortKey: { + name: "SessionId", + type: dynamodb.AttributeType.STRING, + }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + encryption: dynamodb.TableEncryption.AWS_MANAGED, + removalPolicy: cdk.RemovalPolicy.DESTROY + }); + + userFeedbackTable.addGlobalSecondaryIndex({ + indexName: this.bySessionIdIndex, + partitionKey: { name: "SessionId", type: dynamodb.AttributeType.STRING} + }); + this.sessionsTable = sessionsTable; + this.userFeedbackTable = userFeedbackTable; } } diff --git a/lib/chatbot-api/functions/api-handler/index.py b/lib/chatbot-api/functions/api-handler/index.py index 6b255334..a3a268cf 100644 --- a/lib/chatbot-api/functions/api-handler/index.py +++ b/lib/chatbot-api/functions/api-handler/index.py @@ -23,6 +23,7 @@ from routes.semantic_search import router as semantic_search_router from routes.documents import router as documents_router from routes.kendra import router as kendra_router +from routes.user_feedback import router as user_feedback_router tracer = Tracer() logger = Logger() @@ -45,6 +46,7 @@ app.include_router(semantic_search_router) app.include_router(documents_router) app.include_router(kendra_router) +app.include_router(user_feedback_router) diff --git a/lib/chatbot-api/functions/api-handler/routes/user_feedback.py b/lib/chatbot-api/functions/api-handler/routes/user_feedback.py new file mode 100644 index 00000000..a2715c6a --- /dev/null +++ b/lib/chatbot-api/functions/api-handler/routes/user_feedback.py @@ -0,0 +1,35 @@ +import genai_core.types +import genai_core.auth +import genai_core.user_feedback +from pydantic import BaseModel +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler.api_gateway import Router + +tracer = Tracer() +router = Router() +logger = Logger() + + +class CreateUserFeedbackRequest(BaseModel): + sessionId: str + key: str + feedback: str + +@router.put("/user-feedback") +@tracer.capture_method +def user_feedback(): + data: dict = router.current_event.json_body + request = CreateUserFeedbackRequest(**data) + + session_id = request.sessionId + key = request.key + feedback = request.feedback + + user_id = genai_core.auth.get_user_id(router) + + if user_id is None: + raise genai_core.types.CommonError("User not found") + + result = genai_core.user_feedback.add_user_feedback(session_id, user_id, key, feedback) + + return {"ok": True, "data": result} diff --git a/lib/chatbot-api/index.ts b/lib/chatbot-api/index.ts index f1dda76e..a7a6057c 100644 --- a/lib/chatbot-api/index.ts +++ b/lib/chatbot-api/index.ts @@ -29,6 +29,8 @@ export class ChatBotApi extends Construct { public readonly messagesTopic: sns.Topic; public readonly sessionsTable: dynamodb.Table; public readonly byUserIdIndex: string; + public readonly userFeedbackTable: dynamodb.Table; + public readonly bySessionIdIndex: string; public readonly filesBucket: s3.Bucket; constructor(scope: Construct, id: string, props: ChatBotApiProps) { @@ -41,6 +43,8 @@ export class ChatBotApi extends Construct { ...props, sessionsTable: chatTables.sessionsTable, byUserIdIndex: chatTables.byUserIdIndex, + userFeedbackTable: chatTables.userFeedbackTable, + bySessionIdIndex: chatTables.bySessionIdIndex }); const webSocketApi = new WebSocketApi(this, "WebSocketApi", props); @@ -50,6 +54,8 @@ export class ChatBotApi extends Construct { this.messagesTopic = webSocketApi.messagesTopic; this.sessionsTable = chatTables.sessionsTable; this.byUserIdIndex = chatTables.byUserIdIndex; + this.userFeedbackTable = chatTables.userFeedbackTable; + this.bySessionIdIndex = chatTables.bySessionIdIndex; this.filesBucket = chatBuckets.filesBucket; } } diff --git a/lib/chatbot-api/rest-api.ts b/lib/chatbot-api/rest-api.ts index 9bc4b274..851df538 100644 --- a/lib/chatbot-api/rest-api.ts +++ b/lib/chatbot-api/rest-api.ts @@ -20,6 +20,8 @@ export interface RestApiProps { readonly userPool: cognito.UserPool; readonly sessionsTable: dynamodb.Table; readonly byUserIdIndex: string; + readonly userFeedbackTable: dynamodb.Table; + readonly bySessionIdIndex: string; readonly modelsParameter: ssm.StringParameter; readonly models: SageMakerModelEndpoint[]; } @@ -57,6 +59,8 @@ export class RestApi extends Construct { API_KEYS_SECRETS_ARN: props.shared.apiKeysSecret.secretArn, SESSIONS_TABLE_NAME: props.sessionsTable.tableName, SESSIONS_BY_USER_ID_INDEX_NAME: props.byUserIdIndex, + USER_FEEDBACK_TABLE_NAME: props.userFeedbackTable.tableName, + USER_FEEDBACK_BY_SESSION_ID_INDEX_NAME: props.bySessionIdIndex, UPLOAD_BUCKET_NAME: props.ragEngines?.uploadBucket?.bucketName ?? "", PROCESSING_BUCKET_NAME: props.ragEngines?.processingBucket?.bucketName ?? "", @@ -247,6 +251,7 @@ export class RestApi extends Construct { props.shared.configParameter.grantRead(apiHandler); props.modelsParameter.grantRead(apiHandler); props.sessionsTable.grantReadWriteData(apiHandler); + props.userFeedbackTable.grantReadWriteData(apiHandler); props.ragEngines?.uploadBucket.grantReadWrite(apiHandler); props.ragEngines?.processingBucket.grantReadWrite(apiHandler); diff --git a/lib/shared/layers/python-sdk/python/genai_core/user_feedback.py b/lib/shared/layers/python-sdk/python/genai_core/user_feedback.py new file mode 100644 index 00000000..26fa105b --- /dev/null +++ b/lib/shared/layers/python-sdk/python/genai_core/user_feedback.py @@ -0,0 +1,36 @@ +import os +import uuid +import boto3 +from datetime import datetime + +dynamodb = boto3.resource("dynamodb") + +USER_FEEDBACK_TABLE_NAME = os.environ.get("USER_FEEDBACK_TABLE_NAME") + +if USER_FEEDBACK_TABLE_NAME: + table = dynamodb.Table(USER_FEEDBACK_TABLE_NAME) + +def add_user_feedback( + session_id: str, + user_id: str, + key: str, + feedback: str +): + feedback_id = str(uuid.uuid4()) + timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + + item = { + "FeedbackId": feedback_id, + "SessionId": session_id, + "UserId": user_id, + "Key": key, + "Feedback": feedback, + "CreatedAt": timestamp + } + + response = table.put_item(Item=item) + print(response) + + return { + "id": feedback_id + } diff --git a/lib/user-interface/react-app/package-lock.json b/lib/user-interface/react-app/package-lock.json index 9a763654..70c467ef 100644 --- a/lib/user-interface/react-app/package-lock.json +++ b/lib/user-interface/react-app/package-lock.json @@ -12,10 +12,14 @@ "@cloudscape-design/components": "^3.0.405", "@cloudscape-design/design-tokens": "^3.0.28", "@cloudscape-design/global-styles": "^1.0.13", + "@fortawesome/fontawesome-svg-core": "^6.4.2", + "@fortawesome/free-solid-svg-icons": "^6.4.2", + "@fortawesome/react-fontawesome": "^0.2.0", "aws-amplify": "^5.3.11", "luxon": "^3.4.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^4.12.0", "react-json-view-lite": "^0.9.8", "react-markdown": "^9.0.0", "react-router-dom": "^6.15.0", @@ -8332,6 +8336,51 @@ "tslib": "^2.4.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", + "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", + "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", + "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -16877,6 +16926,14 @@ "react": ">=16" } }, + "node_modules/react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -25893,6 +25950,35 @@ "tslib": "^2.4.0" } }, + "@fortawesome/fontawesome-common-types": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", + "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", + "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.5.1" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", + "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.5.1" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "requires": { + "prop-types": "^15.8.1" + } + }, "@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -32241,6 +32327,12 @@ "integrity": "sha512-rOFGh3KgC2Ot66DmVCcT1p8lVJCmmCjzGE5WK/KsyDFi43wpIbW1PYcr04cQ3mbF4LaIkY6SpK7k1DOgwtpUXA==", "requires": {} }, + "react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/lib/user-interface/react-app/package.json b/lib/user-interface/react-app/package.json index de5dfaf5..f876759d 100644 --- a/lib/user-interface/react-app/package.json +++ b/lib/user-interface/react-app/package.json @@ -16,10 +16,14 @@ "@cloudscape-design/components": "^3.0.405", "@cloudscape-design/design-tokens": "^3.0.28", "@cloudscape-design/global-styles": "^1.0.13", + "@fortawesome/fontawesome-svg-core": "^6.4.2", + "@fortawesome/free-solid-svg-icons": "^6.4.2", + "@fortawesome/react-fontawesome": "^0.2.0", "aws-amplify": "^5.3.11", "luxon": "^3.4.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^4.12.0", "react-json-view-lite": "^0.9.8", "react-markdown": "^9.0.0", "remark-gfm": "^4.0.0", diff --git a/lib/user-interface/react-app/src/common/api-client/api-client.ts b/lib/user-interface/react-app/src/common/api-client/api-client.ts index 26b91a70..a30983ee 100644 --- a/lib/user-interface/react-app/src/common/api-client/api-client.ts +++ b/lib/user-interface/react-app/src/common/api-client/api-client.ts @@ -9,6 +9,7 @@ import { SessionsClient } from "./sessions-client"; import { SemanticSearchClient } from "./semantic-search-client"; import { DocumentsClient } from "./documents-client"; import { KendraClient } from "./kendra-client"; +import { UserFeedbackClient } from "./user-feedback-client"; export class ApiClient { private _healthClient: HealthClient | undefined; @@ -21,6 +22,7 @@ export class ApiClient { private _semanticSearchClient: SemanticSearchClient | undefined; private _documentsClient: DocumentsClient | undefined; private _kendraClient: KendraClient | undefined; + private _userFeedbackClient: UserFeedbackClient | undefined; public get health() { if (!this._healthClient) { @@ -102,5 +104,13 @@ export class ApiClient { return this._kendraClient; } + public get userFeedback() { + if(!this._userFeedbackClient) { + this._userFeedbackClient = new UserFeedbackClient(this._appConfig); + } + + return this._userFeedbackClient; + } + constructor(protected _appConfig: AppConfig) {} } diff --git a/lib/user-interface/react-app/src/common/api-client/user-feedback-client.ts b/lib/user-interface/react-app/src/common/api-client/user-feedback-client.ts new file mode 100644 index 00000000..6cd23c9b --- /dev/null +++ b/lib/user-interface/react-app/src/common/api-client/user-feedback-client.ts @@ -0,0 +1,29 @@ +import { ApiResult } from "../types"; +import { ApiClientBase } from "./api-client-base"; + +export class UserFeedbackClient extends ApiClientBase { + + async addUserFeedback(params: { + sessionId: string; + key: number; + feedback: string; + }): Promise> { + try { + const headers = await this.getHeaders(); + const result = await fetch(this.getApiUrl("/user-feedback"), { + method: "PUT", + headers, + body: JSON.stringify({ + ...params + }), + }); + + if(!result.ok) { + console.log("Result: ", result); + } + return result.json(); + } catch (error) { + return this.error(error); + } + } +} diff --git a/lib/user-interface/react-app/src/components/chatbot/chat-message.tsx b/lib/user-interface/react-app/src/components/chatbot/chat-message.tsx index 4519c02e..90f29447 100644 --- a/lib/user-interface/react-app/src/components/chatbot/chat-message.tsx +++ b/lib/user-interface/react-app/src/components/chatbot/chat-message.tsx @@ -27,11 +27,14 @@ import { getSignedUrl } from "./utils"; import "react-json-view-lite/dist/index.css"; import "../../styles/app.scss"; +import { FaThumbsUp, FaThumbsDown } from "react-icons/fa"; export interface ChatMessageProps { message: ChatBotHistoryItem; configuration?: ChatBotConfiguration; showMetadata?: boolean; + onThumbsUp: () => void; + onThumbsDown: () => void; } export default function ChatMessage(props: ChatMessageProps) { @@ -40,6 +43,7 @@ export default function ChatMessage(props: ChatMessageProps) { const [files, setFiles] = useState([] as ImageFile[]); const [documentIndex, setDocumentIndex] = useState("0"); const [promptIndex, setPromptIndex] = useState("0"); + const [selectedIcon, setSelectedIcon] = useState<'thumbsUp' | 'thumbsDown' | null>(null); useEffect(() => { const getSignedUrls = async () => { @@ -270,6 +274,26 @@ export default function ChatMessage(props: ChatMessageProps) { }, }} /> +
+ {(selectedIcon === 'thumbsUp' || selectedIcon === null) && ( + { + props.onThumbsUp(); + setSelectedIcon('thumbsUp'); + }} + /> + )} + {(selectedIcon === 'thumbsDown' || selectedIcon === null) && ( + { + props.onThumbsDown(); + setSelectedIcon('thumbsDown'); + }} + /> + )} +
)} {loading && ( diff --git a/lib/user-interface/react-app/src/components/chatbot/chat.tsx b/lib/user-interface/react-app/src/components/chatbot/chat.tsx index 4638ca00..b57dd89f 100644 --- a/lib/user-interface/react-app/src/components/chatbot/chat.tsx +++ b/lib/user-interface/react-app/src/components/chatbot/chat.tsx @@ -64,6 +64,19 @@ export default function Chat(props: { sessionId?: string }) { })(); }, [appContext, props.sessionId]); + const handleFeedback = (feedbackType: 'thumbsUp' | 'thumbsDown', idx: number, message: ChatBotHistoryItem) => { + if (message.metadata.sessionId) { + addUserFeedback(message.metadata.sessionId as string, idx, feedbackType); + } + }; + + const addUserFeedback = async (sessionId: string, key: number, feedback: string) => { + if (!appContext) return; + + const apiClient = new ApiClient(appContext); + await apiClient.userFeedback.addUserFeedback({sessionId, key, feedback}); + }; + return (
@@ -72,6 +85,8 @@ export default function Chat(props: { sessionId?: string }) { key={idx} message={message} configuration={configuration} + onThumbsUp={() => handleFeedback('thumbsUp', idx, message)} + onThumbsDown={() => handleFeedback('thumbsDown', idx, message)} /> ))} diff --git a/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx b/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx index 4a2cb6e7..cbe1476d 100644 --- a/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx +++ b/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx @@ -268,6 +268,19 @@ export default function MultiChat() { [ReadyState.UNINSTANTIATED]: "Uninstantiated", }[readyState]; + const handleFeedback = (feedbackType: 'thumbsUp' | 'thumbsDown', idx: number, message: ChatBotHistoryItem) => { + if (message.metadata.sessionId) { + addUserFeedback(message.metadata.sessionId as string, idx, feedbackType); + } + }; + + const addUserFeedback = async (sessionId: string, key: number, feedback: string) => { + if (!appContext) return; + + const apiClient = new ApiClient(appContext); + await apiClient.userFeedback.addUserFeedback({sessionId, key, feedback}); + }; + return (
@@ -418,6 +431,8 @@ export default function MultiChat() { message={message} configuration={chatSessions[idx].configuration} showMetadata={showMetadata} + onThumbsUp={() => handleFeedback('thumbsUp', idx, message)} + onThumbsDown={() => handleFeedback('thumbsDown', idx, message)} /> ))} diff --git a/lib/user-interface/react-app/src/styles/chat.module.scss b/lib/user-interface/react-app/src/styles/chat.module.scss index 8a551623..83e75734 100644 --- a/lib/user-interface/react-app/src/styles/chat.module.scss +++ b/lib/user-interface/react-app/src/styles/chat.module.scss @@ -157,3 +157,37 @@ .markdownTable tr:nth-child(even) { background-color: awsui.$color-background-container-content; } + +.thumbsContainer { + display: flex; + align-items: center; + margin-top: 8px; +} + +.thumbsIcon { + cursor: pointer; + margin-right: 10px; + opacity: 0.5; +} + +/* Styles for thumbs up icon. Should be compatible with dark theme */ +.thumbsUp { + color: #539fe5; +} + +/* Styles for thumbs down icon. Should be compatible with dark theme */ +.thumbsDown { + color: #539fe5; +} + +/* Style for clicked state */ +.clicked { + opacity: 0.5; + pointer-events: none; /* Disable pointer events for a clicked icon */ +} + +/* Styles for selected icon */ +.thumbsIcon.selected { + opacity: 1 !important; + pointer-events: none; /* Disable pointer events for the selected icon */ +} \ No newline at end of file From cebefd8ffcf22cf5d28f3db3bc92a5b03b27ed80 Mon Sep 17 00:00:00 2001 From: Ajay Lamba Date: Sun, 11 Feb 2024 19:35:33 +0530 Subject: [PATCH 2/3] Earlier we were storing the user feedback in DynamoDB table with minimal information. This was good for beta phase but was not useful if we want to do analysis on it using other services like Athena/Glue etc. As part of this change, we have changed the mechanism and the feedback is being stored in S3 bucket now. We are storing the feedback in the following format: { "feedbackId": "", "sessionId": "", "userId": "", "key": "", "prompt": "", "completion": "", "model": "", "feedback": "thumbsUp/thumbsDown", "createdAt": "" } This feedback can be used by AWS Athena/Glue etc to do analysis and also to train the model if required. We are keeping one object per feedback in S3 'STANDARD_IA' class. --- .../chatbot-dynamodb-tables/index.ts | 22 -------- lib/chatbot-api/chatbot-s3-buckets/index.ts | 25 +++++++++ .../api-handler/routes/user_feedback.py | 32 ++++++------ lib/chatbot-api/index.ts | 9 ++-- lib/chatbot-api/rest-api.ts | 6 ++- lib/chatbot-api/schema/schema.graphql | 14 +++++ .../python/genai_core/user_feedback.py | 51 ++++++++++++------- .../common/api-client/user-feedback-client.ts | 50 +++++++++--------- .../react-app/src/components/chatbot/chat.tsx | 18 +++++-- .../src/components/chatbot/multi-chat.tsx | 27 +++++++--- .../react-app/src/components/chatbot/types.ts | 9 ++++ 11 files changed, 166 insertions(+), 97 deletions(-) diff --git a/lib/chatbot-api/chatbot-dynamodb-tables/index.ts b/lib/chatbot-api/chatbot-dynamodb-tables/index.ts index 774017c1..7b4b7726 100644 --- a/lib/chatbot-api/chatbot-dynamodb-tables/index.ts +++ b/lib/chatbot-api/chatbot-dynamodb-tables/index.ts @@ -4,9 +4,7 @@ import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; export class ChatBotDynamoDBTables extends Construct { public readonly sessionsTable: dynamodb.Table; - public readonly userFeedbackTable: dynamodb.Table; public readonly byUserIdIndex: string = "byUserId"; - public readonly bySessionIdIndex: string = "bySessionId"; constructor(scope: Construct, id: string) { super(scope, id); @@ -31,26 +29,6 @@ export class ChatBotDynamoDBTables extends Construct { partitionKey: { name: "UserId", type: dynamodb.AttributeType.STRING }, }); - const userFeedbackTable = new dynamodb.Table(this, "UserFeedbackTable", { - partitionKey: { - name: "FeedbackId", - type: dynamodb.AttributeType.STRING, - }, - sortKey: { - name: "SessionId", - type: dynamodb.AttributeType.STRING, - }, - billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, - encryption: dynamodb.TableEncryption.AWS_MANAGED, - removalPolicy: cdk.RemovalPolicy.DESTROY - }); - - userFeedbackTable.addGlobalSecondaryIndex({ - indexName: this.bySessionIdIndex, - partitionKey: { name: "SessionId", type: dynamodb.AttributeType.STRING} - }); - this.sessionsTable = sessionsTable; - this.userFeedbackTable = userFeedbackTable; } } diff --git a/lib/chatbot-api/chatbot-s3-buckets/index.ts b/lib/chatbot-api/chatbot-s3-buckets/index.ts index 79ec464b..662badf3 100644 --- a/lib/chatbot-api/chatbot-s3-buckets/index.ts +++ b/lib/chatbot-api/chatbot-s3-buckets/index.ts @@ -5,6 +5,7 @@ import { NagSuppressions } from "cdk-nag"; export class ChatBotS3Buckets extends Construct { public readonly filesBucket: s3.Bucket; + public readonly userFeedbackBucket: s3.Bucket; constructor(scope: Construct, id: string) { super(scope, id); @@ -39,7 +40,31 @@ export class ChatBotS3Buckets extends Construct { ], }); + const userFeedbackBucket = new s3.Bucket(this, "UserFeedbackBucket", { + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + transferAcceleration: true, + enforceSSL: true, + serverAccessLogsBucket: logsBucket, + cors: [ + { + allowedHeaders: ["*"], + allowedMethods: [ + s3.HttpMethods.PUT, + s3.HttpMethods.POST, + s3.HttpMethods.GET, + s3.HttpMethods.HEAD, + ], + allowedOrigins: ["*"], + exposedHeaders: ["ETag"], + maxAge: 3000, + }, + ], + }); + this.filesBucket = filesBucket; + this.userFeedbackBucket = userFeedbackBucket; /** * CDK NAG suppression diff --git a/lib/chatbot-api/functions/api-handler/routes/user_feedback.py b/lib/chatbot-api/functions/api-handler/routes/user_feedback.py index a2715c6a..8098f8f1 100644 --- a/lib/chatbot-api/functions/api-handler/routes/user_feedback.py +++ b/lib/chatbot-api/functions/api-handler/routes/user_feedback.py @@ -3,7 +3,7 @@ import genai_core.user_feedback from pydantic import BaseModel from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.event_handler.api_gateway import Router +from aws_lambda_powertools.event_handler.appsync import Router tracer = Tracer() router = Router() @@ -14,22 +14,24 @@ class CreateUserFeedbackRequest(BaseModel): sessionId: str key: str feedback: str + prompt: str + completion: str + model: str + -@router.put("/user-feedback") +@router.resolver(field_name="addUserFeedback") @tracer.capture_method -def user_feedback(): - data: dict = router.current_event.json_body - request = CreateUserFeedbackRequest(**data) +def user_feedback(input: dict): + request = CreateUserFeedbackRequest(**input) + + userId = genai_core.auth.get_user_id(router) - session_id = request.sessionId - key = request.key - feedback = request.feedback - - user_id = genai_core.auth.get_user_id(router) - - if user_id is None: + if userId is None: raise genai_core.types.CommonError("User not found") + + result = genai_core.user_feedback.add_user_feedback( + request.sessionId, request.key, request.feedback, request.prompt, request.completion, request.model, userId) - result = genai_core.user_feedback.add_user_feedback(session_id, user_id, key, feedback) - - return {"ok": True, "data": result} + return { + "feedback_id": result["feedback_id"], + } diff --git a/lib/chatbot-api/index.ts b/lib/chatbot-api/index.ts index aea5997d..55406b42 100644 --- a/lib/chatbot-api/index.ts +++ b/lib/chatbot-api/index.ts @@ -31,9 +31,8 @@ export class ChatBotApi extends Construct { public readonly messagesTopic: sns.Topic; public readonly sessionsTable: dynamodb.Table; public readonly byUserIdIndex: string; - public readonly userFeedbackTable: dynamodb.Table; - public readonly bySessionIdIndex: string; public readonly filesBucket: s3.Bucket; + public readonly userFeedbackBucket: s3.Bucket; public readonly graphqlApi: appsync.GraphqlApi; constructor(scope: Construct, id: string, props: ChatBotApiProps) { @@ -89,8 +88,7 @@ export class ChatBotApi extends Construct { sessionsTable: chatTables.sessionsTable, byUserIdIndex: chatTables.byUserIdIndex, api, - userFeedbackTable: chatTables.userFeedbackTable, - bySessionIdIndex: chatTables.bySessionIdIndex + userFeedbackBucket: chatBuckets.userFeedbackBucket, }); const realtimeBackend = new RealtimeGraphqlApiBackend(this, "Realtime", { @@ -118,8 +116,7 @@ export class ChatBotApi extends Construct { this.messagesTopic = realtimeBackend.messagesTopic; this.sessionsTable = chatTables.sessionsTable; this.byUserIdIndex = chatTables.byUserIdIndex; - this.userFeedbackTable = chatTables.userFeedbackTable; - this.bySessionIdIndex = chatTables.bySessionIdIndex; + this.userFeedbackBucket = chatBuckets.userFeedbackBucket; this.filesBucket = chatBuckets.filesBucket; this.graphqlApi = api; diff --git a/lib/chatbot-api/rest-api.ts b/lib/chatbot-api/rest-api.ts index ac57abb8..a0ac6271 100644 --- a/lib/chatbot-api/rest-api.ts +++ b/lib/chatbot-api/rest-api.ts @@ -14,6 +14,7 @@ import { Shared } from "../shared"; import * as appsync from "aws-cdk-lib/aws-appsync"; import { parse } from "graphql"; import { readFileSync } from "fs"; +import * as s3 from "aws-cdk-lib/aws-s3"; export interface ApiResolversProps { readonly shared: Shared; @@ -22,8 +23,7 @@ export interface ApiResolversProps { readonly userPool: cognito.UserPool; readonly sessionsTable: dynamodb.Table; readonly byUserIdIndex: string; - readonly userFeedbackTable: dynamodb.Table; - readonly bySessionIdIndex: string; + readonly userFeedbackBucket: s3.Bucket; readonly modelsParameter: ssm.StringParameter; readonly models: SageMakerModelEndpoint[]; readonly api: appsync.GraphqlApi; @@ -64,6 +64,7 @@ export class ApiResolvers extends Construct { API_KEYS_SECRETS_ARN: props.shared.apiKeysSecret.secretArn, SESSIONS_TABLE_NAME: props.sessionsTable.tableName, SESSIONS_BY_USER_ID_INDEX_NAME: props.byUserIdIndex, + USER_FEEDBACK_BUCKET_NAME: props.userFeedbackBucket?.bucketName ?? "", UPLOAD_BUCKET_NAME: props.ragEngines?.uploadBucket?.bucketName ?? "", PROCESSING_BUCKET_NAME: props.ragEngines?.processingBucket?.bucketName ?? "", @@ -257,6 +258,7 @@ export class ApiResolvers extends Construct { props.shared.configParameter.grantRead(apiHandler); props.modelsParameter.grantRead(apiHandler); props.sessionsTable.grantReadWriteData(apiHandler); + props.userFeedbackBucket.grantReadWrite(apiHandler); props.ragEngines?.uploadBucket.grantReadWrite(apiHandler); props.ragEngines?.processingBucket.grantReadWrite(apiHandler); diff --git a/lib/chatbot-api/schema/schema.graphql b/lib/chatbot-api/schema/schema.graphql index bb10fd60..b9d7eee3 100644 --- a/lib/chatbot-api/schema/schema.graphql +++ b/lib/chatbot-api/schema/schema.graphql @@ -89,6 +89,10 @@ type DocumentResult @aws_cognito_user_pools { status: String } +type UserFeedbackResult @aws_cognito_user_pools { + feedback_id: String! +} + input DocumentSubscriptionStatusInput { workspaceId: String! documentId: String! @@ -235,6 +239,15 @@ type SessionHistoryItem @aws_cognito_user_pools { metadata: String } +input UserFeedbackInput { + sessionId: String! + key: Int! + feedback: String! + prompt: String! + completion: String! + model: String! +} + input TextDocumentInput { workspaceId: String! title: String! @@ -296,6 +309,7 @@ type Mutation { deleteWorkspace(workspaceId: String!): Boolean @aws_cognito_user_pools addTextDocument(input: TextDocumentInput!): DocumentResult @aws_cognito_user_pools + addUserFeedback(input: UserFeedbackInput!): UserFeedbackResult @aws_cognito_user_pools addQnADocument(input: QnADocumentInput!): DocumentResult @aws_cognito_user_pools setDocumentSubscriptionStatus( diff --git a/lib/shared/layers/python-sdk/python/genai_core/user_feedback.py b/lib/shared/layers/python-sdk/python/genai_core/user_feedback.py index 26fa105b..e0ce9f84 100644 --- a/lib/shared/layers/python-sdk/python/genai_core/user_feedback.py +++ b/lib/shared/layers/python-sdk/python/genai_core/user_feedback.py @@ -1,36 +1,51 @@ import os import uuid import boto3 +import json +from pydantic import BaseModel from datetime import datetime dynamodb = boto3.resource("dynamodb") +s3_client = boto3.client("s3") -USER_FEEDBACK_TABLE_NAME = os.environ.get("USER_FEEDBACK_TABLE_NAME") +USER_FEEDBACK_BUCKET_NAME = os.environ.get("USER_FEEDBACK_BUCKET_NAME") -if USER_FEEDBACK_TABLE_NAME: - table = dynamodb.Table(USER_FEEDBACK_TABLE_NAME) def add_user_feedback( - session_id: str, - user_id: str, + sessionId: str, key: str, - feedback: str + feedback: str, + prompt: str, + completion: str, + model: str, + userId: str ): - feedback_id = str(uuid.uuid4()) + feedbackId = str(uuid.uuid4()) timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") - + item = { - "FeedbackId": feedback_id, - "SessionId": session_id, - "UserId": user_id, - "Key": key, - "Feedback": feedback, - "CreatedAt": timestamp + "feedbackId": feedbackId, + "sessionId": sessionId, + "userId": userId, + "key": key, + "prompt": prompt, + "completion": completion, + "model": model, + "feedback": feedback, + "createdAt": timestamp } - - response = table.put_item(Item=item) + + response = s3_client.put_object( + Bucket=USER_FEEDBACK_BUCKET_NAME, + Key=feedbackId, + Body=json.dumps(item), + ContentType="application/json", + StorageClass='STANDARD_IA', + ) print(response) - + return { - "id": feedback_id + "feedback_id": feedbackId } + + \ No newline at end of file diff --git a/lib/user-interface/react-app/src/common/api-client/user-feedback-client.ts b/lib/user-interface/react-app/src/common/api-client/user-feedback-client.ts index 6cd23c9b..70452cb9 100644 --- a/lib/user-interface/react-app/src/common/api-client/user-feedback-client.ts +++ b/lib/user-interface/react-app/src/common/api-client/user-feedback-client.ts @@ -1,29 +1,31 @@ -import { ApiResult } from "../types"; -import { ApiClientBase } from "./api-client-base"; +import { GraphQLResult } from "@aws-amplify/api-graphql"; +import { API, GraphQLQuery } from "@aws-amplify/api"; +import { AddUserFeedbackMutation } from "../../API.ts"; +import { + addUserFeedback +} from "../../graphql/mutations.ts"; +import { FeedbackData } from "../../components/chatbot/types.ts"; -export class UserFeedbackClient extends ApiClientBase { +export class UserFeedbackClient { async addUserFeedback(params: { - sessionId: string; - key: number; - feedback: string; - }): Promise> { - try { - const headers = await this.getHeaders(); - const result = await fetch(this.getApiUrl("/user-feedback"), { - method: "PUT", - headers, - body: JSON.stringify({ - ...params - }), - }); - - if(!result.ok) { - console.log("Result: ", result); - } - return result.json(); - } catch (error) { - return this.error(error); - } + feedbackData: FeedbackData + } + ): Promise>> { + const result = API.graphql>({ + query: addUserFeedback, + variables: { + input: { + sessionId: params.feedbackData.sessionId, + key: params.feedbackData.key, + feedback: params.feedbackData.feedback, + prompt: params.feedbackData.prompt, + completion: params.feedbackData.completion, + model: params.feedbackData.model, + }, + }, + }); + return result; } + } diff --git a/lib/user-interface/react-app/src/components/chatbot/chat.tsx b/lib/user-interface/react-app/src/components/chatbot/chat.tsx index ecc29a96..e888da02 100644 --- a/lib/user-interface/react-app/src/components/chatbot/chat.tsx +++ b/lib/user-interface/react-app/src/components/chatbot/chat.tsx @@ -3,6 +3,7 @@ import { ChatBotConfiguration, ChatBotHistoryItem, ChatBotMessageType, + FeedbackData, } from "./types"; import { SpaceBetween, StatusIndicator } from "@cloudscape-design/components"; import { v4 as uuidv4 } from "uuid"; @@ -81,15 +82,26 @@ export default function Chat(props: { sessionId?: string }) { const handleFeedback = (feedbackType: 'thumbsUp' | 'thumbsDown', idx: number, message: ChatBotHistoryItem) => { if (message.metadata.sessionId) { - addUserFeedback(message.metadata.sessionId as string, idx, feedbackType); + const prompt = messageHistory[idx - 1]?.content; + const completion = message.content; + const model = message.metadata.modelId; + const feedbackData: FeedbackData = { + sessionId: message.metadata.sessionId as string, + key: idx, + feedback: feedbackType, + prompt: prompt, + completion: completion, + model: model as string + }; + addUserFeedback(feedbackData); } }; - const addUserFeedback = async (sessionId: string, key: number, feedback: string) => { + const addUserFeedback = async (feedbackData: FeedbackData) => { if (!appContext) return; const apiClient = new ApiClient(appContext); - await apiClient.userFeedback.addUserFeedback({sessionId, key, feedback}); + await apiClient.userFeedback.addUserFeedback({feedbackData}); }; return ( diff --git a/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx b/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx index b068cce7..e6f604ec 100644 --- a/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx +++ b/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx @@ -37,6 +37,7 @@ import { ChabotOutputModality, ChatBotHeartbeatRequest, ChatBotModelInterface, + FeedbackData, } from "./types"; import { LoadingStatus, ModelInterface } from "../../common/types"; import { getSelectedModelMetadata, updateMessageHistoryRef } from "./utils"; @@ -44,7 +45,7 @@ import LLMConfigDialog from "./llm-config-dialog"; import styles from "../../styles/chat.module.scss"; import { useNavigate } from "react-router-dom"; import { receiveMessages } from "../../graphql/subscriptions"; -import { sendQuery } from "../../graphql/mutations"; +import { sendQuery } from "../../graphql/mutations.ts"; import { Utils } from "../../common/utils"; export interface ChatSession { @@ -329,17 +330,29 @@ export default function MultiChat() { [ReadyState.UNINSTANTIATED]: "Uninstantiated", }[readyState]; - const handleFeedback = (feedbackType: 'thumbsUp' | 'thumbsDown', idx: number, message: ChatBotHistoryItem) => { + const handleFeedback = (feedbackType: 'thumbsUp' | 'thumbsDown', idx: number, message: ChatBotHistoryItem, messageHistory: ChatBotHistoryItem[]) => { + console.log("Message history: ", messageHistory); if (message.metadata.sessionId) { - addUserFeedback(message.metadata.sessionId as string, idx, feedbackType); + const prompt = messageHistory[idx - 1]?.content; + const completion = message.content; + const model = message.metadata.modelId; + const feedbackData: FeedbackData = { + sessionId: message.metadata.sessionId as string, + key: idx, + feedback: feedbackType, + prompt: prompt, + completion: completion, + model: model as string + }; + addUserFeedback(feedbackData); } }; - const addUserFeedback = async (sessionId: string, key: number, feedback: string) => { + const addUserFeedback = async (feedbackData: FeedbackData) => { if (!appContext) return; const apiClient = new ApiClient(appContext); - await apiClient.userFeedback.addUserFeedback({sessionId, key, feedback}); + await apiClient.userFeedback.addUserFeedback({feedbackData}); }; return ( @@ -497,8 +510,8 @@ export default function MultiChat() { key={idx} message={message} showMetadata={showMetadata} - onThumbsUp={() => handleFeedback('thumbsUp', idx, message)} - onThumbsDown={() => handleFeedback('thumbsDown', idx, message)} + onThumbsUp={() => handleFeedback('thumbsUp', idx, message, val)} + onThumbsDown={() => handleFeedback('thumbsDown', idx, message, val)} /> ))} diff --git a/lib/user-interface/react-app/src/components/chatbot/types.ts b/lib/user-interface/react-app/src/components/chatbot/types.ts index 747144ea..b31b3a37 100644 --- a/lib/user-interface/react-app/src/components/chatbot/types.ts +++ b/lib/user-interface/react-app/src/components/chatbot/types.ts @@ -147,3 +147,12 @@ export enum ChabotOutputModality { Image = "IMAGE", Embedding = "EMBEDDING", } + +export interface FeedbackData { + sessionId: string; + key: number; + feedback: string; + prompt: string; + completion: string; + model: string; +} From f246de34482ef7a472fceafe9cb568ae8107cc5c Mon Sep 17 00:00:00 2001 From: Ajay Lamba Date: Mon, 12 Feb 2024 21:21:34 +0530 Subject: [PATCH 3/3] Changing the user feedback type. Changed the user feedback type from string to number as number are easy to handle in Athena and other such tools. --- lib/chatbot-api/chatbot-s3-buckets/index.ts | 15 --------------- .../src/components/chatbot/chat-message.tsx | 14 +++++++------- .../react-app/src/components/chatbot/chat.tsx | 6 +++--- .../src/components/chatbot/multi-chat.tsx | 6 +++--- .../react-app/src/components/chatbot/types.ts | 2 +- 5 files changed, 14 insertions(+), 29 deletions(-) diff --git a/lib/chatbot-api/chatbot-s3-buckets/index.ts b/lib/chatbot-api/chatbot-s3-buckets/index.ts index 662badf3..e7666d04 100644 --- a/lib/chatbot-api/chatbot-s3-buckets/index.ts +++ b/lib/chatbot-api/chatbot-s3-buckets/index.ts @@ -44,23 +44,8 @@ export class ChatBotS3Buckets extends Construct { blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, - transferAcceleration: true, enforceSSL: true, serverAccessLogsBucket: logsBucket, - cors: [ - { - allowedHeaders: ["*"], - allowedMethods: [ - s3.HttpMethods.PUT, - s3.HttpMethods.POST, - s3.HttpMethods.GET, - s3.HttpMethods.HEAD, - ], - allowedOrigins: ["*"], - exposedHeaders: ["ETag"], - maxAge: 3000, - }, - ], }); this.filesBucket = filesBucket; diff --git a/lib/user-interface/react-app/src/components/chatbot/chat-message.tsx b/lib/user-interface/react-app/src/components/chatbot/chat-message.tsx index 90f29447..2649bebc 100644 --- a/lib/user-interface/react-app/src/components/chatbot/chat-message.tsx +++ b/lib/user-interface/react-app/src/components/chatbot/chat-message.tsx @@ -43,7 +43,7 @@ export default function ChatMessage(props: ChatMessageProps) { const [files, setFiles] = useState([] as ImageFile[]); const [documentIndex, setDocumentIndex] = useState("0"); const [promptIndex, setPromptIndex] = useState("0"); - const [selectedIcon, setSelectedIcon] = useState<'thumbsUp' | 'thumbsDown' | null>(null); + const [selectedIcon, setSelectedIcon] = useState<1 | 0 | null>(null); useEffect(() => { const getSignedUrls = async () => { @@ -275,21 +275,21 @@ export default function ChatMessage(props: ChatMessageProps) { }} />
- {(selectedIcon === 'thumbsUp' || selectedIcon === null) && ( + {(selectedIcon === 1 || selectedIcon === null) && ( { props.onThumbsUp(); - setSelectedIcon('thumbsUp'); + setSelectedIcon(1); }} /> )} - {(selectedIcon === 'thumbsDown' || selectedIcon === null) && ( + {(selectedIcon === 0 || selectedIcon === null) && ( { props.onThumbsDown(); - setSelectedIcon('thumbsDown'); + setSelectedIcon(0); }} /> )} diff --git a/lib/user-interface/react-app/src/components/chatbot/chat.tsx b/lib/user-interface/react-app/src/components/chatbot/chat.tsx index e888da02..9f7b6bf8 100644 --- a/lib/user-interface/react-app/src/components/chatbot/chat.tsx +++ b/lib/user-interface/react-app/src/components/chatbot/chat.tsx @@ -80,7 +80,7 @@ export default function Chat(props: { sessionId?: string }) { })(); }, [appContext, props.sessionId]); - const handleFeedback = (feedbackType: 'thumbsUp' | 'thumbsDown', idx: number, message: ChatBotHistoryItem) => { + const handleFeedback = (feedbackType: 1 | 0, idx: number, message: ChatBotHistoryItem) => { if (message.metadata.sessionId) { const prompt = messageHistory[idx - 1]?.content; const completion = message.content; @@ -112,8 +112,8 @@ export default function Chat(props: { sessionId?: string }) { key={idx} message={message} showMetadata={configuration.showMetadata} - onThumbsUp={() => handleFeedback('thumbsUp', idx, message)} - onThumbsDown={() => handleFeedback('thumbsDown', idx, message)} + onThumbsUp={() => handleFeedback(1, idx, message)} + onThumbsDown={() => handleFeedback(0, idx, message)} /> ))} diff --git a/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx b/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx index e6f604ec..66b375c6 100644 --- a/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx +++ b/lib/user-interface/react-app/src/components/chatbot/multi-chat.tsx @@ -330,7 +330,7 @@ export default function MultiChat() { [ReadyState.UNINSTANTIATED]: "Uninstantiated", }[readyState]; - const handleFeedback = (feedbackType: 'thumbsUp' | 'thumbsDown', idx: number, message: ChatBotHistoryItem, messageHistory: ChatBotHistoryItem[]) => { + const handleFeedback = (feedbackType: 1 | 0, idx: number, message: ChatBotHistoryItem, messageHistory: ChatBotHistoryItem[]) => { console.log("Message history: ", messageHistory); if (message.metadata.sessionId) { const prompt = messageHistory[idx - 1]?.content; @@ -510,8 +510,8 @@ export default function MultiChat() { key={idx} message={message} showMetadata={showMetadata} - onThumbsUp={() => handleFeedback('thumbsUp', idx, message, val)} - onThumbsDown={() => handleFeedback('thumbsDown', idx, message, val)} + onThumbsUp={() => handleFeedback(1, idx, message, val)} + onThumbsDown={() => handleFeedback(0, idx, message, val)} /> ))} diff --git a/lib/user-interface/react-app/src/components/chatbot/types.ts b/lib/user-interface/react-app/src/components/chatbot/types.ts index b31b3a37..d61ffa2f 100644 --- a/lib/user-interface/react-app/src/components/chatbot/types.ts +++ b/lib/user-interface/react-app/src/components/chatbot/types.ts @@ -151,7 +151,7 @@ export enum ChabotOutputModality { export interface FeedbackData { sessionId: string; key: number; - feedback: string; + feedback: number; prompt: string; completion: string; model: string;