@@ -1,9 +1,4 @@
|
||||
<script>
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import { knowledge, prompts } from '$lib/stores';
|
||||
|
||||
import { removeLastWordFromString } from '$lib/utils';
|
||||
@@ -15,8 +10,15 @@
|
||||
import Models from './Commands/Models.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
|
||||
export let prompt = '';
|
||||
export let show = false;
|
||||
|
||||
export let files = [];
|
||||
export let command = '';
|
||||
|
||||
export let onSelect = (e) => {};
|
||||
export let onUpload = (e) => {};
|
||||
|
||||
export let insertTextHandler = (text) => {};
|
||||
|
||||
let loading = false;
|
||||
let commandElement = null;
|
||||
@@ -29,12 +31,6 @@
|
||||
commandElement?.selectDown();
|
||||
};
|
||||
|
||||
let command = '';
|
||||
$: command = prompt?.split('\n').pop()?.split(' ')?.pop() ?? '';
|
||||
|
||||
let show = false;
|
||||
$: show = ['/', '#', '@'].includes(command?.charAt(0)) || '\\#' === command.slice(0, 2);
|
||||
|
||||
$: if (show) {
|
||||
init();
|
||||
}
|
||||
@@ -56,54 +52,63 @@
|
||||
{#if show}
|
||||
{#if !loading}
|
||||
{#if command?.charAt(0) === '/'}
|
||||
<Prompts bind:this={commandElement} bind:prompt bind:files {command} />
|
||||
<Prompts
|
||||
bind:this={commandElement}
|
||||
{command}
|
||||
onSelect={(e) => {
|
||||
const { type, data } = e;
|
||||
|
||||
if (type === 'prompt') {
|
||||
insertTextHandler(data.content);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if (command?.charAt(0) === '#' && command.startsWith('#') && !command.includes('# ')) || ('\\#' === command.slice(0, 2) && command.startsWith('#') && !command.includes('# '))}
|
||||
<Knowledge
|
||||
bind:this={commandElement}
|
||||
bind:prompt
|
||||
command={command.includes('\\#') ? command.slice(2) : command}
|
||||
on:youtube={(e) => {
|
||||
console.log(e);
|
||||
dispatch('upload', {
|
||||
type: 'youtube',
|
||||
data: e.detail
|
||||
});
|
||||
}}
|
||||
on:url={(e) => {
|
||||
console.log(e);
|
||||
dispatch('upload', {
|
||||
type: 'web',
|
||||
data: e.detail
|
||||
});
|
||||
}}
|
||||
on:select={(e) => {
|
||||
console.log(e);
|
||||
if (files.find((f) => f.id === e.detail.id)) {
|
||||
return;
|
||||
onSelect={(e) => {
|
||||
const { type, data } = e;
|
||||
|
||||
if (type === 'knowledge') {
|
||||
insertTextHandler('');
|
||||
|
||||
onUpload({
|
||||
type: 'file',
|
||||
data: data
|
||||
});
|
||||
} else if (type === 'youtube') {
|
||||
insertTextHandler('');
|
||||
|
||||
onUpload({
|
||||
type: 'youtube',
|
||||
data: data
|
||||
});
|
||||
} else if (type === 'web') {
|
||||
insertTextHandler('');
|
||||
|
||||
onUpload({
|
||||
type: 'web',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
...e.detail,
|
||||
status: 'processed'
|
||||
}
|
||||
];
|
||||
|
||||
dispatch('select');
|
||||
}}
|
||||
/>
|
||||
{:else if command?.charAt(0) === '@'}
|
||||
<Models
|
||||
bind:this={commandElement}
|
||||
{command}
|
||||
on:select={(e) => {
|
||||
prompt = removeLastWordFromString(prompt, command);
|
||||
onSelect={(e) => {
|
||||
const { type, data } = e;
|
||||
|
||||
dispatch('select', {
|
||||
type: 'model',
|
||||
data: e.detail
|
||||
});
|
||||
if (type === 'model') {
|
||||
insertTextHandler('');
|
||||
|
||||
onSelect({
|
||||
type: 'model',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -6,16 +6,15 @@
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
import { createEventDispatcher, tick, getContext, onMount, onDestroy } from 'svelte';
|
||||
import { tick, getContext, onMount, onDestroy } from 'svelte';
|
||||
import { removeLastWordFromString, isValidHttpUrl } from '$lib/utils';
|
||||
import { knowledge } from '$lib/stores';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let prompt = '';
|
||||
export let command = '';
|
||||
export let onSelect = (e) => {};
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let selectedIdx = 0;
|
||||
|
||||
let items = [];
|
||||
@@ -60,37 +59,12 @@
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
const confirmSelect = async (item) => {
|
||||
dispatch('select', item);
|
||||
|
||||
prompt = removeLastWordFromString(prompt, command);
|
||||
const chatInputElement = document.getElementById('chat-input');
|
||||
|
||||
await tick();
|
||||
chatInputElement?.focus();
|
||||
await tick();
|
||||
};
|
||||
|
||||
const confirmSelectWeb = async (url) => {
|
||||
dispatch('url', url);
|
||||
|
||||
prompt = removeLastWordFromString(prompt, command);
|
||||
const chatInputElement = document.getElementById('chat-input');
|
||||
|
||||
await tick();
|
||||
chatInputElement?.focus();
|
||||
await tick();
|
||||
};
|
||||
|
||||
const confirmSelectYoutube = async (url) => {
|
||||
dispatch('youtube', url);
|
||||
|
||||
prompt = removeLastWordFromString(prompt, command);
|
||||
const chatInputElement = document.getElementById('chat-input');
|
||||
|
||||
await tick();
|
||||
chatInputElement?.focus();
|
||||
await tick();
|
||||
const confirmSelect = async (type, data) => {
|
||||
onSelect({
|
||||
type: type,
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
const decodeString = (str: string) => {
|
||||
@@ -189,7 +163,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if filteredItems.length > 0 || prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
|
||||
{#if filteredItems.length > 0 || command?.substring(1).startsWith('http')}
|
||||
<div
|
||||
id="commands-container"
|
||||
class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
|
||||
@@ -210,7 +184,7 @@
|
||||
type="button"
|
||||
on:click={() => {
|
||||
console.log(item);
|
||||
confirmSelect(item);
|
||||
confirmSelect('knowledge', item);
|
||||
}}
|
||||
on:mousemove={() => {
|
||||
selectedIdx = idx;
|
||||
@@ -298,18 +272,15 @@
|
||||
</div> -->
|
||||
{/each}
|
||||
|
||||
{#if prompt
|
||||
.split(' ')
|
||||
.some((s) => s.substring(1).startsWith('https://www.youtube.com') || s
|
||||
.substring(1)
|
||||
.startsWith('https://youtu.be'))}
|
||||
{#if command.substring(1).startsWith('https://www.youtube.com') || command
|
||||
.substring(1)
|
||||
.startsWith('https://youtu.be')}
|
||||
<button
|
||||
class="px-3 py-1.5 rounded-xl w-full text-left bg-gray-50 dark:bg-gray-850 dark:text-gray-100 selected-command-option-button"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
const url = prompt.split(' ')?.at(0)?.substring(1);
|
||||
if (isValidHttpUrl(url)) {
|
||||
confirmSelectYoutube(url);
|
||||
if (isValidHttpUrl(command.substring(1))) {
|
||||
confirmSelect('youtube', command.substring(1));
|
||||
} else {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
@@ -320,19 +291,18 @@
|
||||
}}
|
||||
>
|
||||
<div class=" font-medium text-black dark:text-gray-100 line-clamp-1">
|
||||
{prompt.split(' ')?.at(0)?.substring(1)}
|
||||
{command.substring(1)}
|
||||
</div>
|
||||
|
||||
<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Youtube')}</div>
|
||||
</button>
|
||||
{:else if prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
|
||||
{:else if command.substring(1).startsWith('http')}
|
||||
<button
|
||||
class="px-3 py-1.5 rounded-xl w-full text-left bg-gray-50 dark:bg-gray-850 dark:text-gray-100 selected-command-option-button"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
const url = prompt.split(' ')?.at(0)?.substring(1);
|
||||
if (isValidHttpUrl(url)) {
|
||||
confirmSelectWeb(url);
|
||||
if (isValidHttpUrl(command.substring(1))) {
|
||||
confirmSelect('web', command.substring(1));
|
||||
} else {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
@@ -343,7 +313,7 @@
|
||||
}}
|
||||
>
|
||||
<div class=" font-medium text-black dark:text-gray-100 line-clamp-1">
|
||||
{prompt.split(' ')?.at(0)?.substring(1)}
|
||||
{command}
|
||||
</div>
|
||||
|
||||
<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Web')}</div>
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let command = '';
|
||||
export let onSelect = (e) => {};
|
||||
|
||||
let selectedIdx = 0;
|
||||
let filteredItems = [];
|
||||
@@ -71,8 +70,7 @@
|
||||
};
|
||||
|
||||
const confirmSelect = async (model) => {
|
||||
command = '';
|
||||
dispatch('select', model);
|
||||
onSelect({ type: 'model', data: model });
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
|
||||
@@ -14,10 +14,8 @@
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let files;
|
||||
|
||||
export let prompt = '';
|
||||
export let command = '';
|
||||
export let onSelect = (e) => {};
|
||||
|
||||
let selectedPromptIdx = 0;
|
||||
let filteredPrompts = [];
|
||||
@@ -58,137 +56,7 @@
|
||||
};
|
||||
|
||||
const confirmPrompt = async (command) => {
|
||||
let text = command.content;
|
||||
|
||||
if (command.content.includes('{{CLIPBOARD}}')) {
|
||||
const clipboardText = await navigator.clipboard.readText().catch((err) => {
|
||||
toast.error($i18n.t('Failed to read clipboard contents'));
|
||||
return '{{CLIPBOARD}}';
|
||||
});
|
||||
|
||||
const clipboardItems = await navigator.clipboard.read();
|
||||
|
||||
let imageUrl = null;
|
||||
for (const item of clipboardItems) {
|
||||
// Check for known image types
|
||||
for (const type of item.types) {
|
||||
if (type.startsWith('image/')) {
|
||||
const blob = await item.getType(type);
|
||||
imageUrl = URL.createObjectURL(blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
type: 'image',
|
||||
url: imageUrl
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
text = text.replaceAll('{{CLIPBOARD}}', clipboardText);
|
||||
}
|
||||
|
||||
if (command.content.includes('{{USER_LOCATION}}')) {
|
||||
let location;
|
||||
try {
|
||||
location = await getUserPosition();
|
||||
} catch (error) {
|
||||
toast.error($i18n.t('Location access not allowed'));
|
||||
location = 'LOCATION_UNKNOWN';
|
||||
}
|
||||
text = text.replaceAll('{{USER_LOCATION}}', String(location));
|
||||
}
|
||||
|
||||
if (command.content.includes('{{USER_NAME}}')) {
|
||||
console.log($user);
|
||||
const name = $user?.name || 'User';
|
||||
text = text.replaceAll('{{USER_NAME}}', name);
|
||||
}
|
||||
|
||||
if (command.content.includes('{{USER_LANGUAGE}}')) {
|
||||
const language = localStorage.getItem('locale') || 'en-US';
|
||||
text = text.replaceAll('{{USER_LANGUAGE}}', language);
|
||||
}
|
||||
|
||||
if (command.content.includes('{{CURRENT_DATE}}')) {
|
||||
const date = getFormattedDate();
|
||||
text = text.replaceAll('{{CURRENT_DATE}}', date);
|
||||
}
|
||||
|
||||
if (command.content.includes('{{CURRENT_TIME}}')) {
|
||||
const time = getFormattedTime();
|
||||
text = text.replaceAll('{{CURRENT_TIME}}', time);
|
||||
}
|
||||
|
||||
if (command.content.includes('{{CURRENT_DATETIME}}')) {
|
||||
const dateTime = getCurrentDateTime();
|
||||
text = text.replaceAll('{{CURRENT_DATETIME}}', dateTime);
|
||||
}
|
||||
|
||||
if (command.content.includes('{{CURRENT_TIMEZONE}}')) {
|
||||
const timezone = getUserTimezone();
|
||||
text = text.replaceAll('{{CURRENT_TIMEZONE}}', timezone);
|
||||
}
|
||||
|
||||
if (command.content.includes('{{CURRENT_WEEKDAY}}')) {
|
||||
const weekday = getWeekday();
|
||||
text = text.replaceAll('{{CURRENT_WEEKDAY}}', weekday);
|
||||
}
|
||||
|
||||
const lines = prompt.split('\n');
|
||||
const lastLine = lines.pop();
|
||||
|
||||
const lastLineWords = lastLine.split(' ');
|
||||
const lastWord = lastLineWords.pop();
|
||||
|
||||
if ($settings?.richTextInput ?? true) {
|
||||
lastLineWords.push(
|
||||
`${text.replace(/</g, '<').replace(/>/g, '>').replaceAll('\n', '<br/>')}`
|
||||
);
|
||||
|
||||
lines.push(lastLineWords.join(' '));
|
||||
prompt = lines.join('<br/>');
|
||||
} else {
|
||||
lastLineWords.push(text);
|
||||
lines.push(lastLineWords.join(' '));
|
||||
prompt = lines.join('\n');
|
||||
}
|
||||
|
||||
const chatInputContainerElement = document.getElementById('chat-input-container');
|
||||
const chatInputElement = document.getElementById('chat-input');
|
||||
|
||||
await tick();
|
||||
if (chatInputContainerElement) {
|
||||
chatInputContainerElement.scrollTop = chatInputContainerElement.scrollHeight;
|
||||
}
|
||||
|
||||
await tick();
|
||||
if (chatInputElement) {
|
||||
chatInputElement.focus();
|
||||
chatInputElement.dispatchEvent(new Event('input'));
|
||||
|
||||
const words = extractCurlyBraceWords(prompt);
|
||||
|
||||
if (words.length > 0) {
|
||||
const word = words.at(0);
|
||||
await tick();
|
||||
|
||||
if (!($settings?.richTextInput ?? true)) {
|
||||
chatInputElement.setSelectionRange(word.startIndex, word.endIndex + 1);
|
||||
chatInputElement.focus();
|
||||
|
||||
// This is a workaround to ensure the cursor is placed correctly
|
||||
// after the text is inserted, especially for multiline inputs.
|
||||
chatInputElement.setSelectionRange(word.startIndex, word.endIndex + 1);
|
||||
}
|
||||
} else {
|
||||
chatInputElement.scrollTop = chatInputElement.scrollHeight;
|
||||
}
|
||||
}
|
||||
onSelect({ type: 'prompt', data: command });
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
@@ -213,14 +81,14 @@
|
||||
id="command-options-container"
|
||||
bind:this={container}
|
||||
>
|
||||
{#each filteredPrompts as prompt, promptIdx}
|
||||
{#each filteredPrompts as promptItem, promptIdx}
|
||||
<button
|
||||
class=" px-3 py-1.5 rounded-xl w-full text-left {promptIdx === selectedPromptIdx
|
||||
? ' bg-gray-50 dark:bg-gray-850 selected-command-option-button'
|
||||
: ''}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
confirmPrompt(prompt);
|
||||
confirmPrompt(promptItem);
|
||||
}}
|
||||
on:mousemove={() => {
|
||||
selectedPromptIdx = promptIdx;
|
||||
@@ -228,11 +96,11 @@
|
||||
on:focus={() => {}}
|
||||
>
|
||||
<div class=" font-medium text-black dark:text-gray-100">
|
||||
{prompt.command}
|
||||
{promptItem.command}
|
||||
</div>
|
||||
|
||||
<div class=" text-xs text-gray-600 dark:text-gray-100">
|
||||
{prompt.title}
|
||||
{promptItem.title}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
Reference in New Issue
Block a user