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,