fix(rebase): restore FeedbackButtons component and enrichment lost during rebase

The FeedbackButtons component (defined inline in message-list-item.tsx)
was introduced in commit 95df8d13 but lost during rebase. The previous
rebase cleanup commit incorrectly removed the feedback/runId props and
enrichment hook as "orphaned code" instead of restoring the missing
component. This commit restores:

- FeedbackButtons component with thumbs up/down toggle and optimistic state
- FeedbackData/upsertFeedback/deleteFeedback imports
- feedback and runId props on MessageListItem
- useThreadMessageEnrichment hook and entry lookup in message-list.tsx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
rayhpeng 2026-04-12 09:43:36 +08:00
parent 500cdfc8e4
commit 8f7eb28c0d
2 changed files with 88 additions and 2 deletions

View File

@ -1,6 +1,6 @@
import type { Message } from "@langchain/langgraph-sdk";
import { FileIcon, Loader2Icon } from "lucide-react";
import { memo, useMemo, type ImgHTMLAttributes } from "react";
import { FileIcon, Loader2Icon, ThumbsDownIcon, ThumbsUpIcon } from "lucide-react";
import { memo, useCallback, useMemo, useState, type ImgHTMLAttributes } from "react";
import rehypeKatex from "rehype-katex";
import { Loader } from "@/components/ai-elements/loader";
@ -17,6 +17,11 @@ import {
} from "@/components/ai-elements/reasoning";
import { Task, TaskTrigger } from "@/components/ai-elements/task";
import { Badge } from "@/components/ui/badge";
import {
deleteFeedback,
upsertFeedback,
type FeedbackData,
} from "@/core/api/feedback";
import { resolveArtifactURL } from "@/core/artifacts/utils";
import { useI18n } from "@/core/i18n/hooks";
import {
@ -34,16 +39,85 @@ import { CopyButton } from "../copy-button";
import { MarkdownContent } from "./markdown-content";
function FeedbackButtons({
threadId,
runId,
initialFeedback,
}: {
threadId: string;
runId: string;
initialFeedback: FeedbackData | null;
}) {
const [feedback, setFeedback] = useState<FeedbackData | null>(initialFeedback);
const [isSubmitting, setIsSubmitting] = useState(false);
const handleClick = useCallback(
async (rating: number) => {
if (isSubmitting) return;
setIsSubmitting(true);
try {
if (feedback?.rating === rating) {
await deleteFeedback(threadId, runId);
setFeedback(null);
} else {
const result = await upsertFeedback(threadId, runId, rating);
setFeedback(result);
}
} catch {
// Revert on error — feedback state unchanged on catch
} finally {
setIsSubmitting(false);
}
},
[threadId, runId, feedback, isSubmitting],
);
return (
<div className="flex gap-1">
<button
type="button"
className={cn(
"text-muted-foreground hover:text-foreground rounded-md p-1 transition-colors",
feedback?.rating === 1 && "text-foreground",
)}
onClick={() => handleClick(1)}
disabled={isSubmitting}
>
<ThumbsUpIcon
className={cn("size-4", feedback?.rating === 1 && "fill-current")}
/>
</button>
<button
type="button"
className={cn(
"text-muted-foreground hover:text-foreground rounded-md p-1 transition-colors",
feedback?.rating === -1 && "text-foreground",
)}
onClick={() => handleClick(-1)}
disabled={isSubmitting}
>
<ThumbsDownIcon
className={cn("size-4", feedback?.rating === -1 && "fill-current")}
/>
</button>
</div>
);
}
export function MessageListItem({
className,
threadId,
message,
isLoading,
feedback,
runId,
}: {
className?: string;
threadId: string;
message: Message;
isLoading?: boolean;
feedback?: FeedbackData | null;
runId?: string;
}) {
const isHuman = message.type === "human";
return (
@ -72,6 +146,13 @@ export function MessageListItem({
""
}
/>
{feedback !== undefined && runId && threadId && (
<FeedbackButtons
threadId={threadId}
runId={runId}
initialFeedback={feedback}
/>
)}
</div>
</MessageToolbar>
)}

View File

@ -18,6 +18,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 { useThreadMessageEnrichment } from "@/core/threads/hooks";
import { cn } from "@/lib/utils";
import { ArtifactFileList } from "../artifacts/artifact-file-list";
@ -47,6 +48,7 @@ export function MessageList({
const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading);
const updateSubtask = useUpdateSubtask();
const messages = thread.messages;
const { data: enrichment } = useThreadMessageEnrichment(threadId);
if (thread.isThreadLoading && messages.length === 0) {
return <MessageListSkeleton />;
@ -59,12 +61,15 @@ export function MessageList({
{groupMessages(messages, (group) => {
if (group.type === "human" || group.type === "assistant") {
return group.messages.map((msg) => {
const entry = msg.id ? enrichment?.get(msg.id) : undefined;
return (
<MessageListItem
key={`${group.id}/${msg.id}`}
threadId={threadId}
message={msg}
isLoading={thread.isLoading}
runId={entry?.run_id}
feedback={entry?.feedback}
/>
);
});