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 ? (
- }>
+ }>
Cancel
) : (
- }>
+ }>
Delete
)}
{isDeleteArmed && (
- }>
+ }>
Delete
)}
@@ -123,24 +127,22 @@ export function DocumentFragmentEditor(props: {
{isEditing ? (
- }>
+ }>
Cancel
) : (
- }>
+ }>
Edit
)}
{isEditing && (
- }>
+ }>
Save
)}
-
-
);
}
\ 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));
}