"sgl-kernel/csrc/gemm/per_token_quant_fp8.cu" did not exist on "2cadd51d11a7fddf7c15833f6fca617428af7ef2"
+page.svelte 14.1 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
<script lang="ts">
2
	import { v4 as uuidv4 } from 'uuid';
Timothy J. Baek's avatar
Timothy J. Baek committed
3
4
	import toast from 'svelte-french-toast';

5
	import { OLLAMA_API_BASE_URL } from '$lib/constants';
6
	import { onMount, tick } from 'svelte';
7
8
	import { splitStream } from '$lib/utils';
	import { goto } from '$app/navigation';
Timothy J. Baek's avatar
Timothy J. Baek committed
9

10
	import { config, modelfiles, user, settings, db, chats, chatId } from '$lib/stores';
11
12
13
14

	import MessageInput from '$lib/components/chat/MessageInput.svelte';
	import Messages from '$lib/components/chat/Messages.svelte';
	import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
15
	import Navbar from '$lib/components/layout/Navbar.svelte';
16
	import { page } from '$app/stores';
17

18
19
	let stopResponseFlag = false;
	let autoScroll = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
20

Timothy J. Baek's avatar
Timothy J. Baek committed
21
	let selectedModels = [''];
22
23
24
25
26
27
	let selectedModelfile = null;
	$: selectedModelfile =
		selectedModels.length === 1 &&
		$modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0]).length > 0
			? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0]
			: null;
28

Timothy J. Baek's avatar
Timothy J. Baek committed
29
	let title = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
30
	let prompt = '';
31
	let files = [];
32

33
	let messages = [];
Timothy J. Baek's avatar
Timothy J. Baek committed
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
	let history = {
		messages: {},
		currentId: null
	};

	$: if (history.currentId !== null) {
		let _messages = [];

		let currentMessage = history.messages[history.currentId];
		while (currentMessage !== null) {
			_messages.unshift({ ...currentMessage });
			currentMessage =
				currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null;
		}
		messages = _messages;
	}
Timothy J. Baek's avatar
Timothy J. Baek committed
50

51
	onMount(async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
52
53
54
55
56
		await chatId.set(uuidv4());

		chatId.subscribe(async () => {
			await initNewChat();
		});
57
58
59
60
61
62
	});

	//////////////////////////
	// Web functions
	//////////////////////////

63
64
	const initNewChat = async () => {
		console.log($chatId);
Timothy J. Baek's avatar
Timothy J. Baek committed
65

66
		autoScroll = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
67

68
69
70
71
72
		title = '';
		messages = [];
		history = {
			messages: {},
			currentId: null
Timothy J. Baek's avatar
Timothy J. Baek committed
73
		};
74
75
76
		selectedModels = $page.url.searchParams.get('models')
			? $page.url.searchParams.get('models')?.split(',')
			: $settings.models ?? [''];
Timothy J. Baek's avatar
Timothy J. Baek committed
77
78
	};

79
80
81
82
	//////////////////////////
	// Ollama functions
	//////////////////////////

