diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/ToolInvocationComponent.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/ToolInvocationComponent.tsx index c18f275fc..f2053a3bc 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/ToolInvocationComponent.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/ToolInvocationComponent.tsx @@ -12,51 +12,54 @@ interface ToolInvocationComponentProps { part: ToolUIPart } -export const ToolInvocationComponent: React.FC = ({ part }) => { - const toolName = getToolName(part) - return ( -
-
- - - - {/* Tool Info */} -
-
- - Tool Calling: -

{toolName}

+export const ToolInvocationComponent: React.FC = React.memo( + ({ part }) => { + const toolName = getToolName(part) + + return ( +
+
+ + + + {/* Tool Info */} +
+
+ + Tool Calling: +

{toolName}

+
-
- + - -
- {"input" in part && ( -
-
- Arguments + +
+ {"input" in part && ( +
+
+ Arguments +
+
+                      {JSON.stringify(part.input, null, 2)}
+                    
-
-                    {JSON.stringify(part.input, null, 2)}
-                  
-
- )} + )} - {"output" in part && ( -
-
- Result + {"output" in part && ( +
+
+ Result +
+
+                      {JSON.stringify(part.output, null, 2)}
+                    
-
-                    {JSON.stringify(part.output, null, 2)}
-                  
-
- )} -
-
- - -
- ) -} + )} +
+ + + +
+ ) + }, +) diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayAnalyticsPart.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayAnalyticsPart.tsx index b9724315d..4788e749a 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayAnalyticsPart.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayAnalyticsPart.tsx @@ -14,6 +14,7 @@ import { TableRow, } from "@follow/components/ui/table/index.js" import dayjs from "dayjs" +import { memo } from "react" import type { AIDisplayAnalyticsTool } from "../../__internal__/types" import { ErrorState, LoadingState } from "../common-states" @@ -229,18 +230,7 @@ const OverviewAnalytics = ({ data }: { data: AnalyticsData["overviewStats"] }) = ) } -export const AIDisplayAnalyticsPart = ({ part }: { part: AIDisplayAnalyticsTool }) => { - // Handle loading state - if (part.state === "input-streaming" || part.state === "input-available") { - return ( - - ) - } - +export const AIDisplayAnalyticsPart = memo(({ part }: { part: AIDisplayAnalyticsTool }) => { // Handle error state if (part.state === "output-error") { return ( @@ -256,9 +246,8 @@ export const AIDisplayAnalyticsPart = ({ part }: { part: AIDisplayAnalyticsTool ) } - // Handle no output - const { output } = part - if (!output) { + // Handle no output or invalid state + if (part.state !== "output-available" || !part.output) { return ( { switch (analyticsType) { @@ -309,4 +298,4 @@ export const AIDisplayAnalyticsPart = ({ part }: { part: AIDisplayAnalyticsTool {renderAnalytics()} ) -} +}) diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayEntriesPart.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayEntriesPart.tsx index 789a01ff3..f38ecc3fd 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayEntriesPart.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayEntriesPart.tsx @@ -14,7 +14,7 @@ import { FeedIcon } from "~/modules/feed/feed-icon" import type { AIDisplayEntriesTool } from "../../__internal__/types" import { ErrorState, LoadingState } from "../common-states" -import { CategoryTag, EmptyState, GridContainer, StatCard } from "./shared" +import { CategoryTag, EmptyState, StatCard } from "./shared" type EntryData = AIDisplayEntriesTool["output"]["entries"] type EntryItem = EntryData[number] @@ -59,10 +59,7 @@ const EntriesGrid = ({ } return ( - +
{data.map((item) => ( ))} - +
) } @@ -266,18 +263,7 @@ const GroupedEntries = ({ ) } -export const AIDisplayEntriesPart = ({ part }: { part: AIDisplayEntriesTool }) => { - // Handle loading state - if (part.state === "input-streaming" || part.state === "input-available") { - return ( - - ) - } - +export const AIDisplayEntriesPart = memo(({ part }: { part: AIDisplayEntriesTool }) => { // Handle error state if (part.state === "output-error") { return ( @@ -355,16 +341,16 @@ export const AIDisplayEntriesPart = ({ part }: { part: AIDisplayEntriesTool }) = {/* Statistics Overview */} - +
- +
{/* Entries Display */} {renderEntries()}
) -} +}) diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayFeedsPart.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayFeedsPart.tsx index 202f78752..7e22e8bbf 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayFeedsPart.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplayFeedsPart.tsx @@ -6,12 +6,13 @@ import { CardTitle, } from "@follow/components/ui/card/index.js" import dayjs from "dayjs" +import { memo } from "react" import { FeedIcon } from "~/modules/feed/feed-icon" import type { AIDisplayFeedsTool } from "../../__internal__/types" import { ErrorState, LoadingState } from "../common-states" -import { AnalyticsMetrics, EmptyState, GridContainer, StatCard } from "./shared" +import { AnalyticsMetrics, EmptyState, StatCard } from "./shared" type FeedData = AIDisplayFeedsTool["output"]["feeds"] @@ -21,7 +22,7 @@ const FeedsGrid = ({ data, showAnalytics }: { data: FeedData; showAnalytics: boo } return ( - +
{data.map((item) => ( @@ -72,22 +73,11 @@ const FeedsGrid = ({ data, showAnalytics }: { data: FeedData; showAnalytics: boo ))} - +
) } -export const AIDisplayFeedsPart = ({ part }: { part: AIDisplayFeedsTool }) => { - // Handle loading state - if (part.state === "input-streaming" || part.state === "input-available") { - return ( - - ) - } - +export const AIDisplayFeedsPart = memo(({ part }: { part: AIDisplayFeedsTool }) => { // Handle error state if (part.state === "output-error") { return ( @@ -145,7 +135,7 @@ export const AIDisplayFeedsPart = ({ part }: { part: AIDisplayFeedsTool }) => { {/* Statistics Overview */} - +
{ )} - +
{/* Feeds Display */} {renderFeeds()}
) -} +}) diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplaySubscriptionsPart.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplaySubscriptionsPart.tsx index 9b0653493..f459900d3 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplaySubscriptionsPart.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/AIDisplaySubscriptionsPart.tsx @@ -6,12 +6,13 @@ import { CardTitle, } from "@follow/components/ui/card/index.js" import dayjs from "dayjs" +import { memo } from "react" import { FeedIcon } from "~/modules/feed/feed-icon" import type { AIDisplaySubscriptionsTool } from "../../__internal__/types" import { ErrorState, LoadingState } from "../common-states" -import { AnalyticsMetrics, CategoryTag, EmptyState, GridContainer, StatCard } from "./shared" +import { AnalyticsMetrics, CategoryTag, EmptyState, StatCard } from "./shared" type SubscriptionData = AIDisplaySubscriptionsTool["output"]["subscriptions"] @@ -58,7 +59,7 @@ const SubscriptionsGrid = ({ } return ( - +
{data.map((sub) => ( @@ -105,7 +106,7 @@ const SubscriptionsGrid = ({ ))} - +
) } @@ -171,18 +172,8 @@ const GroupedSubscriptions = ({ ) } -export const AIDisplaySubscriptionsPart = ({ part }: { part: AIDisplaySubscriptionsTool }) => { - // Handle loading state - if (part.state === "input-streaming" || part.state === "input-available") { - return ( - - ) - } - +export const AIDisplaySubscriptionsPart = memo(({ part }: { part: AIDisplaySubscriptionsTool }) => { + return null // Handle error state if (part.state === "output-error") { return ( @@ -194,7 +185,7 @@ export const AIDisplaySubscriptionsPart = ({ part }: { part: AIDisplaySubscripti ) } - // Handle no output or invalid state + // Handle loading state if (part.state !== "output-available" || !part.output) { return ( {/* Statistics Overview */} - +
{showCategories && } - +
{/* Subscriptions Display */} {renderSubscriptions()}
) -} +}) diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/GridContainer.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/GridContainer.tsx deleted file mode 100644 index 493ef2094..000000000 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/GridContainer.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { cn } from "@follow/utils/utils" -import type { ReactNode } from "react" - -export interface GridContainerProps { - columns?: { - base: number - md: number - lg?: number - } - gap?: number - children: ReactNode - className?: string -} - -export const GridContainer = ({ - columns = { base: 1, md: 2 }, - gap = 4, - children, - className, -}: GridContainerProps) => ( -
- {children} -
-) diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/StatisticsOverview.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/StatisticsOverview.tsx deleted file mode 100644 index c93cf151e..000000000 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/StatisticsOverview.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { cn } from "@follow/utils/utils" - -import type { StatCardProps } from "./StatCard" -import { StatCard } from "./StatCard" - -export interface StatisticsOverviewProps { - stats: StatCardProps[] - columns?: { - base: number - md: number - lg?: number - } - className?: string -} - -export const StatisticsOverview = ({ - stats, - columns = { base: 2, md: 4 }, - className, -}: StatisticsOverviewProps) => ( -
- {stats.map((stat, index) => ( - - ))} -
-) diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/index.ts b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/index.ts index 9f5d25734..6279e0af9 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/index.ts +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/displays/shared/index.ts @@ -4,7 +4,5 @@ export * from "./ChartPlaceholder" export * from "./DisplayHeader" export * from "./EmptyState" export * from "./FeedItemCard" -export * from "./GridContainer" export * from "./GroupedContent" export * from "./StatCard" -export * from "./StatisticsOverview" diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/layouts/AIChatSendButton.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/layouts/AIChatSendButton.tsx index a4a58b0f0..bf843d6af 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/layouts/AIChatSendButton.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/layouts/AIChatSendButton.tsx @@ -26,7 +26,7 @@ export const AIChatSendButton: FC = ({ ? "bg-red-500/90 hover:bg-red-500 shadow-lg shadow-red-500/25 backdrop-blur-sm" : disabled ? "bg-gray-200/80 cursor-not-allowed backdrop-blur-sm" - : "bg-gradient-to-r from-blue-500/90 to-blue-600/90 hover:from-blue-500 hover:to-blue-600 shadow-lg shadow-blue-500/25 backdrop-blur-sm hover:shadow-blue-500/35", + : "bg-gradient-to-r from-accent to-accent/90 hover:from-accent hover:to-accent/90 shadow-lg shadow-accent/25 backdrop-blur-sm hover:shadow-accent/35", className, )} > diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/AIChatMessage.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/AIChatMessage.tsx index e1ca63753..27d4562bb 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/AIChatMessage.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/AIChatMessage.tsx @@ -1,5 +1,7 @@ +import { createDefaultLexicalEditor } from "@follow/components/ui/lexical-rich-editor/editor.js" import { stopPropagation } from "@follow/utils" import type { UIDataTypes, UIMessage } from "ai" +import type { LexicalEditor, SerializedEditorState } from "lexical" import { m } from "motion/react" import * as React from "react" import { toast } from "sonner" @@ -9,6 +11,8 @@ import { useChatActions } from "~/modules/ai/chat/__internal__/hooks" import type { BizUIMetadata, BizUITools } from "~/modules/ai/chat/__internal__/types" import { useEditingMessageId, useSetEditingMessageId } from "~/modules/ai/chat/atoms/session" +import type { RichTextPart } from "../../types/ChatSession" +import { convertLexicalToMarkdown } from "../../utils/lexical-markdown" import { AIMessageParts } from "./AIMessageParts" import { EditableMessage } from "./EditableMessage" @@ -36,39 +40,67 @@ export const AIChatMessage: React.FC = React.memo(({ message const isEditing = editingMessageId === messageId const isUserMessage = message.role === "user" - // Get message content for editing - const messageContent = React.useMemo(() => { - return ( - message.parts - ?.filter((part) => part.type === "text") - .map((part) => part.text) - .join(" ") || "" - ) - }, [message.parts]) - const handleEdit = React.useCallback(() => { if (isUserMessage) { setEditingMessageId(messageId) } }, [isUserMessage, messageId, setEditingMessageId]) - const handleSaveEdit = React.useCallback( - (newContent: string) => { - const messages = chatActions.getMessages() - if (newContent.trim() !== messageContent.trim()) { - // Find the message index and remove all messages after it (including AI responses) - const messageIndex = messages.findIndex((msg) => msg.id === messageId) - if (messageIndex !== -1) { - const messagesToKeep = messages.slice(0, messageIndex) - chatActions.setMessages(messagesToKeep) - - // Send the edited message - chatActions.sendMessage(newContent) + const getMessageMarkdownFormat = React.useCallback(() => { + let content = "" + for (const part of message.parts) { + let lexicalEditor: LexicalEditor | null = null + switch (part.type) { + case "text": { + content += part.text + break } + case "data-rich-text": { + lexicalEditor ||= createDefaultLexicalEditor() + lexicalEditor.setEditorState( + lexicalEditor.parseEditorState((part as RichTextPart).data.state), + ) + content += convertLexicalToMarkdown(lexicalEditor) + break + } + + default: { + if (part.type.startsWith("tool-")) { + content += `\n\n[TOOL CALL: ${part.type.replace("tool-", "")}]\n\n` + } + break + } + } + } + return content + }, [message.parts]) + + const handleSaveEdit = React.useCallback( + (newState: SerializedEditorState, editor: LexicalEditor) => { + const messageContent = convertLexicalToMarkdown(editor) + const messages = chatActions.getMessages() + const messageIndex = messages.findIndex((msg) => msg.id === messageId) + if (messageIndex !== -1) { + const messagesToKeep = messages.slice(0, messageIndex) + const nextMessage = messages[messageIndex]! + chatActions.setMessages(messagesToKeep) + + const richTextPart = nextMessage.parts.find( + (part) => part.type === "data-rich-text", + ) as RichTextPart + if (richTextPart) { + richTextPart.data = { + state: newState, + text: messageContent, + } + } + + // Send the edited message + chatActions.sendMessage(nextMessage) } setEditingMessageId(null) }, - [messageContent, messageId, chatActions, setEditingMessageId], + [chatActions, messageId, setEditingMessageId], ) const handleCancelEdit = React.useCallback(() => { @@ -76,13 +108,14 @@ export const AIChatMessage: React.FC = React.memo(({ message }, [setEditingMessageId]) const handleCopy = React.useCallback(async () => { + const messageContent = getMessageMarkdownFormat() try { await copyToClipboard(messageContent) toast.success("Message copied to clipboard") } catch { toast.error("Failed to copy message") } - }, [messageContent]) + }, [getMessageMarkdownFormat]) const handleRetry = React.useCallback(() => { chatActions.regenerate({ messageId }) @@ -115,7 +148,7 @@ export const AIChatMessage: React.FC = React.memo(({ message {isEditing && isUserMessage ? ( diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/AIRichTextMessage.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/AIRichTextMessage.tsx index cef7f88f4..f4cef9c96 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/AIRichTextMessage.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/AIRichTextMessage.tsx @@ -1,17 +1,12 @@ -import { defaultLexicalTheme } from "@follow/components/ui/lexical-rich-editor/index.js" +import { LexicalRichEditorNodes } from "@follow/components/ui/lexical-rich-editor/nodes.js" +import { defaultLexicalTheme } from "@follow/components/ui/lexical-rich-editor/theme.js" import { cn } from "@follow/utils" -import { CodeHighlightNode, CodeNode } from "@lexical/code" -import { LinkNode } from "@lexical/link" -import { ListItemNode, ListNode } from "@lexical/list" -import { MarkNode } from "@lexical/mark" import type { InitialConfigType } from "@lexical/react/LexicalComposer" import { LexicalComposer } from "@lexical/react/LexicalComposer" import { ContentEditable } from "@lexical/react/LexicalContentEditable" import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary" import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin" -import { HeadingNode, QuoteNode } from "@lexical/rich-text" import type { SerializedEditorState } from "lexical" -import { ParagraphNode, TextNode } from "lexical" import * as React from "react" function onError(error: Error) { @@ -26,49 +21,29 @@ interface AIRichTextMessageProps { className?: string } -export const AIRichTextMessage: React.FC = ({ data, className }) => { - const initialConfig: InitialConfigType = { - namespace: "AIRichTextDisplay", - theme: defaultLexicalTheme, - onError, - editable: false, // Read-only mode - editorState: JSON.stringify(data.state), - nodes: [ - // Core nodes - ParagraphNode, - TextNode, +export const AIRichTextMessage: React.FC = React.memo( + ({ data, className }) => { + const initialConfig: InitialConfigType = { + namespace: "AIRichTextDisplay", + theme: defaultLexicalTheme, + onError, + editable: false, // Read-only mode + editorState: JSON.stringify(data.state), + nodes: LexicalRichEditorNodes, + } - // Rich text nodes - HeadingNode, - QuoteNode, - - // List nodes - ListNode, - ListItemNode, - - // Code nodes - CodeNode, - CodeHighlightNode, - - // Link nodes - LinkNode, - - // Text format nodes - MarkNode, - ], - } - - return ( -
- - - } - ErrorBoundary={LexicalErrorBoundary} - placeholder={null} - /> - -
- ) -} + return ( +
+ + + } + ErrorBoundary={LexicalErrorBoundary} + placeholder={null} + /> + +
+ ) + }, +) diff --git a/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/EditableMessage.tsx b/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/EditableMessage.tsx index c520ae37b..6c470ba64 100644 --- a/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/EditableMessage.tsx +++ b/apps/desktop/layer/renderer/src/modules/ai/chat/components/message/EditableMessage.tsx @@ -1,21 +1,29 @@ -import { useInputComposition } from "@follow/hooks" +import type { LexicalRichEditorRef } from "@follow/components/ui/lexical-rich-editor/index.js" +import { + createDefaultLexicalEditor, + LexicalRichEditor, +} from "@follow/components/ui/lexical-rich-editor/index.js" import { cn } from "@follow/utils" -import { useCallback, useEffect, useRef, useState } from "react" +import type { BizUIMessage } from "@folo-services/ai-tools" +import { isEqual } from "es-toolkit" +import type { EditorState, LexicalEditor, SerializedEditorState } from "lexical" +import { $getRoot } from "lexical" +import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { useChatStatus } from "~/modules/ai/chat/__internal__/hooks" import { useEditingMessageId, useSetEditingMessageId } from "~/modules/ai/chat/atoms/session" interface EditableMessageProps { messageId: string - initialContent: string - onSave: (content: string) => void + parts: BizUIMessage["parts"] + onSave: (content: SerializedEditorState, editor: LexicalEditor) => void onCancel: () => void className?: string } export const EditableMessage = ({ messageId, - initialContent, + parts, onSave, onCancel, className, @@ -23,72 +31,69 @@ export const EditableMessage = ({ const status = useChatStatus() const editingMessageId = useEditingMessageId() const setEditingMessageId = useSetEditingMessageId() - const [content, setContent] = useState(initialContent) const [isEmpty, setIsEmpty] = useState(false) - const textareaRef = useRef(null) + const editorRef = useRef(null) + const [currentEditor, setCurrentEditor] = useState(null) + const initialEditorState = useMemo(() => { + const serializedEditorState = (parts.find((part) => part.type === "data-rich-text") as any) + ?.data.state as SerializedEditorState + return createDefaultLexicalEditor().parseEditorState(serializedEditorState) + }, [parts]) const isEditing = editingMessageId === messageId const isProcessing = status === "submitted" || status === "streaming" - // Auto-resize textarea and maintain minimum height to prevent CLS + // Initialize editor with initial content useEffect(() => { - if (textareaRef.current && isEditing) { - const textarea = textareaRef.current - textarea.style.height = "auto" - const newHeight = Math.max(56, textarea.scrollHeight) // Minimum height of 56px - textarea.style.height = `${newHeight}px` + if (isEditing && editorRef.current && currentEditor) { + // Focus the editor + editorRef.current.focus() } - }, [content, isEditing]) - - // Focus on edit start - useEffect(() => { - if (isEditing && textareaRef.current) { - textareaRef.current.focus() - textareaRef.current.setSelectionRange( - textareaRef.current.value.length, - textareaRef.current.value.length, - ) - } - }, [isEditing]) + }, [isEditing, initialEditorState, currentEditor]) const handleSave = useCallback(() => { - if (content.trim() && content.trim() !== initialContent) { - onSave(content.trim()) + if (currentEditor && editorRef.current && !editorRef.current.isEmpty()) { + const serializedEditorState = currentEditor.getEditorState().toJSON() + + if (!isEqual(serializedEditorState, initialEditorState.toJSON())) { + onSave(serializedEditorState, currentEditor) + } } - setEditingMessageId(null) - }, [content, initialContent, onSave, setEditingMessageId]) + }, [currentEditor, initialEditorState, onSave]) const handleCancel = useCallback(() => { - setContent(initialContent) setEditingMessageId(null) onCancel() - }, [initialContent, onCancel, setEditingMessageId]) + }, [onCancel, setEditingMessageId]) - const handleKeyPress = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault() + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault() if (!isProcessing) { handleSave() } - } else if (e.key === "Escape") { - e.preventDefault() + return true + } else if (event.key === "Escape") { + event.preventDefault() handleCancel() + return true } + return false }, [handleSave, handleCancel, isProcessing], ) - const handleChange = useCallback((e: React.ChangeEvent) => { - const newContent = e.target.value - setContent(newContent) - setIsEmpty(newContent.trim() === "") + const handleEditorChange = useCallback((editorState: EditorState, editor: LexicalEditor) => { + setCurrentEditor(editor) + // Update isEmpty state based on editor content + editorState.read(() => { + const root = $getRoot() + const textContent = root.getTextContent().trim() + setIsEmpty(textContent === "") + }) }, []) - const inputProps = useInputComposition({ - onKeyDown: handleKeyPress, - }) - if (!isEditing) { return null } @@ -97,15 +102,14 @@ export const EditableMessage = ({
{/* Edit input */}
-