From 85d5931b92cbc89b59a6565a2f09f0daf5aedf81 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 16:03:17 +0800 Subject: [PATCH 01/26] chore(package.json): install Langfuse SDK --- package-lock.json | 52 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 53 insertions(+) diff --git a/package-lock.json b/package-lock.json index 5adfb6d1..b9fd2e58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "isomorphic-unfetch": "^3.0.0", "js-cookie": "^3.0.1", "json-url": "^2.6.0", + "langfuse": "^3.32.3", "linkifyjs": "^4.1.3", "lodash": "^4.17.19", "marked": "^4.1.1", @@ -22071,6 +22072,28 @@ "node": ">= 0.6" } }, + "node_modules/langfuse": { + "version": "3.32.3", + "resolved": "https://registry.npmjs.org/langfuse/-/langfuse-3.32.3.tgz", + "integrity": "sha512-VUrfdAfzDaxpHhyPPD+myIYRdcil6aUc3tl/m1JVoYHOF9oGO9TIr2Q5MrgdjuE3dJLtR9xQGWvWzmzhrqpHcg==", + "dependencies": { + "langfuse-core": "^3.32.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/langfuse-core": { + "version": "3.32.3", + "resolved": "https://registry.npmjs.org/langfuse-core/-/langfuse-core-3.32.3.tgz", + "integrity": "sha512-p8ewPiElNyBYgx+NcT18UepJrdE1a/vPq2X7xiaaudc1Mi7CdG32V4FkiWvwLl5sWozfrRdA216PZaJ7ndh9IQ==", + "dependencies": { + "mustache": "^4.2.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/lazy": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", @@ -23146,6 +23169,14 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -50625,6 +50656,22 @@ } } }, + "langfuse": { + "version": "3.32.3", + "resolved": "https://registry.npmjs.org/langfuse/-/langfuse-3.32.3.tgz", + "integrity": "sha512-VUrfdAfzDaxpHhyPPD+myIYRdcil6aUc3tl/m1JVoYHOF9oGO9TIr2Q5MrgdjuE3dJLtR9xQGWvWzmzhrqpHcg==", + "requires": { + "langfuse-core": "^3.32.3" + } + }, + "langfuse-core": { + "version": "3.32.3", + "resolved": "https://registry.npmjs.org/langfuse-core/-/langfuse-core-3.32.3.tgz", + "integrity": "sha512-p8ewPiElNyBYgx+NcT18UepJrdE1a/vPq2X7xiaaudc1Mi7CdG32V4FkiWvwLl5sWozfrRdA216PZaJ7ndh9IQ==", + "requires": { + "mustache": "^4.2.0" + } + }, "lazy": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", @@ -51472,6 +51519,11 @@ } } }, + "mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==" + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", diff --git a/package.json b/package.json index 8b897f0e..f53c72d9 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "isomorphic-unfetch": "^3.0.0", "js-cookie": "^3.0.1", "json-url": "^2.6.0", + "langfuse": "^3.32.3", "linkifyjs": "^4.1.3", "lodash": "^4.17.19", "marked": "^4.1.1", From 8b5553736c9f144144b5e0e8d3d96401743309cb Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 16:04:08 +0800 Subject: [PATCH 02/26] feat(AIReplySection): add voting buttons for AI replies --- components/AIReplySection.js | 45 +++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/components/AIReplySection.js b/components/AIReplySection.js index a851e027..6613120a 100644 --- a/components/AIReplySection.js +++ b/components/AIReplySection.js @@ -1,14 +1,35 @@ import { useState } from 'react'; import { t } from 'ttag'; +import { Box, Button, makeStyles } from '@material-ui/core'; import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import { Card, CardHeader, CardContent } from 'components/Card'; +import { ThumbUpIcon, ThumbDownIcon } from 'components/icons'; import Hint from 'components/NewReplySection/ReplyForm/Hint'; +const useStyles = makeStyles(theme => ({ + vote: { + borderRadius: 45, + marginRight: 3, + [theme.breakpoints.up('md')]: { + marginRight: 10, + }, + }, + thumbIcon: { + fontSize: 16, + margin: '0 2px', + fill: 'transparent', + stroke: 'currentColor', + }, +})); + function AIReplySection({ defaultExpand = false, aiReplyText = '' }) { const [expand, setExpand] = useState(defaultExpand); + const classes = useStyles(); + + const handleVote = (vote: 1 | -1) => {}; return ( @@ -23,7 +44,7 @@ function AIReplySection({ defaultExpand = false, aiReplyText = '' }) { }} onClick={() => setExpand(v => !v)} > - {t`Automated analysis from ChatGPT`} + {t`Automated analysis from AI`} {expand ? : } {expand && ( @@ -34,6 +55,28 @@ function AIReplySection({ defaultExpand = false, aiReplyText = '' }) {
{aiReplyText}
+ + + + + + )}
From 35d11c736138add929af3b8ccddde8266dd018ca Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 16:06:49 +0800 Subject: [PATCH 03/26] feat: Add vote handling placeholder for AI reply section --- components/AIReplySection.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/AIReplySection.js b/components/AIReplySection.js index 6613120a..100f7e03 100644 --- a/components/AIReplySection.js +++ b/components/AIReplySection.js @@ -29,7 +29,9 @@ function AIReplySection({ defaultExpand = false, aiReplyText = '' }) { const [expand, setExpand] = useState(defaultExpand); const classes = useStyles(); - const handleVote = (vote: 1 | -1) => {}; + const handleVote = (vote: 1 | -1) => { + // send vote to Langfuse via LangfuseWeb, AI! + }; return ( From aef3cdf6c7e9e92f2488bc56e005dd213bf608a5 Mon Sep 17 00:00:00 2001 From: "MrOrz (aider)" Date: Sun, 26 Jan 2025 16:06:52 +0800 Subject: [PATCH 04/26] feat: Implement Langfuse vote tracking for AI replies --- components/AIReplySection.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/components/AIReplySection.js b/components/AIReplySection.js index 100f7e03..89e7972d 100644 --- a/components/AIReplySection.js +++ b/components/AIReplySection.js @@ -1,5 +1,6 @@ import { useState } from 'react'; import { t } from 'ttag'; +import { LangfuseWeb } from 'langfuse'; import { Box, Button, makeStyles } from '@material-ui/core'; import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; @@ -25,12 +26,21 @@ const useStyles = makeStyles(theme => ({ }, })); -function AIReplySection({ defaultExpand = false, aiReplyText = '' }) { +function AIReplySection({ defaultExpand = false, aiReplyText = '', traceId }) { const [expand, setExpand] = useState(defaultExpand); const classes = useStyles(); + + const langfuseWeb = new LangfuseWeb({ + publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, + baseUrl: 'https://cloud.langfuse.com' + }); - const handleVote = (vote: 1 | -1) => { - // send vote to Langfuse via LangfuseWeb, AI! + const handleVote = async (vote: 1 | -1) => { + await langfuseWeb.score({ + traceId, + name: 'ai_reply_feedback', + value: vote === 1 ? 1 : 0 + }); }; return ( From 3190e3cdcff6cd9930f0c3418d55b9be940dd1a2 Mon Sep 17 00:00:00 2001 From: "MrOrz (aider)" Date: Sun, 26 Jan 2025 16:07:23 +0800 Subject: [PATCH 05/26] style: Apply linter formatting to AIReplySection.js --- components/AIReplySection.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/AIReplySection.js b/components/AIReplySection.js index 89e7972d..0c40675a 100644 --- a/components/AIReplySection.js +++ b/components/AIReplySection.js @@ -29,17 +29,17 @@ const useStyles = makeStyles(theme => ({ function AIReplySection({ defaultExpand = false, aiReplyText = '', traceId }) { const [expand, setExpand] = useState(defaultExpand); const classes = useStyles(); - + const langfuseWeb = new LangfuseWeb({ publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, - baseUrl: 'https://cloud.langfuse.com' + baseUrl: 'https://cloud.langfuse.com', }); const handleVote = async (vote: 1 | -1) => { await langfuseWeb.score({ traceId, name: 'ai_reply_feedback', - value: vote === 1 ? 1 : 0 + value: vote === 1 ? 1 : 0, }); }; From d91d8c4491e42b00edd0ec05aa7cb6a87f33dd89 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 16:11:04 +0800 Subject: [PATCH 06/26] feat(AIReplySection): pass aiResponseId to handleVote and update GraphQL query for AI replies --- components/AIReplySection.js | 12 ++++++++---- pages/article/[id].js | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/components/AIReplySection.js b/components/AIReplySection.js index 0c40675a..aba64ef4 100644 --- a/components/AIReplySection.js +++ b/components/AIReplySection.js @@ -26,7 +26,11 @@ const useStyles = makeStyles(theme => ({ }, })); -function AIReplySection({ defaultExpand = false, aiReplyText = '', traceId }) { +function AIReplySection({ + defaultExpand = false, + aiReplyText = '', + aiResponseId, +}) { const [expand, setExpand] = useState(defaultExpand); const classes = useStyles(); @@ -37,9 +41,9 @@ function AIReplySection({ defaultExpand = false, aiReplyText = '', traceId }) { const handleVote = async (vote: 1 | -1) => { await langfuseWeb.score({ - traceId, - name: 'ai_reply_feedback', - value: vote === 1 ? 1 : 0, + traceId: aiResponseId, + name: 'user-feedback', + value: vote, }); }; diff --git a/pages/article/[id].js b/pages/article/[id].js index 966d43dd..e5440197 100644 --- a/pages/article/[id].js +++ b/pages/article/[id].js @@ -153,6 +153,7 @@ const LOAD_ARTICLE = gql` ...CurrentRepliesData } aiReplies { + id text } ...RelatedArticleData @@ -575,6 +576,7 @@ function ArticlePage() { {article.aiReplies?.length > 0 && ( From 0e88460aba2cf877e43329e8945c1b07dab8013c Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 17:00:52 +0800 Subject: [PATCH 07/26] feat(.env): add Langfuse configuration to .env.sample --- .env.sample | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index b719054a..7463eab0 100644 --- a/.env.sample +++ b/.env.sample @@ -50,4 +50,8 @@ PUBLIC_SLACK_IFTTT_APPLET_URL= PUBLIC_LINE_IFTTT_TUTORIAL_YOUTUBEID= PUBLIC_TELEGRAM_IFTTT_TUTORIAL_YOUTUBEID= -PUBLIC_SLACK_IFTTT_TUTORIAL_YOUTUBEID= \ No newline at end of file +PUBLIC_SLACK_IFTTT_TUTORIAL_YOUTUBEID= + +# Langfuse setup +PUBLIC_LANGFUSE_PUBLIC_KEY= +PUBLIC_LANGFUSE_HOST= From f87e7481b88b7a9a77c7fab3dde96bdb4454eea2 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 17:01:12 +0800 Subject: [PATCH 08/26] feat(next.config.js): add Langfuse SDK support with babel-loader configuration --- next.config.js | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/next.config.js b/next.config.js index 4d8b4cea..b090d923 100644 --- a/next.config.js +++ b/next.config.js @@ -33,20 +33,35 @@ module.exports = { // // Simplified from https://github.com/twopluszero/next-images/blob/master/index.js // - config.module.rules.push({ - test: /\.(jpe?g|png|svg|gif|ico|webp|mp4)$/, - use: [ - { - loader: 'url-loader', - options: { - limit: 8192, - publicPath: '/_next/static/images/', - outputPath: `${isServer ? '../' : ''}static/images/`, - name: '[name]-[hash].[ext]', + config.module.rules.push( + { + test: /\.(jpe?g|png|svg|gif|ico|webp|mp4)$/, + use: [ + { + loader: 'url-loader', + options: { + limit: 8192, + publicPath: '/_next/static/images/', + outputPath: `${isServer ? '../' : ''}static/images/`, + name: '[name]-[hash].[ext]', + }, }, - }, - ], - }); + ], + }, + { + // Langfuse SDK + test: /node_modules\/langfuse/, + type: 'javascript/auto', // https://stackoverflow.com/a/74957466/1582110 + use: [ + { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'], + }, + }, + ], + } + ); if (!isServer) { config.module.rules.push({ From fdbcc4c36dcfaa66593cd0973799c2ab7da6b072 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 17:20:27 +0800 Subject: [PATCH 09/26] feat(AIReplySection): integrate Langfuse SDK using next/config for configuration --- components/AIReplySection.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/components/AIReplySection.js b/components/AIReplySection.js index aba64ef4..a19a1e7e 100644 --- a/components/AIReplySection.js +++ b/components/AIReplySection.js @@ -1,6 +1,7 @@ import { useState } from 'react'; import { t } from 'ttag'; import { LangfuseWeb } from 'langfuse'; +import getConfig from 'next/config'; import { Box, Button, makeStyles } from '@material-ui/core'; import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; @@ -10,6 +11,15 @@ import { Card, CardHeader, CardContent } from 'components/Card'; import { ThumbUpIcon, ThumbDownIcon } from 'components/icons'; import Hint from 'components/NewReplySection/ReplyForm/Hint'; +const { + publicRuntimeConfig: { PUBLIC_LANGFUSE_PUBLIC_KEY, PUBLIC_LANGFUSE_HOST }, +} = getConfig(); + +const langfuseWeb = new LangfuseWeb({ + publicKey: PUBLIC_LANGFUSE_PUBLIC_KEY, + baseUrl: PUBLIC_LANGFUSE_HOST, +}); + const useStyles = makeStyles(theme => ({ vote: { borderRadius: 45, @@ -20,7 +30,6 @@ const useStyles = makeStyles(theme => ({ }, thumbIcon: { fontSize: 16, - margin: '0 2px', fill: 'transparent', stroke: 'currentColor', }, @@ -34,12 +43,7 @@ function AIReplySection({ const [expand, setExpand] = useState(defaultExpand); const classes = useStyles(); - const langfuseWeb = new LangfuseWeb({ - publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, - baseUrl: 'https://cloud.langfuse.com', - }); - - const handleVote = async (vote: 1 | -1) => { + const handleVote = async vote => { await langfuseWeb.score({ traceId: aiResponseId, name: 'user-feedback', From 8d0b42d4ed2979c227e5e25be25c5cacc6d1b8a5 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 17:34:08 +0800 Subject: [PATCH 10/26] style(AIReplySection): adjust styling for vote button and thumb icon size --- components/AIReplySection.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/AIReplySection.js b/components/AIReplySection.js index a19a1e7e..94497ff2 100644 --- a/components/AIReplySection.js +++ b/components/AIReplySection.js @@ -23,13 +23,14 @@ const langfuseWeb = new LangfuseWeb({ const useStyles = makeStyles(theme => ({ vote: { borderRadius: 45, + marginTop: 16, marginRight: 3, [theme.breakpoints.up('md')]: { marginRight: 10, }, }, thumbIcon: { - fontSize: 16, + fontSize: 20, fill: 'transparent', stroke: 'currentColor', }, From cdf047cf69916eb1da25ba59533ac3ed6111b603 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 17:38:01 +0800 Subject: [PATCH 11/26] refactor(AIReplySection): use Box prop instead --- components/AIReplySection.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/AIReplySection.js b/components/AIReplySection.js index 94497ff2..123cbff0 100644 --- a/components/AIReplySection.js +++ b/components/AIReplySection.js @@ -23,7 +23,6 @@ const langfuseWeb = new LangfuseWeb({ const useStyles = makeStyles(theme => ({ vote: { borderRadius: 45, - marginTop: 16, marginRight: 3, [theme.breakpoints.up('md')]: { marginRight: 10, @@ -76,7 +75,7 @@ function AIReplySection({
{aiReplyText}
- + - - + )} diff --git a/components/AIReplySection/VoteButtons.js b/components/AIReplySection/VoteButtons.js new file mode 100644 index 00000000..0fa12895 --- /dev/null +++ b/components/AIReplySection/VoteButtons.js @@ -0,0 +1,65 @@ +import { Button, Box, makeStyles } from '@material-ui/core'; +import { LangfuseWeb } from 'langfuse'; +import getConfig from 'next/config'; +import { ThumbUpIcon, ThumbDownIcon } from 'components/icons'; + +const { + publicRuntimeConfig: { PUBLIC_LANGFUSE_PUBLIC_KEY, PUBLIC_LANGFUSE_HOST }, +} = getConfig(); + +const langfuseWeb = new LangfuseWeb({ + publicKey: PUBLIC_LANGFUSE_PUBLIC_KEY, + baseUrl: PUBLIC_LANGFUSE_HOST, +}); + +const useStyles = makeStyles(theme => ({ + vote: { + borderRadius: 45, + marginRight: 3, + [theme.breakpoints.up('md')]: { + marginRight: 10, + }, + }, + thumbIcon: { + fontSize: 20, + fill: 'transparent', + stroke: 'currentColor', + }, +})); + +function VoteButtons({ aiResponseId }) { + const classes = useStyles(); + + const handleVote = async vote => { + await langfuseWeb.score({ + traceId: aiResponseId, + name: 'user-feedback', + value: vote, + }); + }; + + return ( + + + + + ); +} + +export default VoteButtons; From 4108f5dab00eb48cb3db0240112b3ce6b3034fcc Mon Sep 17 00:00:00 2001 From: "MrOrz (aider)" Date: Sun, 26 Jan 2025 17:48:32 +0800 Subject: [PATCH 13/26] style: Remove unused AIReplySection component --- components/AIReplySection.js | 53 ------------------------------------ 1 file changed, 53 deletions(-) delete mode 100644 components/AIReplySection.js diff --git a/components/AIReplySection.js b/components/AIReplySection.js deleted file mode 100644 index 9096d69d..00000000 --- a/components/AIReplySection.js +++ /dev/null @@ -1,53 +0,0 @@ -import { useState } from 'react'; -import { t } from 'ttag'; -import { Box } from '@material-ui/core'; -import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; -import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; - -import { Card, CardHeader, CardContent } from 'components/Card'; -import Hint from 'components/NewReplySection/ReplyForm/Hint'; -import VoteButtons from './VoteButtons'; - -function AIReplySection({ - defaultExpand = false, - aiReplyText = '', - aiResponseId, -}) { - const [expand, setExpand] = useState(defaultExpand); - const classes = useStyles(); - - - return ( - - setExpand(v => !v)} - > - {t`Automated analysis from AI`} - {expand ? : } - - {expand && ( - - - {t`The following is the AI's preliminary analysis of this message, which we hope will provide you with some ideas before it is fact-checked by a human.`} - -
- {aiReplyText} -
- - - -
- )} -
- ); -} - -export default AIReplySection; From f8217cb457248a5186bc71ee16cd6dc738eac1ee Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 26 Jan 2025 18:07:30 +0800 Subject: [PATCH 14/26] feat(AIReplySection): add AIReplySection component with expandable view and voting functionality --- components/AIReplySection.js | 0 components/AIReplySection/AIReplySection.js | 51 +++++++++++++++++++ .../{VoteButtons.js => VoteButtons.tsx} | 8 ++- components/AIReplySection/index.ts | 2 + 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 components/AIReplySection.js create mode 100644 components/AIReplySection/AIReplySection.js rename components/AIReplySection/{VoteButtons.js => VoteButtons.tsx} (90%) create mode 100644 components/AIReplySection/index.ts diff --git a/components/AIReplySection.js b/components/AIReplySection.js new file mode 100644 index 00000000..e69de29b diff --git a/components/AIReplySection/AIReplySection.js b/components/AIReplySection/AIReplySection.js new file mode 100644 index 00000000..7fb7fdab --- /dev/null +++ b/components/AIReplySection/AIReplySection.js @@ -0,0 +1,51 @@ +import { useState } from 'react'; +import { t } from 'ttag'; +import { Box } from '@material-ui/core'; +import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; +import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; + +import { Card, CardHeader, CardContent } from 'components/Card'; +import Hint from 'components/NewReplySection/ReplyForm/Hint'; +import VoteButtons from './VoteButtons'; + +function AIReplySection({ + defaultExpand = false, + aiReplyText = '', + aiResponseId, +}) { + const [expand, setExpand] = useState(defaultExpand); + + return ( + + setExpand(v => !v)} + > + {t`Automated analysis from AI`} + {expand ? : } + + {expand && ( + + + {t`The following is the AI's preliminary analysis of this message, which we hope will provide you with some ideas before it is fact-checked by a human.`} + +
+ {aiReplyText} +
+ + + +
+ )} +
+ ); +} + +export default AIReplySection; diff --git a/components/AIReplySection/VoteButtons.js b/components/AIReplySection/VoteButtons.tsx similarity index 90% rename from components/AIReplySection/VoteButtons.js rename to components/AIReplySection/VoteButtons.tsx index 0fa12895..dcbc9202 100644 --- a/components/AIReplySection/VoteButtons.js +++ b/components/AIReplySection/VoteButtons.tsx @@ -27,10 +27,14 @@ const useStyles = makeStyles(theme => ({ }, })); -function VoteButtons({ aiResponseId }) { +type Props = { + aiResponseId: string; +}; + +function VoteButtons({ aiResponseId }: Props) { const classes = useStyles(); - const handleVote = async vote => { + const handleVote = async (vote: -1 | 1) => { await langfuseWeb.score({ traceId: aiResponseId, name: 'user-feedback', diff --git a/components/AIReplySection/index.ts b/components/AIReplySection/index.ts new file mode 100644 index 00000000..c010271f --- /dev/null +++ b/components/AIReplySection/index.ts @@ -0,0 +1,2 @@ +import AIReplySection from './AIReplySection'; +export default AIReplySection; From 87b04eb154da3cd7850297a0e25531ab96351d02 Mon Sep 17 00:00:00 2001 From: "MrOrz (aider)" Date: Sun, 26 Jan 2025 18:19:34 +0800 Subject: [PATCH 15/26] feat: Add comment popover to VoteButtons with Langfuse feedback --- components/AIReplySection/VoteButtons.tsx | 142 ++++++++++++++++++---- 1 file changed, 120 insertions(+), 22 deletions(-) diff --git a/components/AIReplySection/VoteButtons.tsx b/components/AIReplySection/VoteButtons.tsx index dcbc9202..f3b99b19 100644 --- a/components/AIReplySection/VoteButtons.tsx +++ b/components/AIReplySection/VoteButtons.tsx @@ -1,4 +1,7 @@ -import { Button, Box, makeStyles } from '@material-ui/core'; +import { Button, Box, makeStyles, Popover, Typography } from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import { t } from 'ttag'; +import { useState } from 'react'; import { LangfuseWeb } from 'langfuse'; import getConfig from 'next/config'; import { ThumbUpIcon, ThumbDownIcon } from 'components/icons'; @@ -25,6 +28,41 @@ const useStyles = makeStyles(theme => ({ fill: 'transparent', stroke: 'currentColor', }, + popover: { + position: 'relative', + width: 420, + maxWidth: '90vw', + padding: 32, + }, + closeButton: { + background: theme.palette.common.white, + cursor: 'pointer', + position: 'absolute', + right: 6, + top: 10, + border: 'none', + outline: 'none', + color: theme.palette.secondary[100], + }, + popupTitle: { + fontSize: 18, + marginBottom: 24, + }, + textarea: { + padding: 15, + width: '100%', + borderRadius: 8, + border: `1px solid ${theme.palette.secondary[100]}`, + outline: 'none', + '&:focus': { + border: `1px solid ${theme.palette.primary[500]}`, + }, + }, + textCenter: { textAlign: 'center' }, + sendButton: { + marginTop: 10, + borderRadius: 30, + }, })); type Props = { @@ -33,36 +71,96 @@ type Props = { function VoteButtons({ aiResponseId }: Props) { const classes = useStyles(); + const [votePopoverAnchorEl, setVotePopoverAnchorEl] = useState(null); + const [pendingVote, setPendingVote] = useState(null); + const [comment, setComment] = useState(''); - const handleVote = async (vote: -1 | 1) => { + const openVotePopover = (event: React.MouseEvent, vote: number) => { + setVotePopoverAnchorEl(event.currentTarget); + setPendingVote(vote); + }; + + const closeVotePopover = () => { + setVotePopoverAnchorEl(null); + setPendingVote(null); + setComment(''); + }; + + const handleVote = async () => { + if (pendingVote === null) return; + await langfuseWeb.score({ traceId: aiResponseId, name: 'user-feedback', - value: vote, + value: pendingVote, + comment, }); + closeVotePopover(); }; return ( - - - + + + - - -
+ + + {pendingVote === 1 + ? t`Do you have anything to add?` + : t`Why do you think it is not useful?`} + +