Timothy Jaeryang Baek 2 months ago
parent
commit
fa92abce31

+ 3 - 0
src/lib/components/chat/Messages/ContentRenderer.svelte

@@ -18,6 +18,8 @@
 	export let floatingButtons = true;
 
 	export let onSourceClick = () => {};
+	export let onTaskClick = () => {};
+
 	export let onAddMessages = () => {};
 
 	let contentContainerElement;
@@ -141,6 +143,7 @@
 			return acc.filter((item, index) => acc.indexOf(item) === index);
 		}, [])}
 		{onSourceClick}
+		{onTaskClick}
 		on:update={(e) => {
 			dispatch('update', e.detail);
 		}}

+ 3 - 0
src/lib/components/chat/Messages/Markdown.svelte

@@ -17,7 +17,9 @@
 	export let save = false;
 
 	export let sourceIds = [];
+
 	export let onSourceClick = () => {};
+	export let onTaskClick = () => {};
 
 	let tokens = [];
 
@@ -45,6 +47,7 @@
 		{tokens}
 		{id}
 		{save}
+		{onTaskClick}
 		{onSourceClick}
 		on:update={(e) => {
 			dispatch('update', e.detail);

+ 50 - 1
src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte

@@ -26,6 +26,8 @@
 	export let attributes = {};
 
 	export let save = false;
+
+	export let onTaskClick: Function = () => {};
 	export let onSourceClick: Function = () => {};
 
 	const headerComponent = (depth: number) => {
@@ -168,17 +170,37 @@
 		</div>
 	{:else if token.type === 'blockquote'}
 		<blockquote>
-			<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} />
+			<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} {onTaskClick} {onSourceClick} />
 		</blockquote>
 	{:else if token.type === 'list'}
 		{#if token.ordered}
 			<ol start={token.start || 1}>
 				{#each token.items as item, itemIdx}
 					<li>
+						{#if item?.task}
+							<input
+								class=" translate-y-[1px] -translate-x-1"
+								type="checkbox"
+								checked={item.checked}
+								on:change={(e) => {
+									onTaskClick({
+										id: id,
+										token: token,
+										tokenIdx: tokenIdx,
+										item: item,
+										itemIdx: itemIdx,
+										checked: e.target.checked
+									});
+								}}
+							/>
+						{/if}
+
 						<svelte:self
 							id={`${id}-${tokenIdx}-${itemIdx}`}
 							tokens={item.tokens}
 							top={token.loose}
+							{onTaskClick}
+							{onSourceClick}
 						/>
 					</li>
 				{/each}
@@ -187,15 +209,40 @@
 			<ul>
 				{#each token.items as item, itemIdx}
 					<li>
+						{#if item?.task}
+							<input
+								class=" translate-y-[1px] -translate-x-1"
+								type="checkbox"
+								checked={item.checked}
+								on:change={(e) => {
+									onTaskClick({
+										id: id,
+										token: token,
+										tokenIdx: tokenIdx,
+										item: item,
+										itemIdx: itemIdx,
+										checked: e.target.checked
+									});
+								}}
+							/>
+						{/if}
+
 						<svelte:self
 							id={`${id}-${tokenIdx}-${itemIdx}`}
 							tokens={item.tokens}
 							top={token.loose}
+							{onTaskClick}
+							{onSourceClick}
 						/>
 					</li>
 				{/each}
 			</ul>
 		{/if}
+	{:else if token.type === 'list_item'}
+		{JSON.stringify(token)}
+		<p>
+			<MarkdownInlineTokens id={`${id}-${tokenIdx}-li`} tokens={token.tokens} {onSourceClick} />
+		</p>
 	{:else if token.type === 'details'}
 		<Collapsible title={token.summary} attributes={token?.attributes} className="w-full space-y-1">
 			<div class=" mb-1.5" slot="content">
@@ -203,6 +250,8 @@
 					id={`${id}-${tokenIdx}-d`}
 					tokens={marked.lexer(token.text)}
 					attributes={token?.attributes}
+					{onTaskClick}
+					{onSourceClick}
 				/>
 			</div>
 		</Collapsible>

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

@@ -716,6 +716,9 @@
 										floatingButtons={message?.done}
 										save={!readOnly}
 										{model}
+										onTaskClick={async (e) => {
+											console.log(e);
+										}}
 										onSourceClick={async (e) => {
 											console.log(e);
 											let sourceButton = document.getElementById(`source-${e}`);

+ 67 - 47
src/lib/components/common/RichTextInput.svelte

@@ -35,6 +35,8 @@
 	export let value = '';
 	export let id = '';
 
+	export let raw = false;
+
 	export let preserveBreaks = false;
 	export let generateAutoCompletion: Function = async () => null;
 	export let autocomplete = false;
@@ -137,25 +139,29 @@
 			});
 		}
 
-		async function tryParse(value, attempts = 3, interval = 100) {
-			try {
-				// Try parsing the value
-				return marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
-					breaks: false
-				});
-			} catch (error) {
-				// If no attempts remain, fallback to plain text
-				if (attempts <= 1) {
-					return value;
+		let content = value;
+
+		if (!raw) {
+			async function tryParse(value, attempts = 3, interval = 100) {
+				try {
+					// Try parsing the value
+					return marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
+						breaks: false
+					});
+				} catch (error) {
+					// If no attempts remain, fallback to plain text
+					if (attempts <= 1) {
+						return value;
+					}
+					// Wait for the interval, then retry
+					await new Promise((resolve) => setTimeout(resolve, interval));
+					return tryParse(value, attempts - 1, interval); // Recursive call
 				}
-				// Wait for the interval, then retry
-				await new Promise((resolve) => setTimeout(resolve, interval));
-				return tryParse(value, attempts - 1, interval); // Recursive call
 			}
-		}
 
-		// Usage example
-		let content = await tryParse(value);
+			// Usage example
+			content = await tryParse(value);
+		}
 
 		editor = new Editor({
 			element: element,
@@ -191,28 +197,33 @@
 			onTransaction: () => {
 				// force re-render so `editor.isActive` works as expected
 				editor = editor;
-				let newValue = turndownService
-					.turndown(
-						editor
-							.getHTML()
-							.replace(/<p><\/p>/g, '<br/>')
-							.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
-					)
-					.replace(/\u00a0/g, ' ');
 
-				if (!preserveBreaks) {
-					newValue = newValue.replace(/<br\/>/g, '');
-				}
+				if (!raw) {
+					let newValue = turndownService
+						.turndown(
+							editor
+								.getHTML()
+								.replace(/<p><\/p>/g, '<br/>')
+								.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
+						)
+						.replace(/\u00a0/g, ' ');
+
+					if (!preserveBreaks) {
+						newValue = newValue.replace(/<br\/>/g, '');
+					}
 
-				if (value !== newValue) {
-					value = newValue;
+					if (value !== newValue) {
+						value = newValue;
 
-					// check if the node is paragraph as well
-					if (editor.isActive('paragraph')) {
-						if (value === '') {
-							editor.commands.clearContent();
+						// check if the node is paragraph as well
+						if (editor.isActive('paragraph')) {
+							if (value === '') {
+								editor.commands.clearContent();
+							}
 						}
 					}
+				} else {
+					value = editor.getHTML();
 				}
 			},
 			editorProps: {
@@ -340,21 +351,30 @@
 	// Update the editor content if the external `value` changes
 	$: if (
 		editor &&
-		value !==
-			turndownService
-				.turndown(
-					(preserveBreaks
-						? editor.getHTML().replace(/<p><\/p>/g, '<br/>')
-						: editor.getHTML()
-					).replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
-				)
-				.replace(/\u00a0/g, ' ')
+		(raw
+			? value !== editor.getHTML()
+			: value !==
+				turndownService
+					.turndown(
+						(preserveBreaks
+							? editor.getHTML().replace(/<p><\/p>/g, '<br/>')
+							: editor.getHTML()
+						).replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
+					)
+					.replace(/\u00a0/g, ' '))
 	) {
-		editor.commands.setContent(
-			marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
-				breaks: false
-			})
-		); // Update editor content
+		if (raw) {
+			editor.commands.setContent(value);
+		} else {
+			preserveBreaks
+				? editor.commands.setContent(value)
+				: editor.commands.setContent(
+						marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
+							breaks: false
+						})
+					); // Update editor content
+		}
+
 		selectTemplate();
 	}
 </script>