浏览代码

feat: text select quick actions

Timothy J. Baek 7 月之前
父节点
当前提交
0ad35ffad9

+ 5 - 0
src/lib/components/chat/Chat.svelte

@@ -1978,6 +1978,11 @@
 								{mergeResponses}
 								{chatActionHandler}
 								bottomPadding={files.length > 0}
+								on:submit={(e) => {
+									if (e.detail) {
+										submitPrompt(e.detail);
+									}
+								}}
 							/>
 						</div>
 					</div>

+ 5 - 1
src/lib/components/chat/Messages.svelte

@@ -1,7 +1,8 @@
 <script lang="ts">
 	import { v4 as uuidv4 } from 'uuid';
 	import { chats, config, settings, user as _user, mobile, currentChatPage } from '$lib/stores';
-	import { tick, getContext, onMount } from 'svelte';
+	import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
 
 	import { toast } from 'svelte-sonner';
 	import { getChatList, updateChatById } from '$lib/apis/chats';
@@ -382,6 +383,9 @@
 							{continueResponse}
 							{mergeResponses}
 							{readOnly}
+							on:submit={async (e) => {
+								dispatch('submit', e.detail);
+							}}
 							on:action={async (e) => {
 								if (typeof e.detail === 'string') {
 									await chatActionHandler(chatId, e.detail, message.model, message.id);

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

@@ -0,0 +1,79 @@
+<script>
+	import { onDestroy, onMount, tick, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	import Markdown from './Markdown.svelte';
+	import LightBlub from '$lib/components/icons/LightBlub.svelte';
+
+	export let id;
+	export let content;
+	export let model = null;
+
+	export let floatingButtons = true;
+
+	let contentContainerElement;
+	let buttonsContainerElement;
+
+	const updateButtonPosition = () => {
+		setTimeout(async () => {
+			await tick();
+			let selection = window.getSelection();
+
+			if (selection.toString().trim().length > 0) {
+				const range = selection.getRangeAt(0);
+				const rect = range.getBoundingClientRect();
+				const parentRect = contentContainerElement.getBoundingClientRect();
+
+				// Adjust based on parent rect
+				const top = rect.bottom - parentRect.top;
+				const left = rect.left - parentRect.left;
+
+				buttonsContainerElement.style.display = 'block';
+				buttonsContainerElement.style.left = `${left}px`;
+				buttonsContainerElement.style.top = `${top + 5}px`; // +5 to add some spacing
+			} else {
+				buttonsContainerElement.style.display = 'none';
+			}
+		}, 0);
+	};
+
+	onMount(() => {
+		if (floatingButtons) {
+			contentContainerElement?.addEventListener('mouseup', updateButtonPosition);
+		}
+	});
+
+	onDestroy(() => {
+		if (floatingButtons) {
+			contentContainerElement?.removeEventListener('mouseup', updateButtonPosition);
+		}
+	});
+</script>
+
+<div bind:this={contentContainerElement}>
+	<Markdown {id} {content} {model} />
+</div>
+
+{#if floatingButtons}
+	<div
+		bind:this={buttonsContainerElement}
+		class="absolute rounded-lg mt-1 p-1 bg-white dark:bg-gray-850 text-xs text-medium shadow-lg"
+		style="display: none"
+	>
+		<button
+			class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-0.5"
+			on:click={() => {
+				const selection = window.getSelection();
+				dispatch('explain', selection.toString());
+
+				// Clear selection
+				selection.removeAllRanges();
+				buttonsContainerElement.style.display = 'none';
+			}}
+		>
+			<LightBlub className="size-3" />
+
+			<div>Explain</div>
+		</button>
+	</div>
+{/if}

+ 6 - 0
src/lib/components/chat/Messages/Message.svelte

@@ -76,6 +76,9 @@
 				{rateMessage}
 				{continueResponse}
 				{regenerateResponse}
+				on:submit={async (e) => {
+					dispatch('submit', e.detail);
+				}}
 				on:action={async (e) => {
 					dispatch('action', e.detail);
 				}}
@@ -106,6 +109,9 @@
 				{continueResponse}
 				{regenerateResponse}
 				{mergeResponses}
+				on:submit={async (e) => {
+					dispatch('submit', e.detail);
+				}}
 				on:action={async (e) => {
 					dispatch('action', e.detail);
 				}}

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

@@ -215,6 +215,9 @@
 										groupedMessageIdsIdx[modelIdx] =
 											groupedMessageIds[modelIdx].messageIds.length - 1;
 									}}
+									on:submit={async (e) => {
+										dispatch('submit', e.detail);
+									}}
 									on:action={async (e) => {
 										dispatch('action', e.detail);
 									}}

+ 13 - 2
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -38,6 +38,7 @@
 
 	import type { Writable } from 'svelte/store';
 	import type { i18n as i18nType } from 'i18next';
+	import ContentRenderer from './ContentRenderer.svelte';
 
 	interface MessageType {
 		id: string;
@@ -468,13 +469,23 @@
 								</div>
 							</div>
 						{:else}
-							<div class="w-full flex flex-col">
+							<div class="w-full flex flex-col relative" id="response-content-container">
 								{#if message.content === '' && !message.error}
 									<Skeleton />
 								{:else if message.content && message.error !== true}
 									<!-- always show message contents even if there's an error -->
 									<!-- unless message.error === true which is legacy error handling, where the error message is stored in message.content -->
-									<Markdown id={message.id} content={message.content} {model} />
+									<ContentRenderer
+										id={message.id}
+										content={message.content}
+										{model}
+										on:explain={(e) => {
+											dispatch(
+												'submit',
+												`Can you explain this section to me in more detail?\n\n${e.detail}`
+											);
+										}}
+									/>
 								{/if}
 
 								{#if message.error}

+ 19 - 0
src/lib/components/icons/LightBlub.svelte

@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'w-4 h-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"
+	/>
+</svg>