Timothy J. Baek's avatar
Timothy J. Baek committed
83
	const sendPrompt = async (userPrompt, parentId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
84
85
86
87
88
89
90
91
92
		await Promise.all(
			selectedModels.map(async (model) => {
				if (model.includes('gpt-')) {
					await sendPromptOpenAI(model, userPrompt, parentId);
				} else {
					await sendPromptOllama(model, userPrompt, parentId);
				}
			})
		);
93

94
		await chats.set(await $db.getChats());
95
96
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
97
	const sendPromptOllama = async (model, userPrompt, parentId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
98
		console.log('sendPromptOllama');
Timothy J. Baek's avatar
Timothy J. Baek committed
99
100
		let responseMessageId = uuidv4();

101
		let responseMessage = {
Timothy J. Baek's avatar
Timothy J. Baek committed
102
103
104
			parentId: parentId,
			id: responseMessageId,
			childrenIds: [],
105
			role: 'assistant',
Timothy J. Baek's avatar
Timothy J. Baek committed
106
107
			content: '',
			model: model
108
109
		};

Timothy J. Baek's avatar
Timothy J. Baek committed
110
111
112
113
114
115
116
117
118
		history.messages[responseMessageId] = responseMessage;
		history.currentId = responseMessageId;
		if (parentId !== null) {
			history.messages[parentId].childrenIds = [
				...history.messages[parentId].childrenIds,
				responseMessageId
			];
		}

119
120
		window.scrollTo({ top: document.body.scrollHeight });

121
		const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/generate`, {
122
123
			method: 'POST',
			headers: {
Timothy J. Baek's avatar
Timothy J. Baek committed
124
				'Content-Type': 'text/event-stream',
125
				...($settings.authHeader && { Authorization: $settings.authHeader }),
126
				...($user && { Authorization: `Bearer ${localStorage.token}` })
127
128
			},
			body: JSON.stringify({
Timothy J. Baek's avatar
Timothy J. Baek committed
129
				model: model,
130
				prompt: userPrompt,
131
				system: $settings.system ?? undefined,
132
				options: {
133
134
135
136
					seed: $settings.seed ?? undefined,
					temperature: $settings.temperature ?? undefined,
					repeat_penalty: $settings.repeat_penalty ?? undefined,
					top_k: $settings.top_k ?? undefined,
Anthony Cucci's avatar
Anthony Cucci committed
137
					top_p: $settings.top_p ?? undefined,
138
139
					num_ctx: $settings.num_ctx ?? undefined,
					...($settings.options ?? {})
140
				},
141
				format: $settings.requestFormat ?? undefined,
142
				context:
Timothy J. Baek's avatar
Timothy J. Baek committed
143
144
145
					history.messages[parentId] !== null &&
					history.messages[parentId].parentId in history.messages
						? history.messages[history.messages[parentId].parentId]?.context ?? undefined
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
						: undefined
			})
		});

		const reader = res.body
			.pipeThrough(new TextDecoderStream())
			.pipeThrough(splitStream('\n'))
			.getReader();

		while (true) {
			const { value, done } = await reader.read();
			if (done || stopResponseFlag) {
				if (stopResponseFlag) {
					responseMessage.done = true;
					messages = messages;
				}

				break;
			}

			try {
				let lines = value.split('\n');

				for (const line of lines) {
					if (line !== '') {
						console.log(line);
						let data = JSON.parse(line);
						if (data.done == false) {
							if (responseMessage.content == '' && data.response == '\n') {
								continue;
							} else {
								responseMessage.content += data.response;
								messages = messages;
							}
180
181
						} else if ('detail' in data) {
							throw data;
182
183
184
185
186
187
188
189
190
						} else {
							responseMessage.done = true;
							responseMessage.context = data.context;
							messages = messages;
						}
					}
				}
			} catch (error) {
				console.log(error);
191
192
193
194
				if ('detail' in error) {
					toast.error(error.detail);
				}
				break;
195
196
197
198
199
200
			}

			if (autoScroll) {
				window.scrollTo({ top: document.body.scrollHeight });
			}

201
			await $db.updateChatById($chatId, {
202
				title: title === '' ? 'New Chat' : title,
Timothy J. Baek's avatar
Timothy J. Baek committed
203
				models: selectedModels,
204
				system: $settings.system ?? undefined,
205
				options: {
206
207
208
209
					seed: $settings.seed ?? undefined,
					temperature: $settings.temperature ?? undefined,
					repeat_penalty: $settings.repeat_penalty ?? undefined,
					top_k: $settings.top_k ?? undefined,
Anthony Cucci's avatar
Anthony Cucci committed
210
					top_p: $settings.top_p ?? undefined,
211
212
					num_ctx: $settings.num_ctx ?? undefined,
					...($settings.options ?? {})
213
				},
Timothy J. Baek's avatar
Timothy J. Baek committed
214
				messages: messages,
215
				history: history
216
217
218
219
220
221
222
223
			});
		}

		stopResponseFlag = false;
		await tick();
		if (autoScroll) {
			window.scrollTo({ top: document.body.scrollHeight });
		}
224

225
		if (messages.length == 2 && messages.at(1).content !== '') {
226
227
			window.history.replaceState(history.state, '', `/c/${$chatId}`);
			await generateChatTitle($chatId, userPrompt);
228
229
230
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
231
	const sendPromptOpenAI = async (model, userPrompt, parentId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
232
		if ($settings.OPENAI_API_KEY) {
233
			if (models) {
234
235
				let responseMessageId = uuidv4();

236
				let responseMessage = {
237
238
239
					parentId: parentId,
					id: responseMessageId,
					childrenIds: [],
240
					role: 'assistant',
Timothy J. Baek's avatar
Timothy J. Baek committed
241
242
					content: '',
					model: model
243
244
				};

245
246
247
248
249
250
251
252
253
				history.messages[responseMessageId] = responseMessage;
				history.currentId = responseMessageId;
				if (parentId !== null) {
					history.messages[parentId].childrenIds = [
						...history.messages[parentId].childrenIds,
						responseMessageId
					];
				}

Timothy J. Baek's avatar
Timothy J. Baek committed
254
255
				await tick();

256
257
258
259
260
261
				window.scrollTo({ top: document.body.scrollHeight });

				const res = await fetch(`https://api.openai.com/v1/chat/completions`, {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
Timothy J. Baek's avatar
Timothy J. Baek committed
262
						Authorization: `Bearer ${$settings.OPENAI_API_KEY}`
263
264
					},
					body: JSON.stringify({
Timothy J. Baek's avatar
Timothy J. Baek committed
265
						model: model,
266
						stream: true,
267
						messages: [
268
							$settings.system
269
270
								? {
										role: 'system',
Timothy J. Baek's avatar
Timothy J. Baek committed
271
										content: $settings.system
272
273
274
275
276
								  }
								: undefined,
							...messages
						]
							.filter((message) => message)
277
							.map((message) => ({ role: message.role, content: message.content })),
278
279
						temperature: $settings.temperature ?? undefined,
						top_p: $settings.top_p ?? undefined,
280
						num_ctx: $settings.num_ctx ?? undefined,
281
						frequency_penalty: $settings.repeat_penalty ?? undefined
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
					})
				});

				const reader = res.body
					.pipeThrough(new TextDecoderStream())
					.pipeThrough(splitStream('\n'))
					.getReader();

				while (true) {
					const { value, done } = await reader.read();
					if (done || stopResponseFlag) {
						if (stopResponseFlag) {
							responseMessage.done = true;
							messages = messages;
						}

						break;
					}

					try {
						let lines = value.split('\n');

						for (const line of lines) {
							if (line !== '') {
								console.log(line);
								if (line === 'data: [DONE]') {
									responseMessage.done = true;
									messages = messages;
								} else {
									let data = JSON.parse(line.replace(/^data: /, ''));
									console.log(data);

									if (responseMessage.content == '' && data.choices[0].delta.content == '\n') {
										continue;
									} else {
										responseMessage.content += data.choices[0].delta.content ?? '';
										messages = messages;
									}
								}
							}
						}
					} catch (error) {
						console.log(error);
					}

					if (autoScroll) {
						window.scrollTo({ top: document.body.scrollHeight });
					}

331
					await $db.updateChatById($chatId, {
332
						title: title === '' ? 'New Chat' : title,
Timothy J. Baek's avatar
Timothy J. Baek committed
333
						models: selectedModels,
334
						system: $settings.system ?? undefined,
335
						options: {
336
337
338
339
							seed: $settings.seed ?? undefined,
							temperature: $settings.temperature ?? undefined,
							repeat_penalty: $settings.repeat_penalty ?? undefined,
							top_k: $settings.top_k ?? undefined,
Anthony Cucci's avatar
Anthony Cucci committed
340
							top_p: $settings.top_p ?? undefined,
341
342
							num_ctx: $settings.num_ctx ?? undefined,
							...($settings.options ?? {})
343
						},
Timothy J. Baek's avatar
Timothy J. Baek committed
344
						messages: messages,
345
						history: history
346
347
348
349
350
351
352
353
354
355
356
					});
				}

				stopResponseFlag = false;

				await tick();
				if (autoScroll) {
					window.scrollTo({ top: document.body.scrollHeight });
				}

				if (messages.length == 2) {
357
358
					window.history.replaceState(history.state, '', `/c/${$chatId}`);
					await setChatTitle($chatId, userPrompt);
359
360
361
				}
			}
		}
