feat: replace ImMessageEditor with AgentMessageEditor and CustomerMessageEditor, refactor shared message editor logic

- Introduced AgentMessageEditor component for agent message handling.
- Created CustomerMessageEditor component to handle customer messages.
- Refactored shared message editor logic into SharedMessageEditor.
- Removed the old KefuMessageEditor component.
- Updated chat panel to use the new AgentMessageEditor.
- Updated KefuChatShell to use CustomerMessageEditor instead of KefuMessageEditor.
- Added quick reply fetching and handling in AgentMessageEditor.
This commit is contained in:
mlogclub
2026-04-25 18:33:32 +08:00
parent 9a2ae5c505
commit 67900d9849
7 changed files with 594 additions and 745 deletions
@@ -0,0 +1,70 @@
"use client"
import { useEffect, useState } from "react"
import { toast } from "sonner"
import {
SharedMessageEditor,
type UploadedMessageEditorImage,
} from "@/components/chat/shared-message-editor"
import { fetchQuickReplyListAll, type AdminQuickReply } from "@/lib/api/admin"
type AgentMessageEditorProps = {
disabled?: boolean
uploadingAsset?: boolean
onSend: (html: string) => Promise<void>
onUploadImage: (file: File) => Promise<UploadedMessageEditorImage | null>
onSendAttachment: (file: File) => Promise<void>
}
export function AgentMessageEditor({
disabled = false,
uploadingAsset = false,
onSend,
onUploadImage,
onSendAttachment,
}: AgentMessageEditorProps) {
const [quickReplies, setQuickReplies] = useState<AdminQuickReply[]>([])
const [loadingQuickReplies, setLoadingQuickReplies] = useState(true)
const [quickReplyPickerOpen, setQuickReplyPickerOpen] = useState(false)
useEffect(() => {
let cancelled = false
void fetchQuickReplyListAll()
.then((list) => {
if (!cancelled) {
setQuickReplies(list)
}
})
.catch((error) => {
if (!cancelled) {
toast.error(error instanceof Error ? error.message : "加载快捷回复失败")
}
})
.finally(() => {
if (!cancelled) {
setLoadingQuickReplies(false)
}
})
return () => {
cancelled = true
}
}, [])
return (
<SharedMessageEditor
variant="agent"
disabled={disabled}
uploadingAsset={uploadingAsset}
quickReplies={{
open: quickReplyPickerOpen,
loading: loadingQuickReplies,
items: quickReplies,
onOpenChange: setQuickReplyPickerOpen,
}}
onSend={onSend}
onUploadImage={onUploadImage}
onSendAttachment={onSendAttachment}
/>
)
}
@@ -4,7 +4,6 @@ import { memo, useCallback, useEffect, useLayoutEffect, useRef, useState } from
import { toast } from "sonner";
import { ConversationTransferDialog } from "@/components/conversation-actions/transfer-dialog";
import { ImMessageEditor } from "@/components/im-message-editor";
import { ImMessageHTML } from "@/components/im-message-html";
import { useImageLightbox } from "@/components/image-lightbox";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -35,6 +34,7 @@ import {
type AgentConversationFilterKey,
} from "@/lib/stores/agent-conversations";
import { formatDateTime } from "@/lib/utils";
import { AgentMessageEditor } from "./agent-message-editor";
const EMPTY_AGENT_MESSAGES: AgentMessage[] = [];
@@ -410,7 +410,7 @@ export function ChatPanel() {
) : (
<div className="flex h-full min-h-0 flex-col">
<div className="min-h-0 flex-1">
<ImMessageEditor
<AgentMessageEditor
disabled={!conversation || sending}
uploadingAsset={uploadingAsset}
onSend={handleSend}