- 删除按钮只保留在用户提问气泡上;AI 回答不再单独提供删除
- 后端 DELETE /messages/{id} 改为按整轮删除:从提问删到下一条提问之前,
返回实际删除的 deleted_ids,保持 checkpoint 合法(无孤立工具结果/回答)
- 前端隐藏后端返回的整批 id,删除后即时消失
- 更新删除相关中英文案
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
2.6 KiB
TypeScript
100 lines
2.6 KiB
TypeScript
import { Trash2Icon } from "lucide-react";
|
|
import { useState } from "react";
|
|
import { toast } from "sonner";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { useI18n } from "@/core/i18n/hooks";
|
|
import { useDeleteMessage } from "@/core/threads/hooks";
|
|
|
|
import { Tooltip } from "../tooltip";
|
|
|
|
/**
|
|
* Hover affordance that deletes a conversation turn (a user question together
|
|
* with its answer) after a confirmation step. The actual removal is persisted
|
|
* server-side; `onDeleted` receives the ids the server removed so the parent
|
|
* can hide those bubbles immediately without waiting for the stream cache to
|
|
* refresh.
|
|
*/
|
|
export function DeleteMessageButton({
|
|
threadId,
|
|
messageId,
|
|
onDeleted,
|
|
className,
|
|
}: {
|
|
threadId: string;
|
|
messageId: string;
|
|
onDeleted?: (deletedIds: string[]) => void;
|
|
className?: string;
|
|
}) {
|
|
const { t } = useI18n();
|
|
const [open, setOpen] = useState(false);
|
|
const deleteMessage = useDeleteMessage();
|
|
|
|
const handleConfirm = async () => {
|
|
try {
|
|
const deletedIds = await deleteMessage.mutateAsync({
|
|
threadId,
|
|
messageId,
|
|
});
|
|
onDeleted?.(deletedIds);
|
|
setOpen(false);
|
|
} catch (error) {
|
|
toast.error(
|
|
error instanceof Error ? error.message : "删除消息失败。",
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Tooltip content={t.inputBox.deleteMessage}>
|
|
<Button
|
|
size="icon-sm"
|
|
type="button"
|
|
variant="ghost"
|
|
className={className}
|
|
onClick={() => setOpen(true)}
|
|
>
|
|
<Trash2Icon size={12} />
|
|
</Button>
|
|
</Tooltip>
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogContent className="sm:max-w-sm">
|
|
<DialogHeader>
|
|
<DialogTitle>{t.inputBox.deleteMessageConfirmTitle}</DialogTitle>
|
|
<DialogDescription>
|
|
{t.inputBox.deleteMessageConfirmDescription}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
onClick={() => setOpen(false)}
|
|
disabled={deleteMessage.isPending}
|
|
>
|
|
{t.common.cancel}
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
variant="destructive"
|
|
onClick={() => void handleConfirm()}
|
|
disabled={deleteMessage.isPending}
|
|
>
|
|
{t.common.delete}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|