PromptCommands.svelte 4.14 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');

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

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

	$: 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) => {
31
32
33
34
		let text = command.content;

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

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
			console.log(clipboardText);

			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);
						console.log(`Image URL (${type}): ${imageUrl}`);
					}
				}
			}

			if (imageUrl) {
				files = [
					...files,
					{
						type: 'image',
						url: imageUrl
					}
				];
			}

65
66
67
68
			text = command.content.replaceAll('{{CLIPBOARD}}', clipboardText);
		}

		prompt = text;
Timothy J. Baek's avatar
Timothy J. Baek committed
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

		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
91
	<div class="pl-1 pr-12 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
92
93
		<div class="flex w-full dark:border dark:border-gray-850 rounded-lg">
			<div class="  bg-gray-50 dark:bg-gray-850 w-10 rounded-l-lg text-center">
Timothy J. Baek's avatar
Timothy J. Baek committed
94
				<div class=" text-lg font-semibold mt-2">/</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
95
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
96

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
97
98
99
100
			<div
				class="max-h-60 flex flex-col w-full rounded-r-lg bg-white dark:bg-gray-900 dark:text-gray-100"
			>
				<div class="m-1 overflow-y-auto p-1 rounded-r-lg space-y-0.5 scrollbar-hidden">
Timothy J. Baek's avatar
Timothy J. Baek committed
101
102
					{#each filteredPromptCommands as command, commandIdx}
						<button
Timothy J. Baek's avatar
Timothy J. Baek committed
103
							class=" px-3 py-1.5 rounded-xl w-full text-left {commandIdx === selectedCommandIdx
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
104
								? '  bg-gray-50 dark:bg-gray-850 selected-command-option-button'
Timothy J. Baek's avatar
Timothy J. Baek committed
105
106
107
108
109
110
111
112
113
114
								: ''}"
							type="button"
							on:click={() => {
								confirmCommand(command);
							}}
							on:mousemove={() => {
								selectedCommandIdx = commandIdx;
							}}
							on:focus={() => {}}
						>
115
							<div class=" font-medium text-black dark:text-gray-100">
Timothy J. Baek's avatar
Timothy J. Baek committed
116
117
118
								{command.command}
							</div>

119
							<div class=" text-xs text-gray-600 dark:text-gray-100">
Timothy J. Baek's avatar
Timothy J. Baek committed
120
121
122
123
124
125
								{command.title}
							</div>
						</button>
					{/each}
				</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
126
				<div
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
127
					class=" px-2 pb-1 text-xs text-gray-600 dark:text-gray-100 bg-white dark:bg-gray-900 rounded-br-xl flex items-center space-x-1"
Timothy J. Baek's avatar
Timothy J. Baek committed
128
				>
Timothy J. Baek's avatar
Timothy J. Baek committed
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
					<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">
147
148
149
						{$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
150
151
					</div>
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
152
153
154
155
			</div>
		</div>
	</div>
{/if}