From fc9867cc4c9c241df9a69ccc611076fb48a2dd98 Mon Sep 17 00:00:00 2001 From: kevinkim-ogp Date: Thu, 20 Nov 2025 22:34:26 +0800 Subject: [PATCH 01/14] feat: chat feedback --- package-lock.json | 25 ++++ packages/backend/src/helpers/csp.ts | 3 + packages/frontend/package.json | 1 + packages/frontend/src/config/app.ts | 12 ++ .../components/ChatMessages/ChatMessage.tsx | 2 +- .../ChatMessages/ChatMessageToolbar.tsx | 119 ++++++++++++++++-- 6 files changed, 149 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 91db78b798..79379e1e85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25274,6 +25274,30 @@ "zod": "^3.25.0 || ^4.0.0" } }, + "node_modules/langfuse": { + "version": "3.38.6", + "resolved": "https://registry.npmjs.org/langfuse/-/langfuse-3.38.6.tgz", + "integrity": "sha512-mtwfsNGIYvObRh+NYNGlJQJDiBN+Wr3Hnr++wN25mxuOpSTdXX+JQqVCyAqGL5GD2TAXRZ7COsN42Vmp9krYmg==", + "license": "MIT", + "dependencies": { + "langfuse-core": "^3.38.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/langfuse-core": { + "version": "3.38.6", + "resolved": "https://registry.npmjs.org/langfuse-core/-/langfuse-core-3.38.6.tgz", + "integrity": "sha512-EcZXa+DK9FJdi1I30+u19eKjuBJ04du6j2Nybk19KKCuraLczg/ppkTQcGvc4QOk//OAi3qUHrajUuV74RXsBQ==", + "license": "MIT", + "dependencies": { + "mustache": "^4.2.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/langium": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", @@ -38502,6 +38526,7 @@ "dedent": "1.5.1", "escape-html": "1.0.3", "file-saver": "2.0.5", + "langfuse": "3.38.6", "lens-widget": "^1.37.0", "lodash": "^4.17.21", "lottie-web": "5.12.2", diff --git a/packages/backend/src/helpers/csp.ts b/packages/backend/src/helpers/csp.ts index 97cf02640f..ac3d615751 100644 --- a/packages/backend/src/helpers/csp.ts +++ b/packages/backend/src/helpers/csp.ts @@ -19,6 +19,9 @@ const helmetOptions: HelmetOptions = { 'https://rum-proxy.plumber.gov.sg', // For Lens Survey 'https://lens.open.gov.sg', + // For Pair Rome and Istanbul + 'https://istanbul.pair.gov.sg', + 'https://rome.pair.gov.sg', // For S3 bucket 'https://plumber-uat-attachment-bucket-private-0d9400e.s3.ap-southeast-1.amazonaws.com', 'https://plumber-staging-attachment-bucket-private-ab28487.s3.ap-southeast-1.amazonaws.com', diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 34cfde29d7..e8fd1e3d39 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -41,6 +41,7 @@ "dedent": "1.5.1", "escape-html": "1.0.3", "file-saver": "2.0.5", + "langfuse": "3.38.6", "lens-widget": "^1.37.0", "lodash": "^4.17.21", "lottie-web": "5.12.2", diff --git a/packages/frontend/src/config/app.ts b/packages/frontend/src/config/app.ts index d5d6b780d3..305eef72a5 100644 --- a/packages/frontend/src/config/app.ts +++ b/packages/frontend/src/config/app.ts @@ -7,6 +7,8 @@ interface AppConfig { lensSurveyClientKey: string ssoClientId: string ssoHostname: string + pairRomeBaseUrl: string + pairRomePublicKey: string } function getAppConfig(): AppConfig { @@ -28,6 +30,8 @@ function getAppConfig(): AppConfig { lensSurveyClientKey: 'cm85ca2f300053ooz4vydrmyw', ssoClientId: 'plumber-prod', ssoHostname: 'https://sso.open.gov.sg', + pairRomeBaseUrl: 'https://rome.pair.gov.sg', + pairRomePublicKey: '', // TODO: add public key ...commonEnv, } // UAT and staging differ for the lens survey client key only @@ -39,6 +43,8 @@ function getAppConfig(): AppConfig { lensSurveyClientKey: 'cm8fp8i030008zm2tbuc07xe5', ssoClientId: 'plumber-uat', ssoHostname: 'https://sso.open.gov.sg', + pairRomeBaseUrl: 'https://rome.pair.gov.sg', + pairRomePublicKey: '', // TODO: add public key ...commonEnv, } case 'staging': @@ -49,6 +55,9 @@ function getAppConfig(): AppConfig { lensSurveyClientKey: 'cm86psst900052orfqetz3gz5', ssoClientId: 'plumber-staging', ssoHostname: 'https://sso.open.gov.sg', + // TODO: update to use Rome when ready + pairRomeBaseUrl: 'https://istanbul.pair.gov.sg', + pairRomePublicKey: 'pk-lf-3874d01f-530f-42f9-afd7-ea46d1bcfac3', ...commonEnv, } default: @@ -59,6 +68,9 @@ function getAppConfig(): AppConfig { lensSurveyClientKey: 'cm8fpeah2000gzm2t572lhfti', ssoClientId: 'plumber-local', ssoHostname: 'http://localhost:5354', + // TODO: update to use Rome when ready + pairRomeBaseUrl: 'https://istanbul.pair.gov.sg', + pairRomePublicKey: 'pk-lf-3874d01f-530f-42f9-afd7-ea46d1bcfac3', ...commonEnv, } } diff --git a/packages/frontend/src/pages/AiBuilder/components/ChatMessages/ChatMessage.tsx b/packages/frontend/src/pages/AiBuilder/components/ChatMessages/ChatMessage.tsx index b9c6439b0b..dc2087c8c4 100644 --- a/packages/frontend/src/pages/AiBuilder/components/ChatMessages/ChatMessage.tsx +++ b/packages/frontend/src/pages/AiBuilder/components/ChatMessages/ChatMessage.tsx @@ -18,7 +18,7 @@ const AiMessage = ({ message }: ChatMessageProps) => { {message.text || ''} - + ) diff --git a/packages/frontend/src/pages/AiBuilder/components/ChatMessages/ChatMessageToolbar.tsx b/packages/frontend/src/pages/AiBuilder/components/ChatMessages/ChatMessageToolbar.tsx index 6549315f95..84a93f7c97 100644 --- a/packages/frontend/src/pages/AiBuilder/components/ChatMessages/ChatMessageToolbar.tsx +++ b/packages/frontend/src/pages/AiBuilder/components/ChatMessages/ChatMessageToolbar.tsx @@ -1,5 +1,31 @@ +import React, { useState } from 'react' import { FaRegThumbsDown } from 'react-icons/fa' -import { Flex, Icon, IconButton, Tooltip } from '@chakra-ui/react' +import { + Button, + ButtonGroup, + Flex, + FocusLock, + FormControl, + FormLabel, + Icon, + IconButton, + Popover, + PopoverArrow, + PopoverBody, + PopoverContent, + PopoverTrigger, + Stack, + Textarea, + useDisclosure, +} from '@chakra-ui/react' +import { LangfuseWeb } from 'langfuse' + +import appConfig from '@/config/app' + +const langfuse = new LangfuseWeb({ + baseUrl: appConfig.pairRomeBaseUrl, + publicKey: appConfig.pairRomePublicKey, +}) const DEFAULT_BUTTON_PROPS = { size: 'xs', @@ -8,21 +34,90 @@ const DEFAULT_BUTTON_PROPS = { _hover: { color: 'gray.700', bg: 'gray.100' }, } -export default function ChatMessageToolbar() { - const handleThumbsDown = () => { - // TODO: Implement feedback functionality +interface ChatMessageToolbarProps { + traceId: string +} + +export default function ChatMessageToolbar({ + traceId, +}: ChatMessageToolbarProps) { + const { onOpen, onClose, isOpen } = useDisclosure() + const firstFieldRef = React.useRef(null) + const [comment, setComment] = useState('') + + const handleSubmitFeedback = (comment: string) => { + try { + if (!traceId) { + return + } + + // Send feedback to Rome / Istanbul + langfuse.score({ + traceId, + id: `user-feedback-${traceId}`, + name: 'user-feedback', + value: 0, // 1 for positive, 0 for negative + comment, + }) + } catch (error) { + console.error('Error submitting feedback:', error) + } finally { + onClose() + } } return ( - - } - onClick={handleThumbsDown} - /> - + + + } + onClick={onOpen} + /> + + + + + + + + + + Why was this not helpful? + +