Navbar.svelte 6.66 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
<script lang="ts">
2
3
4
5
	import toast from 'svelte-french-toast';
	import fileSaver from 'file-saver';
	const { saveAs } = fileSaver;

Timothy J. Baek's avatar
Timothy J. Baek committed
6
	import { getChatById } from '$lib/apis/chats';
Timothy J. Baek's avatar
Timothy J. Baek committed
7
	import { chatId, modelfiles } from '$lib/stores';
Timothy J. Baek's avatar
Timothy J. Baek committed
8
	import ShareChatModal from '../chat/ShareChatModal.svelte';
9
	import { stringify } from 'postcss';
10

11
	export let initNewChat: Function;
12
	export let title: string = 'Ollama Web UI';
Timothy J. Baek's avatar
Timothy J. Baek committed
13
	export let shareEnabled: boolean = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
14

15
16
17
	export let tags = [];
	export let addTag: Function;
	export let deleteTag: Function;
Timothy J. Baek's avatar
Timothy J. Baek committed
18

19
	let showShareChatModal = false;
20
21
22
23

	let tagName = '';
	let showTagInput = false;

Timothy J. Baek's avatar
Timothy J. Baek committed
24
	const shareChat = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
25
		const chat = (await getChatById(localStorage.token, $chatId)).chat;
Timothy J. Baek's avatar
Timothy J. Baek committed
26
27
		console.log('share', chat);

Timothy J. Baek's avatar
Timothy J. Baek committed
28
		toast.success('Redirecting you to OllamaHub');
29
30
		const url = 'https://ollamahub.com';
		// const url = 'http://localhost:5173';
Timothy J. Baek's avatar
Timothy J. Baek committed
31
32
33
34
35
36
37

		const tab = await window.open(`${url}/chats/upload`, '_blank');
		window.addEventListener(
			'message',
			(event) => {
				if (event.origin !== url) return;
				if (event.data === 'loaded') {
38
39
40
41
42
43
44
					tab.postMessage(
						JSON.stringify({
							chat: chat,
							modelfiles: $modelfiles.filter((modelfile) => chat.models.includes(modelfile.tagName))
						}),
						'*'
					);
Timothy J. Baek's avatar
Timothy J. Baek committed
45
46
47
48
49
				}
			},
			false
		);
	};
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

	const downloadChat = async () => {
		const chat = (await getChatById(localStorage.token, $chatId)).chat;
		console.log('download', chat);

		const chatText = chat.messages.reduce((a, message, i, arr) => {
			return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
		}, '');

		let blob = new Blob([chatText], {
			type: 'text/plain'
		});

		saveAs(blob, `chat-${chat.title}.txt`);
	};
65

66
67
68
69
70
71
72
73
74
	const addTagHandler = () => {
		// if (!tags.find((e) => e.name === tagName)) {
		// 	tags = [
		// 		...tags,
		// 		{
		// 			name: JSON.parse(JSON.stringify(tagName))
		// 		}
		// 	];
		// }
75

76
		addTag(tagName);
77
78
79
		tagName = '';
		showTagInput = false;
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
80
81
</script>

Timothy J. Baek's avatar
Timothy J. Baek committed
82
<ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} />
Timothy J. Baek's avatar
Timothy J. Baek committed
83
84
85
<nav
	id="nav"
	class=" fixed py-2.5 top-0 flex flex-row justify-center bg-white/95 dark:bg-gray-800/90 dark:text-gray-200 backdrop-blur-xl w-screen z-30"
Timothy J. Baek's avatar
Timothy J. Baek committed
86
>
Timothy J. Baek's avatar
Timothy J. Baek committed
87
	<div class=" flex max-w-3xl w-full mx-auto px-3">
Timothy J. Baek's avatar
Timothy J. Baek committed
88
89
		<div class="flex items-center w-full max-w-full">
			<div class="pr-2 self-start">
Timothy J. Baek's avatar
Timothy J. Baek committed
90
				<button
91
					id="new-chat-button"
Timothy J. Baek's avatar
Timothy J. Baek committed
92
					class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-lg transition"
93
					on:click={initNewChat}
Timothy J. Baek's avatar
Timothy J. Baek committed
94
95
96
97
98
99
100
				>
					<div class=" m-auto self-center">
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 20 20"
							fill="currentColor"
							class="w-5 h-5"
