+layout.svelte 5.53 KB
Newer Older
1
<script lang="ts">
2
	import { v4 as uuidv4 } from 'uuid';
3
	import { openDB, deleteDB } from 'idb';
Timothy J. Baek's avatar
Timothy J. Baek committed
4
	import { onMount, tick } from 'svelte';
5
6
	import { goto } from '$app/navigation';

7
8
9
10
11
12
13
14
15
16
17
	import {
		config,
		user,
		showSettings,
		settings,
		models,
		db,
		chats,
		chatId,
		modelfiles
	} from '$lib/stores';
18
19
20
21

	import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
	import Sidebar from '$lib/components/layout/Sidebar.svelte';
	import toast from 'svelte-french-toast';
22
	import { OLLAMA_API_BASE_URL, WEBUI_API_BASE_URL } from '$lib/constants';
23

Timothy J. Baek's avatar
Timothy J. Baek committed
24
	let loaded = false;
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

	const getModels = async () => {
		let models = [];
		const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/tags`, {
			method: 'GET',
			headers: {
				Accept: 'application/json',
				'Content-Type': 'application/json',
				...($settings.authHeader && { Authorization: $settings.authHeader }),
				...($user && { Authorization: `Bearer ${localStorage.token}` })
			}
		})
			.then(async (res) => {
				if (!res.ok) throw await res.json();
				return res.json();
			})
			.catch((error) => {
				console.log(error);
				if ('detail' in error) {
					toast.error(error.detail);
				} else {
					toast.error('Server connection failed');
				}
				return null;
			});
		console.log(res);
		models.push(...(res?.models ?? []));

		// If OpenAI API Key exists
		if ($settings.OPENAI_API_KEY) {
			// Validate OPENAI_API_KEY
			const openaiModelRes = await fetch(`https://api.openai.com/v1/models`, {
				method: 'GET',
				headers: {
					'Content-Type': 'application/json',
					Authorization: `Bearer ${$settings.OPENAI_API_KEY}`
				}
			})
				.then(async (res) => {
					if (!res.ok) throw await res.json();
					return res.json();
				})
				.catch((error) => {
					console.log(error);
					toast.error(`OpenAI: ${error?.error?.message ?? 'Network Problem'}`);
					return null;
				});

			const openAIModels = openaiModelRes?.data ?? null;

			models.push(
				...(openAIModels
					? [
							{ name: 'hr' },
							...openAIModels
								.map((model) => ({ name: model.id, label: 'OpenAI' }))
								.filter((model) => model.name.includes('gpt'))
					  ]
					: [])
			);
		}

		return models;
	};

	const getDB = async () => {
91
		const DB = await openDB('Chats', 1, {
92
93
94
95
96
97
98
99
			upgrade(db) {
				const store = db.createObjectStore('chats', {
					keyPath: 'id',
					autoIncrement: true
				});
				store.createIndex('timestamp', 'timestamp');
			}
		});
100
101

		return {
102
			db: DB,
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
			getChatById: async function (id) {
				return await this.db.get('chats', id);
			},
			getChats: async function () {
				let chats = await this.db.getAllFromIndex('chats', 'timestamp');
				chats = chats.map((item, idx) => ({
					title: chats[chats.length - 1 - idx].title,
					id: chats[chats.length - 1 - idx].id
				}));
				return chats;
			},
			exportChats: async function () {
				let chats = await this.db.getAllFromIndex('chats', 'timestamp');
				chats = chats.map((item, idx) => chats[chats.length - 1 - idx]);
				return chats;
			},
			addChats: async function (_chats) {
				for (const chat of _chats) {
					console.log(chat);
					await this.addChat(chat);
				}
Timothy J. Baek's avatar
Timothy J. Baek committed
124
				await chats.set(await this.getChats());
125
126
127
128
129
130
131
132
			},
			addChat: async function (chat) {
				await this.db.put('chats', {
					...chat
				});
			},
			createNewChat: async function (chat) {
				await this.addChat({ ...chat, timestamp: Date.now() });
Timothy J. Baek's avatar
Timothy J. Baek committed
133
				await chats.set(await this.getChats());
134
135
136
137
138
139
140
141
142
143
			},
			updateChatById: async function (id, updated) {
				const chat = await this.getChatById(id);

				await this.db.put('chats', {
					...chat,
					...updated,
					timestamp: Date.now()
				});

Timothy J. Baek's avatar
Timothy J. Baek committed
144
				await chats.set(await this.getChats());
145
146
			},
			deleteChatById: async function (id) {
147
148
149
150
				if ($chatId === id) {
					goto('/');
					await chatId.set(uuidv4());
				}
151
				await this.db.delete('chats', id);
Timothy J. Baek's avatar
Timothy J. Baek committed
152
				await chats.set(await this.getChats());
153
154
155
156
157
			},
			deleteAllChat: async function () {
				const tx = this.db.transaction('chats', 'readwrite');
				await Promise.all([tx.store.clear(), tx.done]);

Timothy J. Baek's avatar
Timothy J. Baek committed
158
				await chats.set(await this.getChats());
159
160
			}
		};
161
162
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
163
164
165
166
167
	onMount(async () => {
		if ($config && $config.auth && $user === undefined) {
			await goto('/auth');
		}

Timothy J. Baek's avatar
Timothy J. Baek committed
168
169
		await settings.set(JSON.parse(localStorage.getItem('settings') ?? JSON.stringify($settings)));

170
171
172
173
174
		let _models = await getModels();
		await models.set(_models);
		let _db = await getDB();
		await db.set(_db);

175
176
177
178
179
180
181
182
		await modelfiles.set(
			JSON.parse(localStorage.getItem('modelfiles') ?? JSON.stringify($modelfiles))
		);

		modelfiles.subscribe(async () => {
			await models.set(await getModels());
		});

Timothy J. Baek's avatar
Timothy J. Baek committed
183
184
185
		await tick();
		loaded = true;
	});
186
187
</script>

Timothy J. Baek's avatar
Timothy J. Baek committed
188
{#if loaded}
189
190
191
192
193
194
195
196
197
198
199
	<div class="app">
		<div
			class=" text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-800 min-h-screen overflow-auto flex flex-row"
		>
			<Sidebar />

			<SettingsModal bind:show={$showSettings} />

			<slot />
		</div>
	</div>
200
{/if}
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244

<style>
	.loading {
		display: inline-block;
		clip-path: inset(0 1ch 0 0);
		animation: l 1s steps(3) infinite;
		letter-spacing: -0.5px;
	}

	@keyframes l {
		to {
			clip-path: inset(0 -1ch 0 0);
		}
	}

	pre[class*='language-'] {
		position: relative;
		overflow: auto;

		/* make space  */
		margin: 5px 0;
		padding: 1.75rem 0 1.75rem 1rem;
		border-radius: 10px;
	}

	pre[class*='language-'] button {
		position: absolute;
		top: 5px;
		right: 5px;

		font-size: 0.9rem;
		padding: 0.15rem;
		background-color: #828282;

		border: ridge 1px #7b7b7c;
		border-radius: 5px;
		text-shadow: #c4c4c4 0 0 2px;
	}

	pre[class*='language-'] button:hover {
		cursor: pointer;
		background-color: #bcbabb;
	}
</style>