Browse Source

refac: message edit

Timothy Jaeryang Baek 3 months ago
parent
commit
be20e6dec0

+ 34 - 3
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -332,9 +332,37 @@
 		}
 	};
 
+	let preprocessedDetailsCache = [];
+
+	function preprocessForEditing(content: string): string {
+		// Replace <details>...</details> with unique ID placeholder
+		const detailsBlocks = [];
+		let i = 0;
+
+		content = content.replace(/<details[\s\S]*?<\/details>/gi, (match) => {
+			detailsBlocks.push(match);
+			return `<details id="__DETAIL_${i++}__"/>`;
+		});
+
+		// Store original blocks in the editedContent or globally (see merging later)
+		preprocessedDetailsCache = detailsBlocks;
+
+		return content;
+	}
+
+	function postprocessAfterEditing(content: string): string {
+		const restoredContent = content.replace(
+			/<details id="__DETAIL_(\d+)__"\/>/g,
+			(_, index) => preprocessedDetailsCache[parseInt(index)] || ''
+		);
+
+		return restoredContent;
+	}
+
 	const editMessageHandler = async () => {
 		edit = true;
-		editedContent = message.content;
+
+		editedContent = preprocessForEditing(message.content);
 
 		await tick();
 
@@ -343,7 +371,8 @@
 	};
 
 	const editMessageConfirmHandler = async () => {
-		editMessage(message.id, editedContent ? editedContent : '', false);
+		const messageContent = postprocessAfterEditing(editedContent ? editedContent : '');
+		editMessage(message.id, messageContent, false);
 
 		edit = false;
 		editedContent = '';
@@ -352,7 +381,9 @@
 	};
 
 	const saveAsCopyHandler = async () => {
-		editMessage(message.id, editedContent ? editedContent : '');
+		const messageContent = postprocessAfterEditing(editedContent ? editedContent : '');
+
+		editMessage(message.id, messageContent);
 
 		edit = false;
 		editedContent = '';

+ 12 - 5
src/lib/components/common/Collapsible.svelte

@@ -1,5 +1,6 @@
 <script lang="ts">
 	import { decode } from 'html-entities';
+	import { v4 as uuidv4 } from 'uuid';
 
 	import { getContext, createEventDispatcher } from 'svelte';
 	const i18n = getContext('i18n');
@@ -54,6 +55,8 @@
 	export let disabled = false;
 	export let hide = false;
 
+	const collapsibleId = uuidv4();
+
 	function parseJSONString(str) {
 		try {
 			return parseJSONString(JSON.parse(str));
@@ -128,14 +131,14 @@
 					{:else if attributes?.type === 'tool_calls'}
 						{#if attributes?.done === 'true'}
 							<Markdown
-								id={`tool-calls-${attributes?.id}`}
+								id={`${collapsibleId}-tool-calls-${attributes?.id}`}
 								content={$i18n.t('View Result from **{{NAME}}**', {
 									NAME: attributes.name
 								})}
 							/>
 						{:else}
 							<Markdown
-								id={`tool-calls-${attributes?.id}-executing`}
+								id={`${collapsibleId}-tool-calls-${attributes?.id}-executing`}
 								content={$i18n.t('Executing **{{NAME}}**...', {
 									NAME: attributes.name
 								})}
@@ -208,7 +211,7 @@
 					{#if attributes?.type === 'tool_calls'}
 						{#if attributes?.done === 'true'}
 							<Markdown
-								id={`tool-calls-${attributes?.id}-result`}
+								id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
 								content={`> \`\`\`json
 > ${formatJSONString(args)}
 > ${formatJSONString(result)}
@@ -216,7 +219,7 @@
 							/>
 						{:else}
 							<Markdown
-								id={`tool-calls-${attributes?.id}-result`}
+								id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
 								content={`> \`\`\`json
 > ${formatJSONString(args)}
 > \`\`\``}
@@ -232,7 +235,11 @@
 				{#if typeof files === 'object'}
 					{#each files ?? [] as file, idx}
 						{#if file.startsWith('data:image/')}
-							<Image id={`tool-calls-${attributes?.id}-result-${idx}`} src={file} alt="Image" />
+							<Image
+								id={`${collapsibleId}-tool-calls-${attributes?.id}-result-${idx}`}
+								src={file}
+								alt="Image"
+							/>
 						{/if}
 					{/each}
 				{/if}