From 1710c2ee223f3b2c4ee536ab45c73a0f39a05ee7 Mon Sep 17 00:00:00 2001 From: Jagger <634750802@qq.com> Date: Tue, 2 Jul 2024 14:38:54 +0800 Subject: [PATCH] feat: support delete feedback (#174) * feat: support delete feedback * support external user id for app auth * no more requires graph index enabled --- .../messages/[messageId]/feedback/route.ts | 45 +++++++---- src/components/chat/message-feedback.tsx | 74 +++++++++++++------ src/components/chat/message-operations.tsx | 4 +- src/components/chat/use-message-feedback.ts | 13 ++++ src/core/repositories/feedback.ts | 8 ++ src/lib/next/handler.ts | 3 +- 6 files changed, 105 insertions(+), 42 deletions(-) diff --git a/src/app/api/v1/chats/[id]/messages/[messageId]/feedback/route.ts b/src/app/api/v1/chats/[id]/messages/[messageId]/feedback/route.ts index 486ab421..0484bdbb 100644 --- a/src/app/api/v1/chats/[id]/messages/[messageId]/feedback/route.ts +++ b/src/app/api/v1/chats/[id]/messages/[messageId]/feedback/route.ts @@ -1,5 +1,5 @@ import { getChat, getChatMessage } from '@/core/repositories/chat'; -import { createFeedback, findFeedback } from '@/core/repositories/feedback'; +import { createFeedback, deleteFeedback, findFeedback } from '@/core/repositories/feedback'; import { getTraceId } from '@/core/services/feedback/utils'; import { defineHandler } from '@/lib/next/handler'; import { notFound } from 'next/navigation'; @@ -19,13 +19,6 @@ export const GET = defineHandler(({ notFound(); } - // Only support chat with knowledge graph enabled. - if (!chat.engine_options.graph_retriever?.enable) { - return Response.json({ - message: 'This conversation does not support knowledge graph feedback. (Knowledge graph not enabled for this conversation)', - }, { status: 400 }); - } - if (!message.trace_url) { return Response.json({ message: 'This conversation does not support knowledge graph feedback. (Langfuse link not recorded)', @@ -55,13 +48,6 @@ export const POST = defineHandler(({ notFound(); } - // Only support chat with knowledge graph enabled. - if (!chat.engine_options.graph_retriever?.enable) { - return Response.json({ - message: 'This conversation does not support knowledge graph feedback. (Knowledge graph not enabled for this conversation)', - }, { status: 400 }); - } - if (!message.trace_url) { return Response.json({ message: 'This conversation does not support knowledge graph feedback. (Langfuse link not recorded)', @@ -92,4 +78,33 @@ export const POST = defineHandler(({ }); }); +export const DELETE = defineHandler({ + params: z.object({ + id: z.coerce.number(), + messageId: z.coerce.number(), + }), + auth: 'anonymous', +}, async ({ params, auth }) => { + const chat = await getChat(params.id); + const message = await getChatMessage(params.messageId); + + if (!chat || !message) { + notFound(); + } + + if (!message.trace_url) { + return Response.json({ + message: 'This conversation does not support knowledge graph feedback. (Langfuse link not recorded)', + }, { status: 400 }); + } + + const traceId = getTraceId(message.trace_url); + const userId = auth.user.id!; + const feedback = await findFeedback(traceId, userId); + + if (feedback) { + await deleteFeedback(feedback.id); + } +}); + export const dynamic = 'force-dynamic'; diff --git a/src/components/chat/message-feedback.tsx b/src/components/chat/message-feedback.tsx index 1f7ce508..9b0f16cc 100644 --- a/src/components/chat/message-feedback.tsx +++ b/src/components/chat/message-feedback.tsx @@ -1,18 +1,19 @@ import type { ContentSource } from '@/components/chat/use-message-feedback'; import { Button } from '@/components/ui/button'; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Textarea } from '@/components/ui/textarea'; import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; import type { Feedback } from '@/core/repositories/feedback'; import { Loader2Icon, ThumbsDownIcon, ThumbsUpIcon } from 'lucide-react'; import { type ReactElement, useEffect, useState } from 'react'; -export function MessageFeedback ({ initial, source, sourceLoading, onFeedback, children }: { initial?: Feedback, source: ContentSource | undefined, sourceLoading: boolean, onFeedback: (action: 'like' | 'dislike', detail: Record, comment: string) => Promise, children: ReactElement }) { +export function MessageFeedback ({ initial, source, sourceLoading, onFeedback, onDeleteFeedback, children }: { initial?: Feedback, source: ContentSource | undefined, sourceLoading: boolean, onFeedback: (action: 'like' | 'dislike', detail: Record, comment: string) => Promise, onDeleteFeedback: () => Promise, children: ReactElement }) { const [open, setOpen] = useState(false); - const [action, setAction] = useState<'like' | 'dislike'>('like'); + const [action, setAction] = useState<'like' | 'dislike'>(initial?.action ?? 'like'); const [detail, setDetail] = useState>(() => (initial ?? {})); const [comment, setComment] = useState(initial?.comment ?? ''); const [running, setRunning] = useState(false); + const [deleting, setDeleting] = useState(false); useEffect(() => { if (initial) { @@ -22,33 +23,34 @@ export function MessageFeedback ({ initial, source, sourceLoading, onFeedback, c } }, [initial]); - const disabled = running || !!initial; + const disabled = running || deleting || !!initial; + const deleteDisabled = running || deleting || !initial; return ( {children} - + Feedback -
+
Do you like this answer
- setAction(value as any)}> - + setAction(value as any)}> + Like - + Dislike
-
+
Sources from Knowledge Graph
{!source && sourceLoading &&
Loading...
} {source && ( @@ -89,20 +91,44 @@ export function MessageFeedback ({ initial, source, sourceLoading, onFeedback, c disabled={disabled} />
- +
+ + +
); diff --git a/src/components/chat/message-operations.tsx b/src/components/chat/message-operations.tsx index 77052528..f20329a8 100644 --- a/src/components/chat/message-operations.tsx +++ b/src/components/chat/message-operations.tsx @@ -11,7 +11,7 @@ import { useState } from 'react'; export function MessageOperations ({ group }: { group: ConversationMessageGroupProps }) { const { handleRegenerate, id } = useMyChatContext(); - const { feedbackData, feedback: callFeedback, source, sourceLoading, disabled } = useMessageFeedback(id, group.assistantAnnotation.messageId, !!group.assistantAnnotation.traceURL); + const { feedbackData, feedback: callFeedback, deleteFeedback: callDeleteFeedback, source, sourceLoading, disabled } = useMessageFeedback(id, group.assistantAnnotation.messageId, !!group.assistantAnnotation.traceURL); const [copied, setCopied] = useState(false); if (!group.finished) { return; @@ -31,7 +31,7 @@ export function MessageOperations ({ group }: { group: ConversationMessageGroupP callFeedback(action, feedback, comment)}> + source={source} sourceLoading={sourceLoading} onFeedback={async (action, feedback, comment) => callFeedback(action, feedback, comment)} onDeleteFeedback={() => callDeleteFeedback()}> diff --git a/src/components/chat/use-message-feedback.ts b/src/components/chat/use-message-feedback.ts index 3565b333..2af04a7b 100644 --- a/src/components/chat/use-message-feedback.ts +++ b/src/components/chat/use-message-feedback.ts @@ -13,6 +13,8 @@ export interface UseMessageFeedbackReturns { sourceLoading: boolean; feedback (action: 'like' | 'dislike', details: Record, comment: string): Promise; + + deleteFeedback (): Promise; } export type ContentSource = { @@ -38,6 +40,10 @@ export function useMessageFeedback (chatId: number, messageId: number, enabled: setActing(true); return addFeedback(chatId, messageId, { action, knowledge_graph_detail: detail, comment }).finally(() => setActing(false)); }, + deleteFeedback: () => { + setActing(true); + return deleteFeedback(chatId, messageId).finally(() => setActing(false)); + }, source: contentData.data, sourceLoading: contentData.isLoading || contentData.isValidating, }; @@ -50,3 +56,10 @@ async function addFeedback (chatId: number, messageId: number, data: any) { }); mutate(['get', `/api/v1/chats/${chatId}/messages/${messageId}/feedback`], data => data, true); } + +async function deleteFeedback (chatId: number, messageId: number) { + await fetch(`/api/v1/chats/${chatId}/messages/${messageId}/feedback`, { + method: 'delete', + }); + mutate(['get', `/api/v1/chats/${chatId}/messages/${messageId}/feedback`], null, false); +} diff --git a/src/core/repositories/feedback.ts b/src/core/repositories/feedback.ts index 356de2d6..8bc37d89 100644 --- a/src/core/repositories/feedback.ts +++ b/src/core/repositories/feedback.ts @@ -45,6 +45,14 @@ export async function findFeedback (traceId: UUID, user: string): Promise { + return await getDb() + .deleteFrom('feedback') + .where('id', '=', id) + .executeTakeFirstOrThrow() + .then(res => Number(res.numDeletedRows) === 1) +} + export async function listFeedbacks (request: PageRequest<{ chat_id?: number, message_id?: number }>) { let builder = getDb() .selectFrom('feedback') diff --git a/src/lib/next/handler.ts b/src/lib/next/handler.ts index bf18ee5f..fe03441a 100644 --- a/src/lib/next/handler.ts +++ b/src/lib/next/handler.ts @@ -172,6 +172,7 @@ async function verifyCronJobAuth(request: NextRequest): Promise { async function verifyAppAuth(request: NextRequest): Promise { const accessToken = request.headers.get('authorization')?.replace('Bearer ', '')?.trim(); + const externalUserId = request.headers.get('x-external-user-id'); if (!accessToken) { throw APP_AUTH_REQUIRE_AUTH_TOKEN_ERROR; } @@ -181,7 +182,7 @@ async function verifyAppAuth(request: NextRequest): Promise { } return { user: { - id: aat.app_id, + id: externalUserId ? `${aat.app_id}:${externalUserId}` : aat.app_id, role: 'app', }, expires: DateTime.now().plus({ month: 12 }).toISO(),