From b65e059da657a5b752c058f10e452c0410552541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 28 May 2026 20:38:39 -0400 Subject: [PATCH] Let authors edit and delete their own chat messages The editMessage/deleteMessage IPC and their audit events existed but had no UI. Add an inline editor and delete control on a member's own, non-deleted messages; show an edited marker and render deleted messages as a muted placeholder. Thread the handlers from ChatPage through the timeline. --- .../src/renderer/src/pages/ChatPage.tsx | 20 +++++++ .../src/pages/chat/ChatConversation.tsx | 9 +++ .../src/pages/chat/ChatMessageRow.tsx | 55 ++++++++++++++++--- .../renderer/src/pages/chat/ChatTimeline.tsx | 9 +++ 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/AgentCompany-New/src/renderer/src/pages/ChatPage.tsx b/AgentCompany-New/src/renderer/src/pages/ChatPage.tsx index 55f17ee8..686416c5 100644 --- a/AgentCompany-New/src/renderer/src/pages/ChatPage.tsx +++ b/AgentCompany-New/src/renderer/src/pages/ChatPage.tsx @@ -207,6 +207,23 @@ export function ChatPage({ snapshot, actions }: PageProps) { decidedByEmployeeId: employee.id, })); + const editChatMessage = (messageId: string, bodyMd: string) => + void actions.run(() => window.agentCompany.editMessage({ + orgId, + messageId, + actorType: "employee", + actorId: employee.id, + bodyMd, + })); + + const deleteChatMessage = (messageId: string) => + void actions.run(() => window.agentCompany.deleteMessage({ + orgId, + messageId, + actorType: "employee", + actorId: employee.id, + })); + return (
decideApproval(approvalId, "approved")} onDecline={(approvalId) => decideApproval(approvalId, "declined")} + onEditMessage={editChatMessage} + onDeleteMessage={deleteChatMessage} onSubmit={sendInChannel} composerRef={composerRef} /> diff --git a/AgentCompany-New/src/renderer/src/pages/chat/ChatConversation.tsx b/AgentCompany-New/src/renderer/src/pages/chat/ChatConversation.tsx index 11173758..c704cf2a 100644 --- a/AgentCompany-New/src/renderer/src/pages/chat/ChatConversation.tsx +++ b/AgentCompany-New/src/renderer/src/pages/chat/ChatConversation.tsx @@ -27,10 +27,13 @@ export function ChatConversation({ files, context, attachmentFileId, + currentEmployeeId, onAttachmentChange, onOpenThread, onApprove, onDecline, + onEditMessage, + onDeleteMessage, onSubmit, composerRef, }: { @@ -46,10 +49,13 @@ export function ChatConversation({ files: FileObjectRecord[]; context: MessageContext; attachmentFileId: string; + currentEmployeeId: string; onAttachmentChange: (next: string) => void; onOpenThread: (threadId: string) => void; onApprove: (approvalId: string) => void; onDecline: (approvalId: string) => void; + onEditMessage: (messageId: string, bodyMd: string) => void; + onDeleteMessage: (messageId: string) => void; onSubmit: (event: FormEvent) => void; composerRef: MutableRefObject; }) { @@ -73,9 +79,12 @@ export function ChatConversation({ slashInvocations={slashInvocations} files={files} context={context} + currentEmployeeId={currentEmployeeId} onOpenThread={onOpenThread} onApprove={onApprove} onDecline={onDecline} + onEditMessage={onEditMessage} + onDeleteMessage={onDeleteMessage} /> )}
diff --git a/AgentCompany-New/src/renderer/src/pages/chat/ChatMessageRow.tsx b/AgentCompany-New/src/renderer/src/pages/chat/ChatMessageRow.tsx index b42f5de0..2e090263 100644 --- a/AgentCompany-New/src/renderer/src/pages/chat/ChatMessageRow.tsx +++ b/AgentCompany-New/src/renderer/src/pages/chat/ChatMessageRow.tsx @@ -1,5 +1,5 @@ -import type { ReactNode } from "react"; -import { Avatar, Icon, IconButton } from "../../shared/ui"; +import { useState, type FormEvent, type ReactNode } from "react"; +import { Avatar, Button, Icon, IconButton } from "../../shared/ui"; import type { ChatAttachmentRecord, ChatMentionRecord, @@ -20,6 +20,9 @@ export function ChatMessageRow({ onOpenThread, context, compact = false, + canModify = false, + onEdit, + onDelete, }: { message: ChatMessageRecord; attachments: ChatAttachmentRecord[]; @@ -30,12 +33,24 @@ export function ChatMessageRow({ onOpenThread: (() => void) | null; context: MessageContext; compact?: boolean; + canModify?: boolean; + onEdit?: (bodyMd: string) => void; + onDelete?: () => void; }) { + const [editing, setEditing] = useState(false); const name = senderName(message, context); const bot = message.senderType === "agent"; - const messageFiles = messageAttachments(message.id, attachments, files); + const deleted = Boolean(message.deletedAt); + const messageFiles = deleted ? [] : messageAttachments(message.id, attachments, files); const messageMentions = mentions.filter((mention) => mention.messageId === message.id); - const slashInvocation = slashInvocations.find((invocation) => invocation.messageId === message.id) ?? null; + const slashInvocation = deleted ? null : slashInvocations.find((invocation) => invocation.messageId === message.id) ?? null; + const submitEdit = (event: FormEvent) => { + event.preventDefault(); + const next = new FormData(event.currentTarget).get("body"); + const value = typeof next === "string" ? next.trim() : ""; + if (value && onEdit) onEdit(value); + setEditing(false); + }; return (
@@ -43,15 +58,41 @@ export function ChatMessageRow({
{name} {formatTime(message.createdAt)} + {message.editedAt && !deleted && (edited)} + {canModify && !editing && ( + + setEditing(true)} /> + + + )}
{slashInvocation && (
/{slashInvocation.command}
)} -
- {renderBody(message.bodyMd, messageMentions, context)} -
+ {deleted ? ( +
Message deleted
+ ) : editing ? ( +
+