diff --git a/src/apps/chat/components/message/fragments-attachment-text/DocumentFragmentEditor.tsx b/src/apps/chat/components/message/fragments-attachment-text/DocumentFragmentEditor.tsx index 95d315a02..f8eae0613 100644 --- a/src/apps/chat/components/message/fragments-attachment-text/DocumentFragmentEditor.tsx +++ b/src/apps/chat/components/message/fragments-attachment-text/DocumentFragmentEditor.tsx @@ -9,7 +9,7 @@ import EditRoundedIcon from '@mui/icons-material/EditRounded'; import { BlocksRenderer } from '~/modules/blocks/BlocksRenderer'; import type { ContentScaling } from '~/common/app.theme'; -import type { DMessageAttachmentFragment, DMessageFragmentId, DMessageRole } from '~/common/stores/chat/chat.message'; +import { createTextAttachmentFragment, DMessageAttachmentFragment, DMessageFragmentId, DMessageRole } from '~/common/stores/chat/chat.message'; import { marshallWrapText } from '~/common/stores/chat/chat.tokens'; import { ContentPartTextEdit } from '../fragments-content/ContentPartTextEdit'; @@ -28,7 +28,7 @@ export function DocumentFragmentEditor(props: { }) { // derived state - const { fragment, onFragmentDelete, onFragmentReplace } = props; + const { editedText, fragment, onFragmentDelete, onFragmentReplace } = props; const [isEditing, setIsEditing] = React.useState(false); const [isDeleteArmed, setIsDeleteArmed] = React.useState(false); @@ -36,34 +36,39 @@ export function DocumentFragmentEditor(props: { const fragmentTitle = fragment.title; const part = fragment.part; - // handlers - - const handleDeleteFragment = React.useCallback(() => { - onFragmentDelete(fragmentId); - }, [fragmentId, onFragmentDelete]); - - const handleReplaceFragment = React.useCallback((newFragment: DMessageAttachmentFragment) => { - onFragmentReplace(fragmentId, newFragment); - }, [fragmentId, onFragmentReplace]); - - if (part.pt !== 'text') throw new Error('Unexpected part type: ' + part.pt); - const handleEditToggle = React.useCallback(() => { + // delete + + const handleToggleDeleteArmed = React.useCallback(() => { + // setIsEditing(false); + setIsDeleteArmed(on => !on); + }, []); + + const handleFragmentDelete = React.useCallback(() => { + onFragmentDelete(fragmentId); + }, [fragmentId, onFragmentDelete]); + + + // edit + + const handleToggleEdit = React.useCallback(() => { setIsDeleteArmed(false); setIsEditing(on => !on); }, []); - const handleEditEnterPressed = React.useCallback(() => { - // setIsEditing(false); - // TODO... - }, []); - - const handleDeleteArmedToggle = React.useCallback(() => { - setIsEditing(false); - setIsDeleteArmed(on => !on); - }, []); + const handleEditApply = React.useCallback(() => { + setIsDeleteArmed(false); + if (editedText === undefined) + return; + if (editedText?.length > 0) { + onFragmentReplace(fragmentId, createTextAttachmentFragment(fragmentTitle, editedText)); + // NOTE: since the former function changes the ID of the fragment, the + // whole editor will disappear as a side effect + } else + handleFragmentDelete(); + }, [editedText, fragmentId, fragmentTitle, handleFragmentDelete, onFragmentReplace]); return ( @@ -85,8 +90,8 @@ export function DocumentFragmentEditor(props: { contentScaling={props.contentScaling} editedText={props.editedText} setEditedText={props.setEditedText} - onEnterPressed={handleEditEnterPressed} - onEscapePressed={handleEditToggle} + onEnterPressed={handleEditApply} + onEscapePressed={handleToggleEdit} /> ) : ( // Document viewer, including collapse/expand @@ -103,19 +108,18 @@ export function DocumentFragmentEditor(props: { {/* Edit / Delete commands */} - {isDeleteArmed ? ( - ) : ( - )} {isDeleteArmed && ( - )} @@ -123,24 +127,22 @@ export function DocumentFragmentEditor(props: { {isEditing ? ( - ) : ( - )} {isEditing && ( - )} - - ); } \ No newline at end of file diff --git a/src/apps/chat/components/message/fragments-attachment-text/DocumentFragments.tsx b/src/apps/chat/components/message/fragments-attachment-text/DocumentFragments.tsx index 44000d47d..c1ccf6d6e 100644 --- a/src/apps/chat/components/message/fragments-attachment-text/DocumentFragments.tsx +++ b/src/apps/chat/components/message/fragments-attachment-text/DocumentFragments.tsx @@ -25,26 +25,26 @@ export function DocumentFragments(props: { }) { // state - const [selectedFragmentId, setSelectedFragmentId] = React.useState(null); - const [textAttachmentsEditState, setTextAttachmentsEditState] = React.useState(null); + const [activeFragmentId, setActiveFragmentId] = React.useState(null); + const [editState, setEditState] = React.useState(null); + + + // selection + + const handleToggleSelectedId = React.useCallback((fragmentId: DMessageFragmentId) => setActiveFragmentId(prevId => prevId === fragmentId ? null : fragmentId), []); + + const selectedFragment = props.attachmentFragments.find(fragment => fragment.fId === activeFragmentId); + + + // editing + + const handleEditSetText = React.useCallback((fragmentId: DMessageFragmentId, value: string) => setEditState(prevState => ({ ...prevState, [fragmentId]: value })), []); // [effect] clear edits on onmount React.useEffect(() => { - return () => setTextAttachmentsEditState(null); + return () => setEditState(null); }, []); - const handleToggleSelected = React.useCallback((fragmentId: DMessageFragmentId) => { - setSelectedFragmentId(prevId => prevId === fragmentId ? null : fragmentId); - }, []); - - const handleSetEditedText = React.useCallback((fragmentId: DMessageFragmentId, value: string) => { - setTextAttachmentsEditState(prevState => ({ - ...prevState, - [fragmentId]: value, - })); - }, []); - - const selectedFragment = props.attachmentFragments.find(fragment => fragment.fId === selectedFragmentId); return ( , )} @@ -79,8 +79,8 @@ export function DocumentFragments(props: { ')); + newFragments.push(createTextAttachmentFragment(ref || '\n', input.altData!)); break; // html to markdown table @@ -333,7 +333,7 @@ export async function attachmentPerformConversion( // fallback to text/plain mdTable = inputDataToString(input.data); } - newFragments.push(createTextAttachmentFragment(mdTable, ref)); + newFragments.push(createTextAttachmentFragment(ref, mdTable)); break; // image resized (default mime/quality, openai-high-res) @@ -397,7 +397,7 @@ export async function attachmentPerformConversion( }, }); const imageText = result.data.text; - newFragments.push(createTextAttachmentFragment(imageText, ref)); + newFragments.push(createTextAttachmentFragment(ref, imageText)); } catch (error) { console.error(error); } @@ -413,7 +413,7 @@ export async function attachmentPerformConversion( // duplicate the ArrayBuffer to avoid mutation const pdfData = new Uint8Array(input.data.slice(0)); const pdfText = await pdfToText(pdfData); - newFragments.push(createTextAttachmentFragment(pdfText, ref)); + newFragments.push(createTextAttachmentFragment(ref, pdfText)); break; // pdf to images diff --git a/src/common/stores/chat/chat.message.ts b/src/common/stores/chat/chat.message.ts index cdccefdbc..3eabf89c1 100644 --- a/src/common/stores/chat/chat.message.ts +++ b/src/common/stores/chat/chat.message.ts @@ -188,7 +188,7 @@ function createContentFragment(part: DMessageContentFragment['part']): DMessageC } -export function createTextAttachmentFragment(text: string, title: string): DMessageAttachmentFragment { +export function createTextAttachmentFragment(title: string, text: string): DMessageAttachmentFragment { return createAttachmentFragment(title, createDMessageTextPart(text)); }