Timothy J. Baek's avatar
Timothy J. Baek committed
101
						>
Timothy J. Baek's avatar
Timothy J. Baek committed
102
103
104
105
106
107
108
							<path
								d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z"
							/>
							<path
								d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0010 3H4.75A2.75 2.75 0 002 5.75v9.5A2.75 2.75 0 004.75 18h9.5A2.75 2.75 0 0017 15.25V10a.75.75 0 00-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5z"
							/>
						</svg>
Timothy J. Baek's avatar
Timothy J. Baek committed
109
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
110
111
				</button>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
112
113
114
115
			<div class=" flex-1 self-center font-medium line-clamp-1">
				<div>
					{title != '' ? title : 'Ollama Web UI'}
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
116
117
			</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
118
			<div class="pl-2 self-center flex items-center space-x-2">
119
120
121
122
123
				{#if shareEnabled}
					<div class="flex flex-row space-x-0.5 line-clamp-1">
						{#each tags as tag}
							<div
								class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition border dark:border-gray-600 dark:text-white"
124
							>
125
126
127
								<div class=" text-[0.65rem] font-medium self-center line-clamp-1">
									{tag.name}
								</div>
128
								<button
129
									class=" m-auto self-center cursor-pointer"
130
									on:click={() => {
131
										deleteTag(tag.name);
132
133
134
135
136
137
138
139
140
									}}
								>
									<svg
										xmlns="http://www.w3.org/2000/svg"
										viewBox="0 0 16 16"
										fill="currentColor"
										class="w-3 h-3"
									>
										<path
141
											d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z"
142
143
144
145
										/>
									</svg>
								</button>
							</div>
146
147
148
149
150
151
152
153
154
155
						{/each}

						<div class="flex space-x-1 pl-1.5">
							{#if showTagInput}
								<div class="flex items-center">
									<input
										bind:value={tagName}
										class=" cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[4rem]"
										placeholder="Add a tag"
									/>
156

157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
									<button
										on:click={() => {
											addTagHandler();
										}}
									>
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 16 16"
											fill="currentColor"
											class="w-3 h-3"
										>
											<path
												fill-rule="evenodd"
												d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z"
												clip-rule="evenodd"
											/>
										</svg>
									</button>
								</div>

								<!-- TODO: Tag Suggestions -->
							{/if}
179

180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
							<button
								class=" cursor-pointer self-center p-0.5 space-x-1 flex h-fit items-center dark:hover:bg-gray-700 rounded-full transition border dark:border-gray-600 border-dashed"
								on:click={() => {
									showTagInput = !showTagInput;
								}}
							>
								<div class=" m-auto self-center">
									<svg
										xmlns="http://www.w3.org/2000/svg"
										viewBox="0 0 16 16"
										fill="currentColor"
										class="w-3 h-3 {showTagInput ? 'rotate-45' : ''} transition-all transform"
									>
										<path
											d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
										/>
									</svg>
								</div>
							</button>
						</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
200
					</div>
201

Timothy J. Baek's avatar
Timothy J. Baek committed
202
					<button
Timothy J. Baek's avatar
Timothy J. Baek committed
203
						class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-lg transition border dark:border-gray-600"
Timothy J. Baek's avatar
Timothy J. Baek committed
204
						on:click={async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
205
206
207
							showShareChatModal = !showShareChatModal;

							// console.log(showShareChatModal);
Timothy J. Baek's avatar
Timothy J. Baek committed
208
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
209
					>
Timothy J. Baek's avatar
Timothy J. Baek committed
210
211
212
						<div class=" m-auto self-center">
							<svg
								xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
213
								viewBox="0 0 24 24"
Timothy J. Baek's avatar
Timothy J. Baek committed
214
215
216
217
								fill="currentColor"
								class="w-4 h-4"
							>
								<path
Timothy J. Baek's avatar
Timothy J. Baek committed
218
219
220
									fill-rule="evenodd"
									d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z"
									clip-rule="evenodd"
Timothy J. Baek's avatar
Timothy J. Baek committed
221
222
223
224
								/>
							</svg>
						</div>
					</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
225
226
				{/if}
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
227
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
228
	</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
229
</nav>