362
363
364
	};

	const submitPrompt = async (userPrompt) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
365
		console.log('submitPrompt');
366

Timothy J. Baek's avatar
Timothy J. Baek committed
367
		if (selectedModels.includes('')) {
Timothy J. Baek's avatar
Timothy J. Baek committed
368
			toast.error('Model not selected');
369
		} else if (messages.length != 0 && messages.at(-1).done != true) {
Timothy J. Baek's avatar
Timothy J. Baek committed
370
371
			console.log('wait');
		} else {
Timothy J. Baek's avatar
Timothy J. Baek committed
372
373
			document.getElementById('chat-textarea').style.height = '';

Timothy J. Baek's avatar
Timothy J. Baek committed
374
375
376
377
378
379
			let userMessageId = uuidv4();
			let userMessage = {
				id: userMessageId,
				parentId: messages.length !== 0 ? messages.at(-1).id : null,
				childrenIds: [],
				role: 'user',
380
381
				content: userPrompt,
				files: files.length > 0 ? files : undefined
Timothy J. Baek's avatar
Timothy J. Baek committed
382
383
384
385
386
387
388
389
			};

			if (messages.length !== 0) {
				history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
			}

			history.messages[userMessageId] = userMessage;
			history.currentId = userMessageId;
Timothy J. Baek's avatar
Timothy J. Baek committed
390
391

			prompt = '';
392
			files = [];
Timothy J. Baek's avatar
Timothy J. Baek committed
393

394
			if (messages.length == 0) {
395
				await $db.createNewChat({
396
397
					id: $chatId,
					title: 'New Chat',
Timothy J. Baek's avatar
Timothy J. Baek committed
398
					models: selectedModels,
399
					system: $settings.system ?? undefined,
400
					options: {
401
402
403
404
						seed: $settings.seed ?? undefined,
						temperature: $settings.temperature ?? undefined,
						repeat_penalty: $settings.repeat_penalty ?? undefined,
						top_k: $settings.top_k ?? undefined,
Anthony Cucci's avatar
Anthony Cucci committed
405
						top_p: $settings.top_p ?? undefined,
406
407
						num_ctx: $settings.num_ctx ?? undefined,
						...($settings.options ?? {})
408
					},
Timothy J. Baek's avatar
Timothy J. Baek committed
409
					messages: messages,
410
					history: history
411
412
				});
			}
413

414
415
416
417
			setTimeout(() => {
				window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
			}, 50);

Timothy J. Baek's avatar
Timothy J. Baek committed
418
			await sendPrompt(userPrompt, userMessageId);
Timothy J. Baek's avatar
Timothy J. Baek committed
419
420
421
		}
	};

