|
<script lang="ts"> |
|
import { prompts } from '$lib/stores'; |
|
import { |
|
findWordIndices, |
|
getUserPosition, |
|
getFormattedDate, |
|
getFormattedTime, |
|
getCurrentDateTime, |
|
getUserTimezone, |
|
getWeekday |
|
} from '$lib/utils'; |
|
import { tick, getContext } from 'svelte'; |
|
import { toast } from 'svelte-sonner'; |
|
|
|
const i18n = getContext('i18n'); |
|
|
|
export let files; |
|
|
|
export let prompt = ''; |
|
export let command = ''; |
|
|
|
let selectedPromptIdx = 0; |
|
let filteredPrompts = []; |
|
|
|
$: filteredPrompts = $prompts |
|
.filter((p) => p.command.toLowerCase().includes(command.toLowerCase())) |
|
.sort((a, b) => a.title.localeCompare(b.title)); |
|
|
|
$: if (command) { |
|
selectedPromptIdx = 0; |
|
} |
|
|
|
export const selectUp = () => { |
|
selectedPromptIdx = Math.max(0, selectedPromptIdx - 1); |
|
}; |
|
|
|
export const selectDown = () => { |
|
selectedPromptIdx = Math.min(selectedPromptIdx + 1, filteredPrompts.length - 1); |
|
}; |
|
|
|
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) { |
|
|
|
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}}')) { |
|
const location = await getUserPosition(); |
|
text = text.replaceAll('{{USER_LOCATION}}', String(location)); |
|
} |
|
|
|
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); |
|
} |
|
|
|
prompt = text; |
|
|
|
const chatInputContainerElement = document.getElementById('chat-input-container'); |
|
const chatInputElement = document.getElementById('chat-input'); |
|
|
|
await tick(); |
|
|
|
if (chatInputContainerElement) { |
|
chatInputContainerElement.style.height = ''; |
|
chatInputContainerElement.style.height = |
|
Math.min(chatInputContainerElement.scrollHeight, 200) + 'px'; |
|
|
|
chatInputElement?.focus(); |
|
} |
|
}; |
|
</script> |
|
|
|
{#if filteredPrompts.length > 0} |
|
<div |
|
id="commands-container" |
|
class="pl-3 pr-14 mb-3 text-left w-full absolute bottom-0 left-0 right-0 z-10" |
|
> |
|
<div class="flex w-full rounded-xl border border-gray-50 dark:border-gray-850"> |
|
<div |
|
class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100" |
|
> |
|
<div class="m-1 overflow-y-auto p-1 space-y-0.5 scrollbar-hidden"> |
|
{#each filteredPrompts as prompt, 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); |
|
}} |
|
on:mousemove={() => { |
|
selectedPromptIdx = promptIdx; |
|
}} |
|
on:focus={() => {}} |
|
> |
|
<div class=" font-medium text-black dark:text-gray-100"> |
|
{prompt.command} |
|
</div> |
|
|
|
<div class=" text-xs text-gray-600 dark:text-gray-100"> |
|
{prompt.title} |
|
</div> |
|
</button> |
|
{/each} |
|
</div> |
|
|
|
<div |
|
class=" px-2 pt-0.5 pb-1 text-xs text-gray-600 dark:text-gray-100 bg-white dark:bg-gray-900 rounded-b-xl flex items-center space-x-1" |
|
> |
|
<div> |
|
<svg |
|
xmlns="http://www.w3.org/2000/svg" |
|
fill="none" |
|
viewBox="0 0 24 24" |
|
stroke-width="1.5" |
|
stroke="currentColor" |
|
class="w-3 h-3" |
|
> |
|
<path |
|
stroke-linecap="round" |
|
stroke-linejoin="round" |
|
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" |
|
/> |
|
</svg> |
|
</div> |
|
|
|
<div class="line-clamp-1"> |
|
{$i18n.t( |
|
'Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.' |
|
)} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
{/if} |
|
|