Przeglądaj źródła

refac: tab text variable select

Timothy J. Baek 6 miesięcy temu
rodzic
commit
6c0d3ce736

+ 13 - 26
src/lib/components/chat/MessageInput.svelte

@@ -53,8 +53,9 @@
 
 	let recording = false;
 
-	let chatTextAreaElement: HTMLTextAreaElement;
 	let chatInputContainerElement;
+	let chatInputElement;
+
 	let filesInputElement;
 	let commandsElement;
 
@@ -70,9 +71,10 @@
 	);
 
 	$: if (prompt) {
-		if (chatTextAreaElement) {
-			chatTextAreaElement.style.height = '';
-			chatTextAreaElement.style.height = Math.min(chatTextAreaElement.scrollHeight, 200) + 'px';
+		if (chatInputContainerElement) {
+			chatInputContainerElement.style.height = '';
+			chatInputContainerElement.style.height =
+				Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
 		}
 	}
 
@@ -320,7 +322,8 @@
 							atSelectedModel = data.data;
 						}
 
-						chatTextAreaElement?.focus();
+						const chatInputElement = document.getElementById('chat-input');
+						chatInputElement?.focus();
 					}}
 				/>
 			</div>
@@ -482,7 +485,9 @@
 										}}
 										onClose={async () => {
 											await tick();
-											chatTextAreaElement?.focus();
+
+											const chatInput = document.getElementById('chat-input');
+											chatInput?.focus();
 										}}
 									>
 										<button
@@ -506,9 +511,11 @@
 
 								<div
 									bind:this={chatInputContainerElement}
+									id="chat-input-container"
 									class="scrollbar-hidden text-left bg-gray-50 dark:bg-gray-850 dark:text-gray-100 outline-none w-full py-3 px-1 rounded-xl resize-none h-[48px] overflow-auto"
 								>
 									<RichTextInput
+										bind:this={chatInputElement}
 										id="chat-input"
 										placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
 										bind:value={prompt}
@@ -634,26 +641,6 @@
 												]?.at(-1);
 
 												commandOptionButton?.click();
-											} else if (e.key === 'Tab') {
-												const words = findWordIndices(prompt);
-
-												if (words.length > 0) {
-													const word = words.at(0);
-													const fullPrompt = prompt;
-
-													prompt = prompt.substring(0, word?.endIndex + 1);
-													await tick();
-
-													e.target.scrollTop = e.target.scrollHeight;
-													prompt = fullPrompt;
-													await tick();
-
-													e.preventDefault();
-													e.target.setSelectionRange(word?.startIndex, word.endIndex + 1);
-												}
-
-												e.target.style.height = '';
-												e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
 											}
 
 											if (e.key === 'Escape') {

+ 6 - 10
src/lib/components/chat/MessageInput/Commands/Prompts.svelte

@@ -110,21 +110,17 @@
 
 		prompt = text;
 
+		const chatInputContainerElement = document.getElementById('chat-input-container');
 		const chatInputElement = document.getElementById('chat-input');
 
 		await tick();
 
-		chatInputElement.style.height = '';
-		chatInputElement.style.height = Math.min(chatInputElement.scrollHeight, 200) + 'px';
+		if (chatInputContainerElement) {
+			chatInputContainerElement.style.height = '';
+			chatInputContainerElement.style.height =
+				Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
 
-		chatInputElement?.focus();
-
-		await tick();
-
-		const words = findWordIndices(prompt);
-		if (words.length > 0) {
-			const word = words.at(0);
-			chatInputElement.setSelectionRange(word?.startIndex, word.endIndex + 1);
+			chatInputElement?.focus();
 		}
 	};
 </script>

+ 68 - 2
src/lib/components/common/RichTextInput.svelte

@@ -164,6 +164,60 @@
 		return liftListItem(schema.nodes.list_item)(state, dispatch);
 	}
 
+	function findNextTemplate(doc, from = 0) {
+		const patterns = [
+			{ start: '[', end: ']' },
+			{ start: '{{', end: '}}' }
+		];
+
+		let result = null;
+
+		doc.nodesBetween(from, doc.content.size, (node, pos) => {
+			if (result) return false; // Stop if we've found a match
+			if (node.isText) {
+				const text = node.text;
+				let index = Math.max(0, from - pos);
+				while (index < text.length) {
+					for (const pattern of patterns) {
+						if (text.startsWith(pattern.start, index)) {
+							const endIndex = text.indexOf(pattern.end, index + pattern.start.length);
+							if (endIndex !== -1) {
+								result = {
+									from: pos + index,
+									to: pos + endIndex + pattern.end.length
+								};
+								return false; // Stop searching
+							}
+						}
+					}
+					index++;
+				}
+			}
+		});
+
+		return result;
+	}
+
+	function selectNextTemplate(state, dispatch) {
+		const { doc, selection } = state;
+		const from = selection.to;
+		let template = findNextTemplate(doc, from);
+
+		if (!template) {
+			// If not found, search from the beginning
+			template = findNextTemplate(doc, 0);
+		}
+
+		if (template) {
+			if (dispatch) {
+				const tr = state.tr.setSelection(TextSelection.create(doc, template.from, template.to));
+				dispatch(tr);
+			}
+			return true;
+		}
+		return true;
+	}
+
 	onMount(() => {
 		const initialDoc = markdownToProseMirrorDoc(value || ''); // Convert the initial content
 
@@ -234,14 +288,16 @@
 					},
 
 					// Prevent default tab navigation and provide indent/outdent behavior inside lists:
-					Tab: (state, dispatch, view) => {
+					Tab: chainCommands((state, dispatch, view) => {
 						const { $from } = state.selection;
 						console.log('Tab key pressed', $from.parent, $from.parent.type);
 						if (isInList(state)) {
 							return sinkListItem(schema.nodes.list_item)(state, dispatch);
+						} else {
+							return selectNextTemplate(state, dispatch);
 						}
 						return true; // Prevent Tab from moving the focus
-					},
+					}),
 					'Shift-Tab': (state, dispatch, view) => {
 						const { $from } = state.selection;
 						console.log('Shift-Tab key pressed', $from.parent, $from.parent.type);
@@ -327,6 +383,16 @@
 			selection: TextSelection.atEnd(newDoc) // This sets the cursor at the end
 		});
 		view.updateState(newState);
+
+		// After updating the state, try to find and select the next template
+		setTimeout(() => {
+			const templateFound = selectNextTemplate(view.state, view.dispatch);
+			if (!templateFound) {
+				// If no template found, set cursor at the end
+				const endPos = view.state.doc.content.size;
+				view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, endPos)));
+			}
+		}, 0);
 	}
 
 	// Destroy ProseMirror instance on unmount