PromptCommands.svelte 3.39 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
<script lang="ts">
Timothy J. Baek's avatar
Timothy J. Baek committed
2
	import { prompts } from '$lib/stores';
Timothy J. Baek's avatar
Timothy J. Baek committed
3
	import { findWordIndices } from '$lib/utils';
4
	import { tick, getContext } from 'svelte';
Jannik Streidl's avatar
Jannik Streidl committed
5
	import { toast } from 'svelte-sonner';
Timothy J. Baek's avatar
Timothy J. Baek committed
6

7
8
	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
9
10
11
12
	export let prompt = '';
	let selectedCommandIdx = 0;
	let filteredPromptCommands = [];

Timothy J. Baek's avatar
Timothy J. Baek committed
13
	$: filteredPromptCommands = $prompts
14
15
		.filter((p) => p.command.includes(prompt))
		.sort((a, b) => a.title.localeCompare(b.title));
Timothy J. Baek's avatar
Timothy J. Baek committed
16
17
18
19
20
21
22
23
24
25
26
27
28
29

	$: if (prompt) {
		selectedCommandIdx = 0;
	}

	export const selectUp = () => {
		selectedCommandIdx = Math.max(0, selectedCommandIdx - 1);
	};

	export const selectDown = () => {
		selectedCommandIdx = Math.min(selectedCommandIdx + 1, filteredPromptCommands.length - 1);
	};

	const confirmCommand = async (command) => {
30
31
32
33
		let text = command.content;

		if (command.content.includes('{{CLIPBOARD}}')) {
			const clipboardText = await navigator.clipboard.readText().catch((err) => {
34
				toast.error($i18n.t('Failed to read clipboard contents'));
35
36
37
38
39
40
41
				return '{{CLIPBOARD}}';
			});

			text = command.content.replaceAll('{{CLIPBOARD}}', clipboardText);
		}

		prompt = text;
Timothy J. Baek's avatar
Timothy J. Baek committed
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

		const chatInputElement = document.getElementById('chat-textarea');

		await tick();

		chatInputElement.style.height = '';
		chatInputElement.style.height = Math.min(chatInputElement.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);
		}
	};
</script>

{#if filteredPromptCommands.length > 0}
Timothy J. Baek's avatar
Timothy J. Baek committed
64
	<div class="md:px-2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
Timothy J. Baek's avatar
Timothy J. Baek committed
65
66
		<div class="flex w-full px-2">
			<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-xl text-center">
Timothy J. Baek's avatar
Timothy J. Baek committed
67
				<div class=" text-lg font-semibold mt-2">/</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
68
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
69

Timothy J. Baek's avatar
Timothy J. Baek committed
70
71
			<div class="max-h-60 flex flex-col w-full rounded-r-xl bg-white">
				<div class="m-1 overflow-y-auto p-1 rounded-r-xl space-y-0.5">
Timothy J. Baek's avatar
Timothy J. Baek committed
72
73
					{#each filteredPromptCommands as command, commandIdx}
						<button
Timothy J. Baek's avatar
Timothy J. Baek committed
74
							class=" px-3 py-1.5 rounded-xl w-full text-left {commandIdx === selectedCommandIdx
Timothy J. Baek's avatar
Timothy J. Baek committed
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
								? ' bg-gray-100 selected-command-option-button'
								: ''}"
							type="button"
							on:click={() => {
								confirmCommand(command);
							}}
							on:mousemove={() => {
								selectedCommandIdx = commandIdx;
							}}
							on:focus={() => {}}
						>
							<div class=" font-medium text-black">
								{command.command}
							</div>

							<div class=" text-xs text-gray-600">
								{command.title}
							</div>
						</button>
					{/each}
				</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
97
				<div
Timothy J. Baek's avatar
Timothy J. Baek committed
98
					class=" px-2 pb-1 text-xs text-gray-600 bg-white rounded-br-xl flex items-center space-x-1"
Timothy J. Baek's avatar
Timothy J. Baek committed
99
				>
Timothy J. Baek's avatar
Timothy J. Baek committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
					<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">
118
119
120
						{$i18n.t(
							'Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.'
						)}
Timothy J. Baek's avatar
Timothy J. Baek committed
121
122
					</div>
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
123
124
125
126
			</div>
		</div>
	</div>
{/if}