diff --git a/src/lib/components/chat/ContentRenderer/FloatingButtons.svelte b/src/lib/components/chat/ContentRenderer/FloatingButtons.svelte index 417dc0badf..fcb54c6eef 100644 --- a/src/lib/components/chat/ContentRenderer/FloatingButtons.svelte +++ b/src/lib/components/chat/ContentRenderer/FloatingButtons.svelte @@ -17,14 +17,15 @@ export let id = ''; export let model = null; export let messages = []; - export let onAdd = () => {}; + export let onAdd = (e) => {}; let floatingInput = false; + let selectedAction = null; let selectedText = ''; let floatingInputValue = ''; - let prompt = ''; + let content = ''; let responseContent = null; let responseDone = false; let controller = null; @@ -42,108 +43,51 @@ } }; - const askHandler = async () => { + const ACTIONS = [ + { + id: 'ask', + label: $i18n.t('Ask'), + icon: ChatBubble, + input: true, + prompt: `{{SELECTED_CONTENT}}\n\n\n{{INPUT_CONTENT}}` + }, + { + id: 'explain', + label: $i18n.t('Explain'), + icon: LightBulb, + prompt: `{{SELECTED_CONTENT}}\n\n\nExplain` + } + ]; + + const actionHandler = async (actionId) => { if (!model) { toast.error('Model not selected'); return; } - prompt = [ - // Blockquote each line of the selected text - ...selectedText.split('\n').map((line) => `> ${line}`), - '', - // Then your question - floatingInputValue - ].join('\n'); - floatingInputValue = ''; - responseContent = ''; - let res; - [res, controller] = await chatCompletion(localStorage.token, { - model: model, - messages: [ - ...messages, - { - role: 'user', - content: prompt - } - ].map((message) => ({ - role: message.role, - content: message.content - })), - stream: true // Enable streaming - }); - - if (res && res.ok) { - const reader = res.body.getReader(); - const decoder = new TextDecoder(); - - const processStream = async () => { - while (true) { - // Read data chunks from the response stream - const { done, value } = await reader.read(); - if (done) { - break; - } - - // Decode the received chunk - const chunk = decoder.decode(value, { stream: true }); - - // Process lines within the chunk - const lines = chunk.split('\n').filter((line) => line.trim() !== ''); - - for (const line of lines) { - if (line.startsWith('data: ')) { - if (line.startsWith('data: [DONE]')) { - responseDone = true; - - await tick(); - autoScroll(); - continue; - } else { - // Parse the JSON chunk - try { - const data = JSON.parse(line.slice(6)); - - // Append the `content` field from the "choices" object - if (data.choices && data.choices[0]?.delta?.content) { - responseContent += data.choices[0].delta.content; - - autoScroll(); - } - } catch (e) { - console.error(e); - } - } - } - } - } - }; - - // Process the stream in the background - try { - await processStream(); - } catch (e) { - if (e.name !== 'AbortError') { - console.error(e); - } - } - } else { - toast.error('An error occurred while fetching the explanation'); - } - }; - - const explainHandler = async () => { - if (!model) { - toast.error('Model not selected'); - return; - } - const quotedText = selectedText + let selectedContent = selectedText .split('\n') .map((line) => `> ${line}`) .join('\n'); - prompt = `${quotedText}\n\nExplain`; + let selectedAction = ACTIONS.find((action) => action.id === actionId); + if (!selectedAction) { + toast.error('Action not found'); + return; + } + + let prompt = selectedAction?.prompt ?? ''; + if (selectedAction.input) { + prompt = prompt.replace('{{INPUT_CONTENT}}', floatingInputValue); + floatingInputValue = ''; + } + + prompt = prompt.replace('{{CONTENT}}', selectedText); + prompt = prompt.replace('{{SELECTED_CONTENT}}', selectedContent); + + content = prompt; responseContent = ''; + let res; [res, controller] = await chatCompletion(localStorage.token, { model: model, @@ -151,7 +95,7 @@ ...messages, { role: 'user', - content: prompt + content: content } ].map((message) => ({ role: message.role, @@ -223,7 +167,7 @@ const messages = [ { role: 'user', - content: prompt + content: content }, { role: 'assistant', @@ -239,6 +183,12 @@ }; export const closeHandler = () => { + if (controller) { + controller.abort(); + } + + selectedAction = null; + selectedText = ''; responseContent = null; responseDone = false; floatingInput = false; @@ -262,36 +212,33 @@
- - + }} + > + +
{action.label}
+ + {/each}
{:else}
{ if (e.key === 'Enter') { - askHandler(); + actionHandler(selectedAction?.id); } }} /> @@ -316,7 +263,7 @@ ? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 ' : 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 m-0.5 self-center" on:click={() => { - askHandler(); + actionHandler(selectedAction?.id); }} >
- +
@@ -349,7 +296,7 @@ class="bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-xl px-3.5 py-3 w-full" >
- {#if responseContent.trim() === ''} + {#if !responseContent || responseContent?.trim() === ''} {:else} diff --git a/src/lib/components/chat/Messages/ContentRenderer.svelte b/src/lib/components/chat/Messages/ContentRenderer.svelte index 54cd2a5aab..630e4bd7a0 100644 --- a/src/lib/components/chat/Messages/ContentRenderer.svelte +++ b/src/lib/components/chat/Messages/ContentRenderer.svelte @@ -34,7 +34,6 @@ export let onAddMessages = (e) => {}; let contentContainerElement; - let floatingButtonsElement; const updateButtonPosition = (event) => {