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
@@ -4,6 +4,7 @@ import {
Conversation, Conversation,
ConversationContent, ConversationContent,
} from "@/components/ai-elements/conversation"; } from "@/components/ai-elements/conversation";
import type { FeedbackData } from "@/core/api/feedback";
import { useI18n } from "@/core/i18n/hooks"; import { useI18n } from "@/core/i18n/hooks";
import { import {
extractContentFromMessage, extractContentFromMessage,
@@ -18,6 +19,7 @@ import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
import type { Subtask } from "@/core/tasks"; import type { Subtask } from "@/core/tasks";
import { useUpdateSubtask } from "@/core/tasks/context"; import { useUpdateSubtask } from "@/core/tasks/context";
import type { AgentThreadState } from "@/core/threads"; import type { AgentThreadState } from "@/core/threads";
import { useThreadFeedback } from "@/core/threads/hooks";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ArtifactFileList } from "../artifacts/artifact-file-list"; import { ArtifactFileList } from "../artifacts/artifact-file-list";
@@ -46,7 +48,11 @@ export function MessageList({
const { t } = useI18n(); const { t } = useI18n();
const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading); const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading);
const updateSubtask = useUpdateSubtask(); const updateSubtask = useUpdateSubtask();
const { data: feedbackData } = useThreadFeedback(threadId);
const messages = thread.messages; const messages = thread.messages;
// Track AI message ordinal index for feedback mapping
let aiMessageIndex = 0;
if (thread.isThreadLoading && messages.length === 0) { if (thread.isThreadLoading && messages.length === 0) {
return <MessageListSkeleton />; return <MessageListSkeleton />;
} }
@@ -58,12 +64,23 @@ export function MessageList({
{groupMessages(messages, (group) => { {groupMessages(messages, (group) => {
if (group.type === "human" || group.type === "assistant") { if (group.type === "human" || group.type === "assistant") {
return group.messages.map((msg) => { 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 ( return (
<MessageListItem <MessageListItem
key={`${group.id}/${msg.id}`} key={`${group.id}/${msg.id}`}
message={msg} message={msg}
isLoading={thread.isLoading} isLoading={thread.isLoading}
threadId={threadId} runId={runId}
feedback={feedback}
/> />
); );
}); });
@@ -167,7 +184,7 @@ export function MessageList({
results.push( results.push(
<div <div
key="subtask-count" 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)} {t.subtasks.executing(tasks.size)}
</div>, </div>,
+24 -6
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) { export function useThreadFeedback(threadId: string | null | undefined) {
return useQuery({ return useQuery({
queryKey: ["thread-feedback", threadId], queryKey: ["thread-feedback", threadId],
queryFn: async () => { queryFn: async (): Promise<ThreadFeedbackData> => {
if (!threadId) return {}; const empty: ThreadFeedbackData = {
runIdByAiIndex: [],
feedbackByRunId: {},
};
if (!threadId) return empty;
const res = await fetchWithAuth( const res = await fetchWithAuth(
`${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/messages?limit=200`, `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/messages?limit=200`,
); );
if (!res.ok) return {}; if (!res.ok) return empty;
const messages: Array<{ const messages: Array<{
run_id: string; run_id: string;
event_type: string; event_type: string;
@@ -697,16 +711,20 @@ export function useThreadFeedback(threadId: string | null | undefined) {
comment: string | null; comment: string | null;
} | null; } | null;
}> = await res.json(); }> = await res.json();
const feedbackMap: Record< const runIdByAiIndex: string[] = [];
const feedbackByRunId: Record<
string, string,
{ feedback_id: string; rating: number; comment: string | null } { feedback_id: string; rating: number; comment: string | null }
> = {}; > = {};
for (const msg of messages) { for (const msg of messages) {
if (msg.event_type === "ai_message") {
runIdByAiIndex.push(msg.run_id);
}
if (msg.feedback && 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, enabled: !!threadId,
staleTime: 30_000, staleTime: 30_000,