forked from open-webui/open-webui
		
	code refactored + regenerate button added
This commit is contained in:
		
							parent
							
								
									457e93b47f
								
							
						
					
					
						commit
						1f52207dcf
					
				
					 1 changed files with 207 additions and 102 deletions
				
			
		|  | @ -6,17 +6,19 @@ | ||||||
| 
 | 
 | ||||||
| 	import type { PageData } from './$types'; | 	import type { PageData } from './$types'; | ||||||
| 	import { ENDPOINT } from '$lib/contants'; | 	import { ENDPOINT } from '$lib/contants'; | ||||||
|  | 	import { tick } from 'svelte'; | ||||||
| 
 | 
 | ||||||
| 	export let data: PageData; | 	export let data: PageData; | ||||||
| 	$: ({ models } = data); | 	$: ({ models } = data); | ||||||
|  | 	let textareaElement; | ||||||
| 
 | 
 | ||||||
| 	let selectedModel = ''; | 	let selectedModel = ''; | ||||||
| 	let prompt = ''; | 	let prompt = ''; | ||||||
| 	let context = ''; | 	let messages = []; | ||||||
| 
 | 
 | ||||||
| 	let chatHistory = {}; | 	////////////////////////// | ||||||
| 
 | 	// Helper functions | ||||||
| 	let textareaElement = ''; | 	////////////////////////// | ||||||
| 
 | 
 | ||||||
