diff --git a/frontend/src/components/Modals/ChatFeedback/index.jsx b/frontend/src/components/Modals/ChatFeedback/index.jsx new file mode 100644 index 00000000000..91209d13505 --- /dev/null +++ b/frontend/src/components/Modals/ChatFeedback/index.jsx @@ -0,0 +1,86 @@ +import React, { useState } from "react"; +import ModalWrapper from "@/components/ModalWrapper"; +import { X } from "@phosphor-icons/react"; +import Workspace from "@/models/workspace"; +import { useTranslation } from "react-i18next"; + +export default function ChatFeedbackModal({ + isOpen, + hideModal, + chatId, + slug, + onSubmitted, +}) { + const { t } = useTranslation(); + const [comment, setComment] = useState(""); + const [submitting, setSubmitting] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!chatId || !slug) { + hideModal(); + return; + } + setSubmitting(true); + try { + // Only forward the comment to parent if there's content; feedback is optional + if (comment && comment.trim() !== "") { + onSubmitted && onSubmitted(comment.trim()); + } + } catch (err) { + // ignore errors for now + } + setSubmitting(false); + hideModal(); + }; + + return ( + + + + + + {t("chat_window.provide_feedback")} + + + + + + + + + + {t("chat_window.bad_response_optional_feedback")} + + setComment(e.target.value)} + className="w-full min-h-[120px] p-3 rounded bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder outline-none" + placeholder={t("chat_window.bad_response_placeholder")} + /> + + + + {t("chat_window.cancel")} + + + {submitting ? t("common.saving") : t("common.save")} + + + + + + ); +} diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/index.jsx index e0c1273fdbd..2e78311430f 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/index.jsx @@ -1,7 +1,14 @@ import React, { memo, useState } from "react"; import useCopyText from "@/hooks/useCopyText"; -import { Check, ThumbsUp, ArrowsClockwise, Copy } from "@phosphor-icons/react"; +import { + Check, + ThumbsUp, + ThumbsDown, + ArrowsClockwise, + Copy, +} from "@phosphor-icons/react"; import Workspace from "@/models/workspace"; +import ChatFeedbackModal from "@/components/Modals/ChatFeedback"; import { EditMessageAction } from "./EditMessage"; import RenderMetrics from "./RenderMetrics"; import ActionMenu from "./ActionMenu"; @@ -22,11 +29,19 @@ const Actions = ({ }) => { const { t } = useTranslation(); const [selectedFeedback, setSelectedFeedback] = useState(feedbackScore); + const [showFeedbackModal, setShowFeedbackModal] = useState(false); + const [submittingComment, setSubmittingComment] = useState(false); const handleFeedback = async (newFeedback) => { const updatedFeedback = selectedFeedback === newFeedback ? null : newFeedback; + // persist feedback score first await Workspace.updateChatFeedback(chatId, slug, updatedFeedback); setSelectedFeedback(updatedFeedback); + + // If user just set negative feedback (not unsetting), show optional feedback modal + if (updatedFeedback === false) { + setShowFeedbackModal(true); + } }; return ( @@ -55,6 +70,15 @@ const Actions = ({ IconComponent={ThumbsUp} /> )} + {chatId && role !== "user" && !isEditing && ( + handleFeedback(false)} + tooltipId="feedback-button" + tooltipContent={t("chat_window.bad_response")} + IconComponent={ThumbsDown} + /> + )} + setShowFeedbackModal(false)} + chatId={chatId} + slug={slug} + onSubmitted={async (comment) => { + if (submittingComment) return; + setSubmittingComment(true); + try { + await Workspace.submitChatFeedbackComment(chatId, slug, comment); + } catch (e) {} + setSubmittingComment(false); + }} + /> ); }; diff --git a/frontend/src/locales/ar/common.js b/frontend/src/locales/ar/common.js index a4ab12253b8..72f058faa23 100644 --- a/frontend/src/locales/ar/common.js +++ b/frontend/src/locales/ar/common.js @@ -660,6 +660,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/da/common.js b/frontend/src/locales/da/common.js index 9c63f2aabaa..3d003073e24 100644 --- a/frontend/src/locales/da/common.js +++ b/frontend/src/locales/da/common.js @@ -698,6 +698,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/de/common.js b/frontend/src/locales/de/common.js index 152198e7909..bf39db0f20f 100644 --- a/frontend/src/locales/de/common.js +++ b/frontend/src/locales/de/common.js @@ -897,6 +897,10 @@ const TRANSLATIONS = { regenerate: "Neu generieren", regenerate_response: "Antwort neu generieren", good_response: "Gute Antwort", + bad_response: "Schlechte Antwort", + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: "Weitere Aktionen", hide_citations: "Quellenangaben ausblenden", show_citations: "Quellenangaben anzeigen", diff --git a/frontend/src/locales/en/common.js b/frontend/src/locales/en/common.js index 3d5c5f6dec0..516c18af18b 100644 --- a/frontend/src/locales/en/common.js +++ b/frontend/src/locales/en/common.js @@ -955,6 +955,10 @@ const TRANSLATIONS = { regenerate: "Regenerate", regenerate_response: "Regenerate response", good_response: "Good response", + bad_response: "Bad response", + provide_feedback: "Provide Feedback", + bad_response_optional_feedback: "Optional feedback (help us improve)", + bad_response_placeholder: "Tell us what was wrong (optional)", more_actions: "More actions", hide_citations: "Hide citations", show_citations: "Show citations", diff --git a/frontend/src/locales/es/common.js b/frontend/src/locales/es/common.js index eeed8283376..8ee3986f0ba 100644 --- a/frontend/src/locales/es/common.js +++ b/frontend/src/locales/es/common.js @@ -910,6 +910,10 @@ const TRANSLATIONS = { regenerate: "Regenerar", regenerate_response: "Regenerar respuesta", good_response: "Buena respuesta", + bad_response: "Mala respuesta", + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: "Más acciones", hide_citations: "Ocultar citas", show_citations: "Mostrar citas", diff --git a/frontend/src/locales/et/common.js b/frontend/src/locales/et/common.js index f4a177ea412..ee39794c4d8 100644 --- a/frontend/src/locales/et/common.js +++ b/frontend/src/locales/et/common.js @@ -857,6 +857,7 @@ const TRANSLATIONS = { regenerate: "Loo uuesti", regenerate_response: "Loo vastus uuesti", good_response: "Hea vastus", + bad_response: "Halb vastus", more_actions: "Rohkem toiminguid", hide_citations: "Peida viited", show_citations: "Näita viiteid", diff --git a/frontend/src/locales/fa/common.js b/frontend/src/locales/fa/common.js index 67bc042b9d0..041804cdde5 100644 --- a/frontend/src/locales/fa/common.js +++ b/frontend/src/locales/fa/common.js @@ -652,6 +652,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/fr/common.js b/frontend/src/locales/fr/common.js index a2ed242b9ce..744c0576d51 100644 --- a/frontend/src/locales/fr/common.js +++ b/frontend/src/locales/fr/common.js @@ -660,6 +660,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/he/common.js b/frontend/src/locales/he/common.js index e572d10d023..6cd5072be83 100644 --- a/frontend/src/locales/he/common.js +++ b/frontend/src/locales/he/common.js @@ -864,6 +864,10 @@ const TRANSLATIONS = { regenerate: "צור מחדש", regenerate_response: "צור תגובה מחדש", good_response: "תגובה טובה", + bad_response: "תגובה רעה", + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: "פעולות נוספות", hide_citations: "הסתר ציטוטים", show_citations: "הצג ציטוטים", diff --git a/frontend/src/locales/it/common.js b/frontend/src/locales/it/common.js index 7d2470a71a6..11a4ddae13a 100644 --- a/frontend/src/locales/it/common.js +++ b/frontend/src/locales/it/common.js @@ -658,6 +658,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/ja/common.js b/frontend/src/locales/ja/common.js index e75590aa7d3..ded1762a459 100644 --- a/frontend/src/locales/ja/common.js +++ b/frontend/src/locales/ja/common.js @@ -690,6 +690,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/ko/common.js b/frontend/src/locales/ko/common.js index f4dcc540965..b4b73a5ad47 100644 --- a/frontend/src/locales/ko/common.js +++ b/frontend/src/locales/ko/common.js @@ -873,6 +873,10 @@ const TRANSLATIONS = { regenerate: "다시 생성", regenerate_response: "응답 다시 생성", good_response: "좋은 답변", + bad_response: "나쁜 답변", + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: "더 많은 작업", hide_citations: "인용 숨기기", show_citations: "인용 보기", diff --git a/frontend/src/locales/lv/common.js b/frontend/src/locales/lv/common.js index 76c4a104798..bae71f0555d 100644 --- a/frontend/src/locales/lv/common.js +++ b/frontend/src/locales/lv/common.js @@ -889,6 +889,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/nl/common.js b/frontend/src/locales/nl/common.js index 61bcc7e016a..ea97b89b55c 100644 --- a/frontend/src/locales/nl/common.js +++ b/frontend/src/locales/nl/common.js @@ -655,6 +655,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/pl/common.js b/frontend/src/locales/pl/common.js index a97c9da5463..f7d9e52ff10 100644 --- a/frontend/src/locales/pl/common.js +++ b/frontend/src/locales/pl/common.js @@ -65,6 +65,10 @@ const TRANSLATIONS = { selection: "Wybór modelu", saving: "Zapisywanie...", save: "Zapisz zmiany", + bad_response: "Zła odpowiedź", + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, previous: "Poprzednia strona", next: "Następna strona", optional: "Opcjonalnie", diff --git a/frontend/src/locales/pt_BR/common.js b/frontend/src/locales/pt_BR/common.js index ef19a448495..aa34af46462 100644 --- a/frontend/src/locales/pt_BR/common.js +++ b/frontend/src/locales/pt_BR/common.js @@ -870,6 +870,10 @@ const TRANSLATIONS = { regenerate: "Regerar", regenerate_response: "Regerar resposta", good_response: "Resposta satisfatória", + bad_response: "Resposta ruim", + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: "Mais ações", hide_citations: "Esconder citações", show_citations: "Exibir citações", diff --git a/frontend/src/locales/ru/common.js b/frontend/src/locales/ru/common.js index 9e9eeb94456..35715d2e864 100644 --- a/frontend/src/locales/ru/common.js +++ b/frontend/src/locales/ru/common.js @@ -699,6 +699,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/vn/common.js b/frontend/src/locales/vn/common.js index 43b3037acce..0ac224704b1 100644 --- a/frontend/src/locales/vn/common.js +++ b/frontend/src/locales/vn/common.js @@ -654,6 +654,10 @@ const TRANSLATIONS = { regenerate: null, regenerate_response: null, good_response: null, + bad_response: null, + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: null, hide_citations: null, show_citations: null, diff --git a/frontend/src/locales/zh/common.js b/frontend/src/locales/zh/common.js index 35f438c2418..f99e20060ac 100644 --- a/frontend/src/locales/zh/common.js +++ b/frontend/src/locales/zh/common.js @@ -832,6 +832,10 @@ const TRANSLATIONS = { regenerate: "重新", regenerate_response: "重新回应", good_response: "反应良好", + bad_response: "不良回复", + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: "更多操作", hide_citations: "隐藏引文", show_citations: "显示引文", diff --git a/frontend/src/locales/zh_TW/common.js b/frontend/src/locales/zh_TW/common.js index 30a0a431654..bdb47d10bbf 100644 --- a/frontend/src/locales/zh_TW/common.js +++ b/frontend/src/locales/zh_TW/common.js @@ -661,6 +661,10 @@ const TRANSLATIONS = { regenerate: "重新", regenerate_response: "重新回應", good_response: "反應良好", + bad_response: "不良回應", + provide_feedback: null, + bad_response_optional_feedback: null, + bad_response_placeholder: null, more_actions: "更多操作", hide_citations: "隱藏引文", show_citations: "顯示引文", diff --git a/frontend/src/models/workspace.js b/frontend/src/models/workspace.js index 3627b4e3823..c563c95e89d 100644 --- a/frontend/src/models/workspace.js +++ b/frontend/src/models/workspace.js @@ -78,6 +78,20 @@ const Workspace = { .catch(() => false); return result; }, + submitChatFeedbackComment: async function (chatId, slug, comment) { + if (!chatId || !slug || !comment) return false; + const result = await fetch( + `${API_BASE}/workspace/${slug}/chat-feedback/${chatId}/comment`, + { + method: "POST", + headers: baseHeaders(), + body: JSON.stringify({ comment }), + } + ) + .then((res) => res.ok) + .catch(() => false); + return result; + }, deleteChats: async function (slug = "", chatIds = []) { return await fetch(`${API_BASE}/workspace/${slug}/delete-chats`, { diff --git a/server/__tests__/utils/helpers/convertTo.test.js b/server/__tests__/utils/helpers/convertTo.test.js index 2b974d87ed7..f13462e3b88 100644 --- a/server/__tests__/utils/helpers/convertTo.test.js +++ b/server/__tests__/utils/helpers/convertTo.test.js @@ -25,6 +25,7 @@ const mockChat = (withImages = false) => { workspace: { name: "Test Workspace", openAiPrompt: "Test OpenAI Prompt" }, user: { username: "testuser" }, feedbackScore: 1, + feedbackComment: "Mmhhh... not great", } }; @@ -61,6 +62,9 @@ describe("prepareChatsForExport", () => { response: responseJson.text, sent_at: chatExample.createdAt, rating: chatExample.feedbackScore ? "GOOD" : "BAD", + comment: chatExample.feedbackComment ?? "", + feedbackComment: chatExample.feedbackComment ?? "", + feedbackScore: chatExample.feedbackScore ?? "", username: chatExample.user.username, workspace: chatExample.workspace.name, attachments: [], @@ -80,6 +84,9 @@ describe("prepareChatsForExport", () => { response: responseJson.text, sent_at: chatExample.createdAt, rating: chatExample.feedbackScore ? "GOOD" : "BAD", + comment: chatExample.feedbackComment ?? "", + feedbackComment: chatExample.feedbackComment ?? "", + feedbackScore: chatExample.feedbackScore ?? "", username: chatExample.user.username, workspace: chatExample.workspace.name, attachments: [ @@ -109,6 +116,9 @@ describe("prepareChatsForExport", () => { response: responseJson.text, sent_at: chatExample.createdAt, rating: chatExample.feedbackScore ? "GOOD" : "BAD", + comment: chatExample.feedbackComment ?? "", + feedbackComment: chatExample.feedbackComment ?? "", + feedbackScore: chatExample.feedbackScore ?? "", username: chatExample.user.username, workspace: chatExample.workspace.name, }]); @@ -168,6 +178,10 @@ describe("prepareChatsForExport", () => { content: [{ type: "text", text: responseJson.text, + feedback: { + comment: chatExample.feedbackComment, + score: chatExample.feedbackScore, + }, }], }, ], @@ -208,6 +222,10 @@ describe("prepareChatsForExport", () => { content: [{ type: "text", text: responseJson.text, + feedback: { + comment: chatExample.feedbackComment, + score: chatExample.feedbackScore, + }, }], }, { @@ -228,6 +246,10 @@ describe("prepareChatsForExport", () => { content: [{ type: "text", text: imageResponseJson.text, + feedback: { + comment: imageChatExample.feedbackComment, + score: imageChatExample.feedbackScore, + }, }], }, ], diff --git a/server/endpoints/api/admin/index.js b/server/endpoints/api/admin/index.js index 93fbff7669e..015e0c5c9d3 100644 --- a/server/endpoints/api/admin/index.js +++ b/server/endpoints/api/admin/index.js @@ -667,12 +667,13 @@ function apiAdminEndpoints(app) { #swagger.tags = ['Admin'] #swagger.description = 'All chats in the system ordered by most recent. Methods are disabled until multi user mode is enabled via the UI.' #swagger.requestBody = { - description: 'Page offset to show of workspace chats. All fields are optional and will not update unless specified.', + description: 'Page offset to show of workspace chats. Feedback Score to filter by all, true, or false. All fields are optional and will not update unless specified.', required: false, content: { "application/json": { example: { offset: 2, + feedbackScore: true } } } @@ -698,15 +699,24 @@ function apiAdminEndpoints(app) { */ try { const pgSize = 20; - const { offset = 0 } = reqBody(request); + const { offset = 0, feedbackScore = null } = reqBody(request); + + // Build a where object and only include feedbackScore when it was supplied + const where = {}; + if (feedbackScore !== null && feedbackScore !== undefined) { + // feedbackScore is expected to be boolean true/false + where.feedbackScore = feedbackScore; + } + const chats = await WorkspaceChats.whereWithData( - {}, + where, pgSize, offset * pgSize, { id: "desc" } ); - const hasPages = (await WorkspaceChats.count()) > (offset + 1) * pgSize; + const totalCount = await WorkspaceChats.count(where); + const hasPages = totalCount > (offset + 1) * pgSize; response.status(200).json({ chats: chats, hasPages }); } catch (e) { console.error(e); diff --git a/server/endpoints/api/workspaceThread/index.js b/server/endpoints/api/workspaceThread/index.js index 9a85b346a82..710e04de8eb 100644 --- a/server/endpoints/api/workspaceThread/index.js +++ b/server/endpoints/api/workspaceThread/index.js @@ -267,12 +267,26 @@ function apiWorkspaceThreadEndpoints(app) { { "role": "user", "content": "What is AnythingLLM?", - "sentAt": 1692851630 + "sentAt": 1692851630, + "attachments": [], + "chatId": 123 }, { + "type": "chat", "role": "assistant", "content": "AnythingLLM is a platform that allows you to convert notes, PDFs, and other source materials into a chatbot. It ensures privacy, cites its answers, and allows multiple people to interact with the same documents simultaneously. It is particularly useful for businesses to enhance the visibility and readability of various written communications such as SOPs, contracts, and sales calls. You can try it out with a free trial to see if it meets your business needs.", - "sources": [{"source": "object about source document and snippets used"}] + "sources": [{"source": "object about source document and snippets used"}], + "chatId": 123, + "sentAt": 1692851635, + "feedbackScore": 0, + "metrics": { + "completionTokens": 29, + "promptTokens": 123, + "totalTokens": 152, + "duration": 3.45, + "outputTps": 4.53 + }, + "feedbackComment": "Bad response because..." } ] } diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js index af4eb9983b3..7f4eddff07c 100644 --- a/server/endpoints/workspaces.js +++ b/server/endpoints/workspaces.js @@ -518,6 +518,46 @@ function workspaceEndpoints(app) { } ); + app.post( + "/workspace/:slug/chat-feedback/:chatId/comment", + [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug], + async (request, response) => { + try { + const { chatId } = request.params; + const { comment = null } = reqBody(request); + const existingChat = await WorkspaceChats.get({ + id: Number(chatId), + workspaceId: response.locals.workspace.id, + }); + + if (!existingChat) { + response.status(404).end(); + return; + } + + if (!comment || String(comment).trim() === "") { + response + .status(400) + .json({ success: false, message: "No comment provided" }); + return; + } + + // Save feedback comment to cache table for now (simple implementation) + await WorkspaceChats._update(Number(chatId), { + // append to response metadata as feedback_comment if desired + feedbackComment: String(comment).trim(), + lastUpdatedAt: new Date(), + }); + + // Optionally, in future persist comments in a dedicated table + response.status(200).json({ success: true }); + } catch (error) { + console.error("Error saving chat feedback comment:", error); + response.status(500).end(); + } + } + ); + app.get( "/workspace/:slug/suggested-messages", [validatedRequest, flexUserRoleValid([ROLES.all])], diff --git a/server/prisma/migrations/20251001000000_init/migration.sql b/server/prisma/migrations/20251001000000_init/migration.sql new file mode 100644 index 00000000000..6b8e99bcd8f --- /dev/null +++ b/server/prisma/migrations/20251001000000_init/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "workspace_chats" ADD COLUMN "feedbackComment" TEXT; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index a3db69f1e2b..ddc14baa9ed 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -193,6 +193,7 @@ model workspace_chats { createdAt DateTime @default(now()) lastUpdatedAt DateTime @default(now()) feedbackScore Boolean? + feedbackComment String? users users? @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Cascade) } diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json index cdca9e46cae..85ea3bd3fe2 100644 --- a/server/swagger/openapi.json +++ b/server/swagger/openapi.json @@ -769,12 +769,13 @@ } }, "requestBody": { - "description": "Page offset to show of workspace chats. All fields are optional and will not update unless specified.", + "description": "Page offset to show of workspace chats. Feedback Score to filter by all, true, or false. All fields are optional and will not update unless specified.", "required": false, "content": { "application/json": { "example": { - "offset": 2 + "offset": 2, + "feedbackScore": true } } } @@ -3057,16 +3058,30 @@ { "role": "user", "content": "What is AnythingLLM?", - "sentAt": 1692851630 + "sentAt": 1692851630, + "attachments": [], + "chatId": 123 }, { + "type": "chat", "role": "assistant", "content": "AnythingLLM is a platform that allows you to convert notes, PDFs, and other source materials into a chatbot. It ensures privacy, cites its answers, and allows multiple people to interact with the same documents simultaneously. It is particularly useful for businesses to enhance the visibility and readability of various written communications such as SOPs, contracts, and sales calls. You can try it out with a free trial to see if it meets your business needs.", "sources": [ { "source": "object about source document and snippets used" } - ] + ], + "chatId": 123, + "sentAt": 1692851635, + "feedbackScore": 0, + "metrics": { + "completionTokens": 29, + "promptTokens": 123, + "totalTokens": 152, + "duration": 3.45, + "outputTps": 4.53 + }, + "feedbackComment": "Bad response because..." } ] } diff --git a/server/utils/helpers/chat/convertTo.js b/server/utils/helpers/chat/convertTo.js index 9ec2b838eea..7fb96106356 100644 --- a/server/utils/helpers/chat/convertTo.js +++ b/server/utils/helpers/chat/convertTo.js @@ -71,6 +71,8 @@ async function prepareChatsForExport(format = "jsonl", chatType = "workspace") { prompt: chat.prompt, response: responseJson.text, sent_at: chat.createdAt, + feedbackScore: chat.feedbackScore ?? "", + feedbackComment: chat.feedbackComment ?? "", // Only add attachments to the json format since we cannot arrange attachments in csv format ...(format === "json" ? { @@ -108,6 +110,7 @@ async function prepareChatsForExport(format = "jsonl", chatType = "workspace") { : chat.feedbackScore ? "GOOD" : "BAD", + comment: chat.feedbackComment === null ? "" : chat.feedbackComment, }; }); @@ -155,6 +158,14 @@ async function prepareChatsForExport(format = "jsonl", chatType = "workspace") { }; } + var feedback = undefined; + if (chat.feedbackScore !== null || chat.feedbackComment !== null) { + feedback = { + score: chat.feedbackScore ?? null, + comment: chat.feedbackComment ?? null, + }; + } + acc[workspaceId].messages.push( { role: "user", @@ -177,6 +188,7 @@ async function prepareChatsForExport(format = "jsonl", chatType = "workspace") { { type: "text", text: responseJson.text, + feedback, }, ], } diff --git a/server/utils/helpers/chat/responses.js b/server/utils/helpers/chat/responses.js index 465d56fd2f6..aab015ac1e3 100644 --- a/server/utils/helpers/chat/responses.js +++ b/server/utils/helpers/chat/responses.js @@ -157,6 +157,7 @@ function convertToChatHistory(history = []) { sentAt: moment(createdAt).unix(), feedbackScore, metrics: data?.metrics || {}, + feedbackComment: record.feedbackComment || null, }, ]); }
+ {t("chat_window.bad_response_optional_feedback")} +