From 95d5c156a1131593a56e2d487e21db1fa58eb2c0 Mon Sep 17 00:00:00 2001 From: rayhpeng Date: Fri, 10 Apr 2026 18:32:35 +0800 Subject: [PATCH] fix(feedback): correct run_id mapping for feedback echo The feedbackMap was keyed by run_id but looked up by LangGraph message ID. Fixed by tracking AI message ordinal index to correlate event store run_ids with LangGraph SDK messages. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../workspace/messages/message-list.tsx | 21 +++++++++++-- frontend/src/core/threads/hooks.ts | 30 +++++++++++++++---- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/workspace/messages/message-list.tsx b/frontend/src/components/workspace/messages/message-list.tsx index b7089bb72..28acad1cf 100644 --- a/frontend/src/components/workspace/messages/message-list.tsx +++ b/frontend/src/components/workspace/messages/message-list.tsx @@ -4,6 +4,7 @@ import { Conversation, ConversationContent, } from "@/components/ai-elements/conversation"; +import type { FeedbackData } from "@/core/api/feedback"; import { useI18n } from "@/core/i18n/hooks"; import { extractContentFromMessage, @@ -18,6 +19,7 @@ import { useRehypeSplitWordsIntoSpans } from "@/core/rehype"; import type { Subtask } from "@/core/tasks"; import { useUpdateSubtask } from "@/core/tasks/context"; import type { AgentThreadState } from "@/core/threads"; +import { useThreadFeedback } from "@/core/threads/hooks"; import { cn } from "@/lib/utils"; import { ArtifactFileList } from "../artifacts/artifact-file-list"; @@ -46,7 +48,11 @@ export function MessageList({ const { t } = useI18n(); const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading); const updateSubtask = useUpdateSubtask(); + const { data: feedbackData } = useThreadFeedback(threadId); const messages = thread.messages; + + // Track AI message ordinal index for feedback mapping + let aiMessageIndex = 0; if (thread.isThreadLoading && messages.length === 0) { return ; } @@ -58,12 +64,23 @@ export function MessageList({ {groupMessages(messages, (group) => { if (group.type === "human" || group.type === "assistant") { return group.messages.map((msg) => { + let runId: string | undefined; + let feedback: FeedbackData | null = null; + if (msg.type !== "human" && feedbackData) { + runId = + feedbackData.runIdByAiIndex[aiMessageIndex] ?? undefined; + feedback = runId + ? (feedbackData.feedbackByRunId[runId] ?? null) + : null; + aiMessageIndex++; + } return ( ); }); @@ -167,7 +184,7 @@ export function MessageList({ results.push(
{t.subtasks.executing(tasks.size)}
, diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts index 63f69c243..333c04fb7 100644 --- a/frontend/src/core/threads/hooks.ts +++ b/frontend/src/core/threads/hooks.ts @@ -679,15 +679,29 @@ export function useRenameThread() { }); } +export interface ThreadFeedbackData { + /** Maps AI message ordinal index (0-based, counting only AI messages) to run_id */ + runIdByAiIndex: string[]; + /** Maps run_id to feedback data */ + feedbackByRunId: Record< + string, + { feedback_id: string; rating: number; comment: string | null } + >; +} + export function useThreadFeedback(threadId: string | null | undefined) { return useQuery({ queryKey: ["thread-feedback", threadId], - queryFn: async () => { - if (!threadId) return {}; + queryFn: async (): Promise => { + const empty: ThreadFeedbackData = { + runIdByAiIndex: [], + feedbackByRunId: {}, + }; + if (!threadId) return empty; const res = await fetchWithAuth( `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/messages?limit=200`, ); - if (!res.ok) return {}; + if (!res.ok) return empty; const messages: Array<{ run_id: string; event_type: string; @@ -697,16 +711,20 @@ export function useThreadFeedback(threadId: string | null | undefined) { comment: string | null; } | null; }> = await res.json(); - const feedbackMap: Record< + const runIdByAiIndex: string[] = []; + const feedbackByRunId: Record< string, { feedback_id: string; rating: number; comment: string | null } > = {}; for (const msg of messages) { + if (msg.event_type === "ai_message") { + runIdByAiIndex.push(msg.run_id); + } if (msg.feedback && msg.run_id) { - feedbackMap[msg.run_id] = msg.feedback; + feedbackByRunId[msg.run_id] = msg.feedback; } } - return feedbackMap; + return { runIdByAiIndex, feedbackByRunId }; }, enabled: !!threadId, staleTime: 30_000,