| 	const splitStream = (splitOn) => { | 	const splitStream = (splitOn) => { | ||||||
| 		let buffer = ''; | 		let buffer = ''; | ||||||
|  | @ -33,93 +35,6 @@ | ||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const submitPrompt = async () => { |  | ||||||
| 		console.log('submitPrompt'); |  | ||||||
| 
 |  | ||||||
| 		if (selectedModel === '') { |  | ||||||
| 			toast.error('Model not selected'); |  | ||||||
| 		} else if ( |  | ||||||
| 			Object.keys(chatHistory).length != 0 && |  | ||||||
| 			chatHistory[Object.keys(chatHistory).length - 1].done != true |  | ||||||
| 		) { |  | ||||||
| 			console.log('wait'); |  | ||||||
| 		} else { |  | ||||||
| 			console.log(prompt); |  | ||||||
| 
 |  | ||||||
| 			let user_prompt = prompt; |  | ||||||
| 
 |  | ||||||
| 			chatHistory[Object.keys(chatHistory).length] = { |  | ||||||
| 				role: 'user', |  | ||||||
| 				content: user_prompt |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 			prompt = ''; |  | ||||||
| 			textareaElement.style.height = ''; |  | ||||||
| 
 |  | ||||||
| 			setTimeout(() => { |  | ||||||
| 				window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); |  | ||||||
| 			}, 50); |  | ||||||
| 
 |  | ||||||
| 			chatHistory[Object.keys(chatHistory).length] = { |  | ||||||
| 				role: 'assistant', |  | ||||||
| 				content: '' |  | ||||||
| 			}; |  | ||||||
| 			window.scrollTo({ top: document.body.scrollHeight }); |  | ||||||
| 
 |  | ||||||
| 			const res = await fetch(`${ENDPOINT}/api/generate`, { |  | ||||||
| 				method: 'POST', |  | ||||||
| 				headers: { |  | ||||||
| 					'Content-Type': 'text/event-stream' |  | ||||||
| 				}, |  | ||||||
| 				body: JSON.stringify({ |  | ||||||
| 					model: selectedModel, |  | ||||||
| 					prompt: user_prompt, |  | ||||||
| 					context: context != '' ? context : undefined |  | ||||||
| 				}) |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			const reader = res.body |  | ||||||
| 				.pipeThrough(new TextDecoderStream()) |  | ||||||
| 				.pipeThrough(splitStream('\n')) |  | ||||||
| 				.getReader(); |  | ||||||
| 			while (true) { |  | ||||||
| 				const { value, done } = await reader.read(); |  | ||||||
| 				if (done) 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 ( |  | ||||||
| 									chatHistory[Object.keys(chatHistory).length - 1].content == '' && |  | ||||||
| 									data.response == '\n' |  | ||||||
| 								) { |  | ||||||
| 									continue; |  | ||||||
| 								} else { |  | ||||||
| 									chatHistory[Object.keys(chatHistory).length - 1].content += data.response; |  | ||||||
| 								} |  | ||||||
| 							} else { |  | ||||||
| 								context = data.context; |  | ||||||
| 								console.log(context); |  | ||||||
| 								chatHistory[Object.keys(chatHistory).length - 1].done = true; |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} catch (error) { |  | ||||||
| 					console.log(error); |  | ||||||
| 				} |  | ||||||
| 				window.scrollTo({ top: document.body.scrollHeight }); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			window.scrollTo({ top: document.body.scrollHeight }); |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	const copyToClipboard = (text) => { | 	const copyToClipboard = (text) => { | ||||||
| 		if (!navigator.clipboard) { | 		if (!navigator.clipboard) { | ||||||
| 			var textArea = document.createElement('textarea'); | 			var textArea = document.createElement('textarea'); | ||||||
|  | @ -155,6 +70,172 @@ | ||||||
| 			} | 			} | ||||||
| 		); | 		); | ||||||
| 	}; | 	}; | ||||||
|  | 
 | ||||||
|  | 	////////////////////////// | ||||||
|  | 	// Ollama functions | ||||||
|  | 	////////////////////////// | ||||||
|  | 
 | ||||||
|  | 	const submitPrompt = async () => { | ||||||
|  | 		console.log('submitPrompt'); | ||||||
|  | 
 | ||||||
|  | 		if (selectedModel === '') { | ||||||
|  | 			toast.error('Model not selected'); | ||||||
|  | 		} else if (messages.length != 0 && messages.at(-1).done != true) { | ||||||
|  | 			console.log('wait'); | ||||||
|  | 		} else { | ||||||
|  | 			console.log(prompt); | ||||||
|  | 
 | ||||||
|  | 			let user_prompt = prompt; | ||||||
|  | 			messages = [ | ||||||
|  | 				...messages, | ||||||
|  | 				{ | ||||||
|  | 					role: 'user', | ||||||
|  | 					content: user_prompt | ||||||
|  | 				} | ||||||
|  | 			]; | ||||||
|  | 			prompt = ''; | ||||||
|  | 
 | ||||||
|  | 			textareaElement.style.height = ''; | ||||||
|  | 			setTimeout(() => { | ||||||
|  | 				window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); | ||||||
|  | 			}, 50); | ||||||
|  | 
 | ||||||
|  | 			let responseMessage = { | ||||||
|  | 				role: 'assistant', | ||||||
|  | 				content: '' | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
|  | 			messages = [...messages, responseMessage]; | ||||||
|  | 			window.scrollTo({ top: document.body.scrollHeight }); | ||||||
|  | 
 | ||||||
|  | 			const res = await fetch(`${ENDPOINT}/api/generate`, { | ||||||
|  | 				method: 'POST', | ||||||
|  | 				headers: { | ||||||
|  | 					'Content-Type': 'text/event-stream' | ||||||
|  | 				}, | ||||||
|  | 				body: JSON.stringify({ | ||||||
|  | 					model: selectedModel, | ||||||
|  | 					prompt: user_prompt, | ||||||
|  | 					context: | ||||||
|  | 						messages.length > 3 && messages.at(-3).context != undefined | ||||||
|  | 							? messages.at(-3).context | ||||||
|  | 							: undefined | ||||||
|  | 				}) | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
|  | 			const reader = res.body | ||||||
|  | 				.pipeThrough(new TextDecoderStream()) | ||||||
|  | 				.pipeThrough(splitStream('\n')) | ||||||
|  | 				.getReader(); | ||||||
|  | 
 | ||||||
|  | 			while (true) { | ||||||
|  | 				const { value, done } = await reader.read(); | ||||||
|  | 				if (done) 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; | ||||||
|  | 								} | ||||||
|  | 							} else { | ||||||
|  | 								responseMessage.done = true; | ||||||
|  | 								responseMessage.context = data.context; | ||||||
|  | 								messages = messages; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} catch (error) { | ||||||
|  | 					console.log(error); | ||||||
|  | 				} | ||||||
|  | 				window.scrollTo({ top: document.body.scrollHeight }); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			window.scrollTo({ top: document.body.scrollHeight }); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	const regenerateResponse = async () => { | ||||||
|  | 		console.log('regenerateResponse'); | ||||||
|  | 
 | ||||||
|  | 		if (messages.length != 0 && messages.at(-1).done == true) { | ||||||
|  | 			messages.splice(messages.length - 1, 1); | ||||||
|  | 			messages = messages; | ||||||
|  | 
 | ||||||
|  | 			let lastUserMessage = messages.at(-1); | ||||||
|  | 
 | ||||||
|  | 			let responseMessage = { | ||||||
|  | 				role: 'assistant', | ||||||
|  | 				content: '' | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
|  | 			messages = [...messages, responseMessage]; | ||||||
|  | 			window.scrollTo({ top: document.body.scrollHeight }); | ||||||
|  | 
 | ||||||
|  | 			const res = await fetch(`${ENDPOINT}/api/generate`, { | ||||||
|  | 				method: 'POST', | ||||||
|  | 				headers: { | ||||||
|  | 					'Content-Type': 'text/event-stream' | ||||||
|  | 				}, | ||||||
|  | 				body: JSON.stringify({ | ||||||
|  | 					model: selectedModel, | ||||||
|  | 					prompt: lastUserMessage.content, | ||||||
|  | 					context: | ||||||
|  | 						messages.length > 3 && messages.at(-3).context != undefined | ||||||
|  | 							? messages.at(-3).context | ||||||
|  | 							: undefined | ||||||
|  | 				}) | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
|  | 			const reader = res.body | ||||||
|  | 				.pipeThrough(new TextDecoderStream()) | ||||||
|  | 				.pipeThrough(splitStream('\n')) | ||||||
|  | 				.getReader(); | ||||||
|  | 
 | ||||||
|  | 			while (true) { | ||||||
|  | 				const { value, done } = await reader.read(); | ||||||
|  | 				if (done) 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; | ||||||
|  | 								} | ||||||
|  | 							} else { | ||||||
|  | 								responseMessage.done = true; | ||||||
|  | 								responseMessage.context = data.context; | ||||||
|  | 								messages = messages; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} catch (error) { | ||||||
|  | 					console.log(error); | ||||||
|  | 				} | ||||||
|  | 				window.scrollTo({ top: document.body.scrollHeight }); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			window.scrollTo({ top: document.body.scrollHeight }); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		console.log(messages); | ||||||
|  | 	}; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="app text-gray-100"> | <div class="app text-gray-100"> | ||||||
|  | @ -171,7 +252,7 @@ | ||||||
| 								id="models" | 								id="models" | ||||||
| 								class="outline-none border border-gray-600 bg-gray-700 text-gray-200 text-sm rounded-lg block w-full p-2.5 placeholder-gray-400" | 								class="outline-none border border-gray-600 bg-gray-700 text-gray-200 text-sm rounded-lg block w-full p-2.5 placeholder-gray-400" | ||||||
| 								bind:value={selectedModel} | 								bind:value={selectedModel} | ||||||
| 								disabled={Object.keys(chatHistory).length != 0} | 								disabled={messages.length != 0} | ||||||
| 							> | 							> | ||||||
| 								<option value="" selected>Select a model</option> | 								<option value="" selected>Select a model</option> | ||||||
| 
 | 
 | ||||||
|  | @ -183,8 +264,8 @@ | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 
 | 
 | ||||||
| 				<div class=" h-full mb-32 w-full flex flex-col"> | 				<div class=" h-full mb-44 w-full flex flex-col"> | ||||||
| 					{#if Object.keys(chatHistory).length == 0} | 					{#if messages.length == 0} | ||||||
| 						<div class="m-auto text-center max-w-md"> | 						<div class="m-auto text-center max-w-md"> | ||||||
| 							<div class="flex justify-center mt-8"> | 							<div class="flex justify-center mt-8"> | ||||||
| 								<img src="/ollama.png" class="w-16 invert-[80%]" /> | 								<img src="/ollama.png" class="w-16 invert-[80%]" /> | ||||||
|  | @ -198,18 +279,18 @@ | ||||||
| 							</div> | 							</div> | ||||||
| 						</div> | 						</div> | ||||||
| 					{:else} | 					{:else} | ||||||
| 						{#each Object.keys(chatHistory) as messageIdx} | 						{#each messages as message, messageIdx} | ||||||
| 							<div class=" w-full {chatHistory[messageIdx].role == 'user' ? '' : ' bg-gray-700'}"> | 							<div class=" w-full {message.role == 'user' ? '' : ' bg-gray-700'}"> | ||||||
| 								<div class="flex justify-between p-5 py-10 max-w-3xl mx-auto rounded-lg"> | 								<div class="flex justify-between p-5 py-10 max-w-3xl mx-auto rounded-lg"> | ||||||
| 									<div class="space-x-7 flex w-full"> | 									<div class="space-x-7 flex w-full"> | ||||||
| 										<div class=""> | 										<div class=""> | ||||||
| 											<img | 											<img | ||||||
| 												src="/{chatHistory[messageIdx].role == 'user' ? 'user' : 'favicon'}.png" | 												src="/{message.role == 'user' ? 'user' : 'favicon'}.png" | ||||||
| 												class=" max-w-[32px] object-cover rounded" | 												class=" max-w-[32px] object-cover rounded" | ||||||
| 											/> | 											/> | ||||||
| 										</div> | 										</div> | ||||||
| 
 | 
 | ||||||
| 										{#if chatHistory[messageIdx].role != 'user' && chatHistory[messageIdx].content == ''} | 										{#if message.role != 'user' && message.content == ''} | ||||||
| 											<div class="w-full pr-28"> | 											<div class="w-full pr-28"> | ||||||
| 												<div class="animate-pulse flex w-full"> | 												<div class="animate-pulse flex w-full"> | ||||||
| 													<div class="space-y-2 w-full"> | 													<div class="space-y-2 w-full"> | ||||||
|  | @ -231,18 +312,18 @@ | ||||||
| 											</div> | 											</div> | ||||||
| 										{:else} | 										{:else} | ||||||
| 											<div class="whitespace-pre-line"> | 											<div class="whitespace-pre-line"> | ||||||
| 												{@html marked.parse(chatHistory[messageIdx].content)} | 												{@html marked.parse(message.content)} | ||||||
| 											</div> | 											</div> | ||||||
| 										{/if} | 										{/if} | ||||||
| 										<!-- {} --> | 										<!-- {} --> | ||||||
| 									</div> | 									</div> | ||||||
| 
 | 
 | ||||||
| 									<div> | 									<div> | ||||||
| 										{#if chatHistory[messageIdx].role != 'user' && chatHistory[messageIdx].done} | 										{#if message.role != 'user' && message.done} | ||||||
| 											<button | 											<button | ||||||
| 												class="p-1 rounded hover:bg-gray-700 transition" | 												class="p-1 rounded hover:bg-gray-700 transition" | ||||||
| 												on:click={() => { | 												on:click={() => { | ||||||
| 													copyToClipboard(chatHistory[messageIdx].content); | 													copyToClipboard(message.content); | ||||||
| 												}} | 												}} | ||||||
| 											> | 											> | ||||||
| 												<svg | 												<svg | ||||||
|  | @ -274,6 +355,30 @@ | ||||||
| 
 | 
 | ||||||
| 				<div class=" bg-gradient-to-t from-gray-900 pt-5"> | 				<div class=" bg-gradient-to-t from-gray-900 pt-5"> | ||||||
| 					<div class="max-w-3xl p-2.5 -mb-0.5 mx-auto inset-x-0"> | 					<div class="max-w-3xl p-2.5 -mb-0.5 mx-auto inset-x-0"> | ||||||
|  | 						{#if messages.length != 0 && messages.at(-1).role == 'assistant' && messages.at(-1).done == true} | ||||||
|  | 							<div class=" flex justify-end mb-2.5"> | ||||||
|  | 								<button | ||||||
|  | 									class=" flex px-4 py-2.5 bg-gray-800 hover:bg-gray-700 outline outline-1 outline-gray-600 rounded" | ||||||
|  | 									on:click={regenerateResponse} | ||||||
|  | 								> | ||||||
|  | 									<div class=" self-center mr-1"> | ||||||
|  | 										<svg | ||||||
|  | 											xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 											viewBox="0 0 20 20" | ||||||
|  | 											fill="currentColor" | ||||||
|  | 											class="w-4 h-4" | ||||||
|  | 										> | ||||||
|  | 											<path | ||||||
|  | 												fill-rule="evenodd" | ||||||
|  | 												d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z" | ||||||
|  | 												clip-rule="evenodd" | ||||||
|  | 											/> | ||||||
|  | 										</svg> | ||||||
|  | 									</div> | ||||||
|  | 									<div class=" self-center text-sm">Regenerate</div> | ||||||
|  | 								</button> | ||||||
|  | 							</div> | ||||||
|  | 						{/if} | ||||||
| 						<form class=" flex shadow-sm relative w-full" on:submit|preventDefault={submitPrompt}> | 						<form class=" flex shadow-sm relative w-full" on:submit|preventDefault={submitPrompt}> | ||||||
| 							<textarea | 							<textarea | ||||||
| 								class="rounded-xl bg-gray-700 outline-none w-full py-3 px-5 pr-12 resize-none" | 								class="rounded-xl bg-gray-700 outline-none w-full py-3 px-5 pr-12 resize-none" | ||||||
|  | @ -296,7 +401,7 @@ | ||||||
| 							/> | 							/> | ||||||
| 							<div class=" absolute right-0 bottom-0"> | 							<div class=" absolute right-0 bottom-0"> | ||||||
| 								<div class="pr-3 pb-2"> | 								<div class="pr-3 pb-2"> | ||||||
| 									{#if Object.keys(chatHistory).length == 0 || chatHistory[Object.keys(chatHistory).length - 1].done == true} | 									{#if messages.length == 0 || messages.at(-1).done == true} | ||||||
| 										<button | 										<button | ||||||
| 											class="{prompt !== '' | 											class="{prompt !== '' | ||||||
| 												? 'bg-emerald-600 text-gray-100 hover:bg-emerald-700 ' | 												? 'bg-emerald-600 text-gray-100 hover:bg-emerald-700 ' | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek