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) <noreply@anthropic.com>
This commit is contained in:
rayhpeng 2026-04-10 18:32:35 +08:00
parent 18393b55d1
commit 95d5c156a1
2 changed files with 43 additions and 8 deletions

View File

@ -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 <MessageListSkeleton />;
}
@ -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 (
<MessageListItem
key={`${group.id}/${msg.id}`}
message={msg}
isLoading={thread.isLoading}
threadId={threadId}
runId={runId}
feedback={feedback}
/>
);
});
@ -167,7 +184,7 @@ export function MessageList({
results.push(
<div
key="subtask-count"
className="text-muted-foreground pt-2 text-sm font-normal"
className="text-muted-foreground font-norma pt-2 text-sm"
>
{t.subtasks.executing(tasks.size)}
</div>,

View File

@ -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<ThreadFeedbackData> => {
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,