422
423
424
425
426
	const stopResponse = () => {
		stopResponseFlag = true;
		console.log('stopResponse');
	};

427
428
429
430
431
	const regenerateResponse = async () => {
		console.log('regenerateResponse');
		if (messages.length != 0 && messages.at(-1).done == true) {
			messages.splice(messages.length - 1, 1);
			messages = messages;
Timothy J. Baek's avatar
Timothy J. Baek committed
432

433
434
			let userMessage = messages.at(-1);
			let userPrompt = userMessage.content;
435

Timothy J. Baek's avatar
Timothy J. Baek committed
436
			await sendPrompt(userPrompt, userMessage.id);
Timothy J. Baek's avatar
Timothy J. Baek committed
437
		}
438
	};
439

440
	const generateChatTitle = async (_chatId, userPrompt) => {
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
		if ($settings.titleAutoGenerate ?? true) {
			console.log('generateChatTitle');

			const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/generate`, {
				method: 'POST',
				headers: {
					'Content-Type': 'text/event-stream',
					...($settings.authHeader && { Authorization: $settings.authHeader }),
					...($user && { Authorization: `Bearer ${localStorage.token}` })
				},
				body: JSON.stringify({
					model: selectedModels[0],
					prompt: `Generate a brief 3-5 word title for this question, excluding the term 'title.' Then, please reply with only the title: ${userPrompt}`,
					stream: false
				})
456
			})
457
458
459
460
461
462
463
464
465
466
467
				.then(async (res) => {
					if (!res.ok) throw await res.json();
					return res.json();
				})
				.catch((error) => {
					if ('detail' in error) {
						toast.error(error.detail);
					}
					console.log(error);
					return null;
				});
468

469
470
471
472
473
			if (res) {
				await setChatTitle(_chatId, res.response === '' ? 'New Chat' : res.response);
			}
		} else {
			await setChatTitle(_chatId, `${userPrompt}`);
474
475
476
477
		}
	};

	const setChatTitle = async (_chatId, _title) => {
478
479
		await $db.updateChatById(_chatId, { title: _title });
		if (_chatId === $chatId) {
480
			title = _title;
481
482
		}
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
483
484
</script>

485
486
<svelte:window
	on:scroll={(e) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
487
		autoScroll = window.innerHeight + window.scrollY >= document.body.offsetHeight - 40;
488
489
490
	}}
/>

491
492
493
494
495
<Navbar {title} />
<div class="min-h-screen w-full flex justify-center">
	<div class=" py-2.5 flex flex-col justify-between w-full">
		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 mt-10">
			<ModelSelector bind:selectedModels disabled={messages.length > 0} />
Timothy J. Baek's avatar
Timothy J. Baek committed
496
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
497

498
		<div class=" h-full mt-10 mb-32 w-full flex flex-col">
499
500
501
502
503
504
505
506
507
			<Messages
				{selectedModels}
				{selectedModelfile}
				bind:history
				bind:messages
				bind:autoScroll
				{sendPrompt}
				{regenerateResponse}
			/>
508
509
		</div>
	</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
510

511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
	<MessageInput
		bind:prompt
		bind:files
		bind:autoScroll
		suggestionPrompts={selectedModelfile?.suggestionPrompts ?? [
			{
				title: ['Help me study', 'vocabulary for a college entrance exam'],
				content: `Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.`
			},
			{
				title: ['Give me ideas', `for what to do with my kids' art`],
				content: `What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.`
			},
			{
				title: ['Tell me a fun fact', 'about the Roman Empire'],
				content: 'Tell me a random fun fact about the Roman Empire'
			},
			{
				title: ['Show me a code snippet', `of a website's sticky header`],
				content: `Show me a code snippet of a website's sticky header in CSS and JavaScript.`
			}
		]}
		{messages}
		{submitPrompt}
		{stopResponse}
	/>
537
</div>