forked from open-webui/open-webui
		
	feat: custom openai api endpoint support
This commit is contained in:
		
							parent
							
								
									77c1a77fcc
								
							
						
					
					
						commit
						f62228165c
					
				
					 4 changed files with 381 additions and 238 deletions
				
			
		|  | @ -56,6 +56,7 @@ | ||||||
| 
 | 
 | ||||||
| 	let gravatarEmail = ''; | 	let gravatarEmail = ''; | ||||||
| 	let OPENAI_API_KEY = ''; | 	let OPENAI_API_KEY = ''; | ||||||
|  | 	let OPENAI_API_BASE_URL = ''; | ||||||
| 
 | 
 | ||||||
| 	// Auth | 	// Auth | ||||||
| 	let authEnabled = false; | 	let authEnabled = false; | ||||||
|  | @ -302,8 +303,10 @@ | ||||||
| 
 | 
 | ||||||
| 		// If OpenAI API Key exists | 		// If OpenAI API Key exists | ||||||
| 		if (type === 'all' && $settings.OPENAI_API_KEY) { | 		if (type === 'all' && $settings.OPENAI_API_KEY) { | ||||||
|  | 			const API_BASE_URL = $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'; | ||||||
|  | 
 | ||||||
| 			// Validate OPENAI_API_KEY | 			// Validate OPENAI_API_KEY | ||||||
| 			const openaiModelRes = await fetch(`https://api.openai.com/v1/models`, { | 			const openaiModelRes = await fetch(`${API_BASE_URL}/models`, { | ||||||
| 				method: 'GET', | 				method: 'GET', | ||||||
| 				headers: { | 				headers: { | ||||||
| 					'Content-Type': 'application/json', | 					'Content-Type': 'application/json', | ||||||
|  | @ -327,8 +330,10 @@ | ||||||
| 					? [ | 					? [ | ||||||
| 							{ name: 'hr' }, | 							{ name: 'hr' }, | ||||||
| 							...openAIModels | 							...openAIModels | ||||||
| 								.map((model) => ({ name: model.id, label: 'OpenAI' })) | 								.map((model) => ({ name: model.id, external: true })) | ||||||
| 								.filter((model) => model.name.includes('gpt')) | 								.filter((model) => | ||||||
|  | 									API_BASE_URL.includes('openai') ? model.name.includes('gpt') : true | ||||||
|  | 								) | ||||||
| 					  ] | 					  ] | ||||||
| 					: []) | 					: []) | ||||||
| 			); | 			); | ||||||
|  | @ -363,6 +368,7 @@ | ||||||
| 
 | 
 | ||||||
| 		gravatarEmail = settings.gravatarEmail ?? ''; | 		gravatarEmail = settings.gravatarEmail ?? ''; | ||||||
| 		OPENAI_API_KEY = settings.OPENAI_API_KEY ?? ''; | 		OPENAI_API_KEY = settings.OPENAI_API_KEY ?? ''; | ||||||
|  | 		OPENAI_API_BASE_URL = settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'; | ||||||
| 
 | 
 | ||||||
| 		authEnabled = settings.authHeader !== undefined ? true : false; | 		authEnabled = settings.authHeader !== undefined ? true : false; | ||||||
| 		if (authEnabled) { | 		if (authEnabled) { | ||||||
|  | @ -476,6 +482,30 @@ | ||||||
| 					<div class=" self-center">Models</div> | 					<div class=" self-center">Models</div> | ||||||
| 				</button> | 				</button> | ||||||
| 
 | 
 | ||||||
|  | 				<button | ||||||
|  | 					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab === | ||||||
|  | 					'external' | ||||||
|  | 						? 'bg-gray-200 dark:bg-gray-700' | ||||||
|  | 						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}" | ||||||
|  | 					on:click={() => { | ||||||
|  | 						selectedTab = 'external'; | ||||||
|  | 					}} | ||||||
|  | 				> | ||||||
|  | 					<div class=" self-center mr-2"> | ||||||
|  | 						<svg | ||||||
|  | 							xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 							viewBox="0 0 16 16" | ||||||
|  | 							fill="currentColor" | ||||||
|  | 							class="w-4 h-4" | ||||||
|  | 						> | ||||||
|  | 							<path | ||||||
|  | 								d="M1 9.5A3.5 3.5 0 0 0 4.5 13H12a3 3 0 0 0 .917-5.857 2.503 2.503 0 0 0-3.198-3.019 3.5 3.5 0 0 0-6.628 2.171A3.5 3.5 0 0 0 1 9.5Z" | ||||||
|  | 							/> | ||||||
|  | 						</svg> | ||||||
|  | 					</div> | ||||||
|  | 					<div class=" self-center">External</div> | ||||||
|  | 				</button> | ||||||
|  | 
 | ||||||
| 				<button | 				<button | ||||||
| 					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab === | 					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab === | ||||||
| 					'addons' | 					'addons' | ||||||
|  | @ -859,14 +889,73 @@ | ||||||
| 							</div> | 							</div> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
|  | 				{:else if selectedTab === 'external'} | ||||||
|  | 					<form | ||||||
|  | 						class="flex flex-col h-full justify-between space-y-3 text-sm" | ||||||
|  | 						on:submit|preventDefault={() => { | ||||||
|  | 							saveSettings({ | ||||||
|  | 								OPENAI_API_KEY: OPENAI_API_KEY !== '' ? OPENAI_API_KEY : undefined, | ||||||
|  | 								OPENAI_API_BASE_URL: OPENAI_API_BASE_URL !== '' ? OPENAI_API_BASE_URL : undefined | ||||||
|  | 							}); | ||||||
|  | 							show = false; | ||||||
|  | 						}} | ||||||
|  | 					> | ||||||
|  | 						<div class=" space-y-3"> | ||||||
|  | 							<div> | ||||||
|  | 								<div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div> | ||||||
|  | 								<div class="flex w-full"> | ||||||
|  | 									<div class="flex-1"> | ||||||
|  | 										<input | ||||||
|  | 											class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||||
|  | 											placeholder="Enter OpenAI API Key" | ||||||
|  | 											bind:value={OPENAI_API_KEY} | ||||||
|  | 											autocomplete="off" | ||||||
|  | 										/> | ||||||
|  | 									</div> | ||||||
|  | 								</div> | ||||||
|  | 								<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> | ||||||
|  | 									Adds optional support for online models. | ||||||
|  | 								</div> | ||||||
|  | 							</div> | ||||||
|  | 
 | ||||||
|  | 							<hr class=" dark:border-gray-700" /> | ||||||
|  | 
 | ||||||
|  | 							<div> | ||||||
|  | 								<div class=" mb-2.5 text-sm font-medium">OpenAI API Base URL</div> | ||||||
|  | 								<div class="flex w-full"> | ||||||
|  | 									<div class="flex-1"> | ||||||
|  | 										<input | ||||||
|  | 											class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||||
|  | 											placeholder="Enter OpenAI API Key" | ||||||
|  | 											bind:value={OPENAI_API_BASE_URL} | ||||||
|  | 											autocomplete="off" | ||||||
|  | 										/> | ||||||
|  | 									</div> | ||||||
|  | 								</div> | ||||||
|  | 								<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> | ||||||
|  | 									WebUI will make requests to <span class=" text-gray-200" | ||||||
|  | 										>'{OPENAI_API_BASE_URL}/chat'</span | ||||||
|  | 									> | ||||||
|  | 								</div> | ||||||
|  | 							</div> | ||||||
|  | 						</div> | ||||||
|  | 
 | ||||||
|  | 						<div class="flex justify-end pt-3 text-sm font-medium"> | ||||||
|  | 							<button | ||||||
|  | 								class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" | ||||||
|  | 								type="submit" | ||||||
|  | 							> | ||||||
|  | 								Save | ||||||
|  | 							</button> | ||||||
|  | 						</div> | ||||||
|  | 					</form> | ||||||
| 				{:else if selectedTab === 'addons'} | 				{:else if selectedTab === 'addons'} | ||||||
| 					<form | 					<form | ||||||
| 						class="flex flex-col h-full justify-between space-y-3 text-sm" | 						class="flex flex-col h-full justify-between space-y-3 text-sm" | ||||||
| 						on:submit|preventDefault={() => { | 						on:submit|preventDefault={() => { | ||||||
| 							saveSettings({ | 							saveSettings({ | ||||||
| 								gravatarEmail: gravatarEmail !== '' ? gravatarEmail : undefined, | 								gravatarEmail: gravatarEmail !== '' ? gravatarEmail : undefined, | ||||||
| 								gravatarUrl: gravatarEmail !== '' ? getGravatarURL(gravatarEmail) : undefined, | 								gravatarUrl: gravatarEmail !== '' ? getGravatarURL(gravatarEmail) : undefined | ||||||
| 								OPENAI_API_KEY: OPENAI_API_KEY !== '' ? OPENAI_API_KEY : undefined |  | ||||||
| 							}); | 							}); | ||||||
| 							show = false; | 							show = false; | ||||||
| 						}} | 						}} | ||||||
|  | @ -962,26 +1051,6 @@ | ||||||
| 									> | 									> | ||||||
| 								</div> | 								</div> | ||||||
| 							</div> | 							</div> | ||||||
| 
 |  | ||||||
| 							<hr class=" dark:border-gray-700" /> |  | ||||||
| 							<div> |  | ||||||
| 								<div class=" mb-2.5 text-sm font-medium"> |  | ||||||
| 									OpenAI API Key <span class=" text-gray-400 text-sm">(optional)</span> |  | ||||||
| 								</div> |  | ||||||
| 								<div class="flex w-full"> |  | ||||||
| 									<div class="flex-1"> |  | ||||||
| 										<input |  | ||||||
| 											class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" |  | ||||||
| 											placeholder="Enter OpenAI API Key" |  | ||||||
| 											bind:value={OPENAI_API_KEY} |  | ||||||
| 											autocomplete="off" |  | ||||||
| 										/> |  | ||||||
| 									</div> |  | ||||||
| 								</div> |  | ||||||
| 								<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> |  | ||||||
| 									Adds optional support for 'gpt-*' models available. |  | ||||||
| 								</div> |  | ||||||
| 							</div> |  | ||||||
| 						</div> | 						</div> | ||||||
| 
 | 
 | ||||||
| 						<div class="flex justify-end pt-3 text-sm font-medium"> | 						<div class="flex justify-end pt-3 text-sm font-medium"> | ||||||
|  |  | ||||||
|  | @ -55,7 +55,9 @@ | ||||||
| 		// If OpenAI API Key exists | 		// If OpenAI API Key exists | ||||||
| 		if ($settings.OPENAI_API_KEY) { | 		if ($settings.OPENAI_API_KEY) { | ||||||
| 			// Validate OPENAI_API_KEY | 			// Validate OPENAI_API_KEY | ||||||
| 			const openaiModelRes = await fetch(`https://api.openai.com/v1/models`, { | 
 | ||||||
|  | 			const API_BASE_URL = $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'; | ||||||
|  | 			const openaiModelRes = await fetch(`${API_BASE_URL}/models`, { | ||||||
| 				method: 'GET', | 				method: 'GET', | ||||||
| 				headers: { | 				headers: { | ||||||
| 					'Content-Type': 'application/json', | 					'Content-Type': 'application/json', | ||||||
|  | @ -79,8 +81,10 @@ | ||||||
| 					? [ | 					? [ | ||||||
| 							{ name: 'hr' }, | 							{ name: 'hr' }, | ||||||
| 							...openAIModels | 							...openAIModels | ||||||
| 								.map((model) => ({ name: model.id, label: 'OpenAI' })) | 								.map((model) => ({ name: model.id, external: true })) | ||||||
| 								.filter((model) => model.name.includes('gpt')) | 								.filter((model) => | ||||||
|  | 									API_BASE_URL.includes('openai') ? model.name.includes('gpt') : true | ||||||
|  | 								) | ||||||
| 					  ] | 					  ] | ||||||
| 					: []) | 					: []) | ||||||
| 			); | 			); | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
| 	import { splitStream } from '$lib/utils'; | 	import { splitStream } from '$lib/utils'; | ||||||
| 	import { goto } from '$app/navigation'; | 	import { goto } from '$app/navigation'; | ||||||
| 
 | 
 | ||||||
| 	import { config, modelfiles, user, settings, db, chats, chatId } from '$lib/stores'; | 	import { config, models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores'; | ||||||
| 
 | 
 | ||||||
| 	import MessageInput from '$lib/components/chat/MessageInput.svelte'; | 	import MessageInput from '$lib/components/chat/MessageInput.svelte'; | ||||||
| 	import Messages from '$lib/components/chat/Messages.svelte'; | 	import Messages from '$lib/components/chat/Messages.svelte'; | ||||||
|  | @ -130,7 +130,8 @@ | ||||||
| 	const sendPrompt = async (userPrompt, parentId, _chatId) => { | 	const sendPrompt = async (userPrompt, parentId, _chatId) => { | ||||||
| 		await Promise.all( | 		await Promise.all( | ||||||
| 			selectedModels.map(async (model) => { | 			selectedModels.map(async (model) => { | ||||||
| 				if (model.includes('gpt-')) { | 				console.log(model); | ||||||
|  | 				if ($models.filter((m) => m.name === model)[0].external) { | ||||||
| 					await sendPromptOpenAI(model, userPrompt, parentId, _chatId); | 					await sendPromptOpenAI(model, userPrompt, parentId, _chatId); | ||||||
| 				} else { | 				} else { | ||||||
| 					await sendPromptOllama(model, userPrompt, parentId, _chatId); | 					await sendPromptOllama(model, userPrompt, parentId, _chatId); | ||||||
|  | @ -368,129 +369,163 @@ | ||||||
| 
 | 
 | ||||||
| 				window.scrollTo({ top: document.body.scrollHeight }); | 				window.scrollTo({ top: document.body.scrollHeight }); | ||||||
| 
 | 
 | ||||||
| 				const res = await fetch(`https://api.openai.com/v1/chat/completions`, { | 				const res = await fetch( | ||||||
| 					method: 'POST', | 					`${$settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'}/chat/completions`, | ||||||
| 					headers: { | 					{ | ||||||
| 						'Content-Type': 'application/json', | 						method: 'POST', | ||||||
| 						Authorization: `Bearer ${$settings.OPENAI_API_KEY}` | 						headers: { | ||||||
| 					}, | 							Authorization: `Bearer ${$settings.OPENAI_API_KEY}`, | ||||||
| 					body: JSON.stringify({ | 							'Content-Type': 'application/json', | ||||||
| 						model: model, | 							'HTTP-Referer': `https://ollamahub.com/`, | ||||||
| 						stream: true, | 							'X-Title': `Ollama WebUI` | ||||||
| 						messages: [ | 						}, | ||||||
| 							$settings.system | 						body: JSON.stringify({ | ||||||
| 								? { | 							model: model, | ||||||
| 										role: 'system', | 							stream: true, | ||||||
| 										content: $settings.system | 							messages: [ | ||||||
| 								  } | 								$settings.system | ||||||
| 								: undefined, |  | ||||||
| 							...messages |  | ||||||
| 						] |  | ||||||
| 							.filter((message) => message) |  | ||||||
| 							.map((message) => ({ |  | ||||||
| 								role: message.role, |  | ||||||
| 								...(message.files |  | ||||||
| 									? { | 									? { | ||||||
| 											content: [ | 											role: 'system', | ||||||
| 												{ | 											content: $settings.system | ||||||
| 													type: 'text', |  | ||||||
| 													text: message.content |  | ||||||
| 												}, |  | ||||||
| 												...message.files |  | ||||||
| 													.filter((file) => file.type === 'image') |  | ||||||
| 													.map((file) => ({ |  | ||||||
| 														type: 'image_url', |  | ||||||
| 														image_url: { |  | ||||||
| 															url: file.url |  | ||||||
| 														} |  | ||||||
| 													})) |  | ||||||
| 											] |  | ||||||
| 									  } | 									  } | ||||||
| 									: { content: message.content }) | 									: undefined, | ||||||
| 							})), | 								...messages | ||||||
| 						temperature: $settings.temperature ?? undefined, | 							] | ||||||
| 						top_p: $settings.top_p ?? undefined, | 								.filter((message) => message) | ||||||
| 						num_ctx: $settings.num_ctx ?? undefined, | 								.map((message) => ({ | ||||||
| 						frequency_penalty: $settings.repeat_penalty ?? undefined | 									role: message.role, | ||||||
| 					}) | 									...(message.files | ||||||
|  | 										? { | ||||||
|  | 												content: [ | ||||||
|  | 													{ | ||||||
|  | 														type: 'text', | ||||||
|  | 														text: message.content | ||||||
|  | 													}, | ||||||
|  | 													...message.files | ||||||
|  | 														.filter((file) => file.type === 'image') | ||||||
|  | 														.map((file) => ({ | ||||||
|  | 															type: 'image_url', | ||||||
|  | 															image_url: { | ||||||
|  | 																url: file.url | ||||||
|  | 															} | ||||||
|  | 														})) | ||||||
|  | 												] | ||||||
|  | 										  } | ||||||
|  | 										: { content: message.content }) | ||||||
|  | 								})), | ||||||
|  | 							temperature: $settings.temperature ?? undefined, | ||||||
|  | 							top_p: $settings.top_p ?? undefined, | ||||||
|  | 							num_ctx: $settings.num_ctx ?? undefined, | ||||||
|  | 							frequency_penalty: $settings.repeat_penalty ?? undefined | ||||||
|  | 						}) | ||||||
|  | 					} | ||||||
|  | 				).catch((err) => { | ||||||
|  | 					console.log(err); | ||||||
|  | 					return null; | ||||||
| 				}); | 				}); | ||||||
| 
 | 
 | ||||||
| 				const reader = res.body | 				if (res && res.ok) { | ||||||
| 					.pipeThrough(new TextDecoderStream()) | 					const reader = res.body | ||||||
| 					.pipeThrough(splitStream('\n')) | 						.pipeThrough(new TextDecoderStream()) | ||||||
| 					.getReader(); | 						.pipeThrough(splitStream('\n')) | ||||||
|  | 						.getReader(); | ||||||
| 
 | 
 | ||||||
| 				while (true) { | 					while (true) { | ||||||
| 					const { value, done } = await reader.read(); | 						const { value, done } = await reader.read(); | ||||||
| 					if (done || stopResponseFlag || _chatId !== $chatId) { | 						if (done || stopResponseFlag || _chatId !== $chatId) { | ||||||
| 						responseMessage.done = true; | 							responseMessage.done = true; | ||||||
| 						messages = messages; | 							messages = messages; | ||||||
| 						break; | 							break; | ||||||
| 					} | 						} | ||||||
| 
 | 
 | ||||||
| 					try { | 						try { | ||||||
| 						let lines = value.split('\n'); | 							let lines = value.split('\n'); | ||||||
| 
 | 
 | ||||||
| 						for (const line of lines) { | 							for (const line of lines) { | ||||||
| 							if (line !== '') { | 								if (line !== '') { | ||||||
| 								console.log(line); | 									console.log(line); | ||||||
| 								if (line === 'data: [DONE]') { | 									if (line === 'data: [DONE]') { | ||||||
| 									responseMessage.done = true; | 										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; | 										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); | ||||||
| 						} | 						} | ||||||
| 					} catch (error) { | 
 | ||||||
|  | 						if ($settings.notificationEnabled && !document.hasFocus()) { | ||||||
|  | 							const notification = new Notification(`OpenAI ${model}`, { | ||||||
|  | 								body: responseMessage.content, | ||||||
|  | 								icon: '/favicon.png' | ||||||
|  | 							}); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if ($settings.responseAutoCopy) { | ||||||
|  | 							copyToClipboard(responseMessage.content); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if (autoScroll) { | ||||||
|  | 							window.scrollTo({ top: document.body.scrollHeight }); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						await $db.updateChatById(_chatId, { | ||||||
|  | 							title: title === '' ? 'New Chat' : title, | ||||||
|  | 							models: selectedModels, | ||||||
|  | 							system: $settings.system ?? undefined, | ||||||
|  | 							options: { | ||||||
|  | 								seed: $settings.seed ?? undefined, | ||||||
|  | 								temperature: $settings.temperature ?? undefined, | ||||||
|  | 								repeat_penalty: $settings.repeat_penalty ?? undefined, | ||||||
|  | 								top_k: $settings.top_k ?? undefined, | ||||||
|  | 								top_p: $settings.top_p ?? undefined, | ||||||
|  | 								num_ctx: $settings.num_ctx ?? undefined, | ||||||
|  | 								...($settings.options ?? {}) | ||||||
|  | 							}, | ||||||
|  | 							messages: messages, | ||||||
|  | 							history: history | ||||||
|  | 						}); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					if (res !== null) { | ||||||
|  | 						const error = await res.json(); | ||||||
| 						console.log(error); | 						console.log(error); | ||||||
|  | 						if ('detail' in error) { | ||||||
|  | 							toast.error(error.detail); | ||||||
|  | 							responseMessage.content = error.detail; | ||||||
|  | 						} else { | ||||||
|  | 							if ('message' in error.error) { | ||||||
|  | 								toast.error(error.error.message); | ||||||
|  | 								responseMessage.content = error.error.message; | ||||||
|  | 							} else { | ||||||
|  | 								toast.error(error.error); | ||||||
|  | 								responseMessage.content = error.error; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						toast.error(`Uh-oh! There was an issue connecting to ${model}.`); | ||||||
|  | 						responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`; | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 					if (autoScroll) { | 					responseMessage.error = true; | ||||||
| 						window.scrollTo({ top: document.body.scrollHeight }); | 					responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`; | ||||||
| 					} | 					responseMessage.done = true; | ||||||
| 
 | 					messages = messages; | ||||||
| 					await $db.updateChatById(_chatId, { |  | ||||||
| 						title: title === '' ? 'New Chat' : title, |  | ||||||
| 						models: selectedModels, |  | ||||||
| 						system: $settings.system ?? undefined, |  | ||||||
| 						options: { |  | ||||||
| 							seed: $settings.seed ?? undefined, |  | ||||||
| 							temperature: $settings.temperature ?? undefined, |  | ||||||
| 							repeat_penalty: $settings.repeat_penalty ?? undefined, |  | ||||||
| 							top_k: $settings.top_k ?? undefined, |  | ||||||
| 							top_p: $settings.top_p ?? undefined, |  | ||||||
| 							num_ctx: $settings.num_ctx ?? undefined, |  | ||||||
| 							...($settings.options ?? {}) |  | ||||||
| 						}, |  | ||||||
| 						messages: messages, |  | ||||||
| 						history: history |  | ||||||
| 					}); |  | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				stopResponseFlag = false; | 				stopResponseFlag = false; | ||||||
| 
 |  | ||||||
| 				await tick(); | 				await tick(); | ||||||
| 
 | 
 | ||||||
| 				if ($settings.notificationEnabled && !document.hasFocus()) { |  | ||||||
| 					const notification = new Notification(`OpenAI ${model}`, { |  | ||||||
| 						body: responseMessage.content, |  | ||||||
| 						icon: '/favicon.png' |  | ||||||
| 					}); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if ($settings.responseAutoCopy) { |  | ||||||
| 					copyToClipboard(responseMessage.content); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if (autoScroll) { | 				if (autoScroll) { | ||||||
| 					window.scrollTo({ top: document.body.scrollHeight }); | 					window.scrollTo({ top: document.body.scrollHeight }); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ | ||||||
| 	import { onMount, tick } from 'svelte'; | 	import { onMount, tick } from 'svelte'; | ||||||
| 	import { convertMessagesToHistory, splitStream } from '$lib/utils'; | 	import { convertMessagesToHistory, splitStream } from '$lib/utils'; | ||||||
| 	import { goto } from '$app/navigation'; | 	import { goto } from '$app/navigation'; | ||||||
| 	import { config, modelfiles, user, settings, db, chats, chatId } from '$lib/stores'; | 	import { config, models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores'; | ||||||
| 
 | 
 | ||||||
| 	import MessageInput from '$lib/components/chat/MessageInput.svelte'; | 	import MessageInput from '$lib/components/chat/MessageInput.svelte'; | ||||||
| 	import Messages from '$lib/components/chat/Messages.svelte'; | 	import Messages from '$lib/components/chat/Messages.svelte'; | ||||||
|  | @ -144,7 +144,8 @@ | ||||||
| 	const sendPrompt = async (userPrompt, parentId, _chatId) => { | 	const sendPrompt = async (userPrompt, parentId, _chatId) => { | ||||||
| 		await Promise.all( | 		await Promise.all( | ||||||
| 			selectedModels.map(async (model) => { | 			selectedModels.map(async (model) => { | ||||||
| 				if (model.includes('gpt-')) { | 				console.log(model); | ||||||
|  | 				if ($models.filter((m) => m.name === model)[0].external) { | ||||||
| 					await sendPromptOpenAI(model, userPrompt, parentId, _chatId); | 					await sendPromptOpenAI(model, userPrompt, parentId, _chatId); | ||||||
| 				} else { | 				} else { | ||||||
| 					await sendPromptOllama(model, userPrompt, parentId, _chatId); | 					await sendPromptOllama(model, userPrompt, parentId, _chatId); | ||||||
|  | @ -382,129 +383,163 @@ | ||||||
| 
 | 
 | ||||||
| 				window.scrollTo({ top: document.body.scrollHeight }); | 				window.scrollTo({ top: document.body.scrollHeight }); | ||||||
| 
 | 
 | ||||||
| 				const res = await fetch(`https://api.openai.com/v1/chat/completions`, { | 				const res = await fetch( | ||||||
| 					method: 'POST', | 					`${$settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'}/chat/completions`, | ||||||
| 					headers: { | 					{ | ||||||
| 						'Content-Type': 'application/json', | 						method: 'POST', | ||||||
| 						Authorization: `Bearer ${$settings.OPENAI_API_KEY}` | 						headers: { | ||||||
| 					}, | 							Authorization: `Bearer ${$settings.OPENAI_API_KEY}`, | ||||||
| 					body: JSON.stringify({ | 							'Content-Type': 'application/json', | ||||||
| 						model: model, | 							'HTTP-Referer': `https://ollamahub.com/`, | ||||||
| 						stream: true, | 							'X-Title': `Ollama WebUI` | ||||||
| 						messages: [ | 						}, | ||||||
| 							$settings.system | 						body: JSON.stringify({ | ||||||
| 								? { | 							model: model, | ||||||
| 										role: 'system', | 							stream: true, | ||||||
| 										content: $settings.system | 							messages: [ | ||||||
| 								  } | 								$settings.system | ||||||
| 								: undefined, |  | ||||||
| 							...messages |  | ||||||
| 						] |  | ||||||
| 							.filter((message) => message) |  | ||||||
| 							.map((message) => ({ |  | ||||||
| 								role: message.role, |  | ||||||
| 								...(message.files |  | ||||||
| 									? { | 									? { | ||||||
| 											content: [ | 											role: 'system', | ||||||
| 												{ | 											content: $settings.system | ||||||
| 													type: 'text', |  | ||||||
| 													text: message.content |  | ||||||
| 												}, |  | ||||||
| 												...message.files |  | ||||||
| 													.filter((file) => file.type === 'image') |  | ||||||
| 													.map((file) => ({ |  | ||||||
| 														type: 'image_url', |  | ||||||
| 														image_url: { |  | ||||||
| 															url: file.url |  | ||||||
| 														} |  | ||||||
| 													})) |  | ||||||
| 											] |  | ||||||
| 									  } | 									  } | ||||||
| 									: { content: message.content }) | 									: undefined, | ||||||
| 							})), | 								...messages | ||||||
| 						temperature: $settings.temperature ?? undefined, | 							] | ||||||
| 						top_p: $settings.top_p ?? undefined, | 								.filter((message) => message) | ||||||
| 						num_ctx: $settings.num_ctx ?? undefined, | 								.map((message) => ({ | ||||||
| 						frequency_penalty: $settings.repeat_penalty ?? undefined | 									role: message.role, | ||||||
| 					}) | 									...(message.files | ||||||
|  | 										? { | ||||||
|  | 												content: [ | ||||||
|  | 													{ | ||||||
|  | 														type: 'text', | ||||||
|  | 														text: message.content | ||||||
|  | 													}, | ||||||
|  | 													...message.files | ||||||
|  | 														.filter((file) => file.type === 'image') | ||||||
|  | 														.map((file) => ({ | ||||||
|  | 															type: 'image_url', | ||||||
|  | 															image_url: { | ||||||
|  | 																url: file.url | ||||||
|  | 															} | ||||||
|  | 														})) | ||||||
|  | 												] | ||||||
|  | 										  } | ||||||
|  | 										: { content: message.content }) | ||||||
|  | 								})), | ||||||
|  | 							temperature: $settings.temperature ?? undefined, | ||||||
|  | 							top_p: $settings.top_p ?? undefined, | ||||||
|  | 							num_ctx: $settings.num_ctx ?? undefined, | ||||||
|  | 							frequency_penalty: $settings.repeat_penalty ?? undefined | ||||||
|  | 						}) | ||||||
|  | 					} | ||||||
|  | 				).catch((err) => { | ||||||
|  | 					console.log(err); | ||||||
|  | 					return null; | ||||||
| 				}); | 				}); | ||||||
| 
 | 
 | ||||||
| 				const reader = res.body | 				if (res && res.ok) { | ||||||
| 					.pipeThrough(new TextDecoderStream()) | 					const reader = res.body | ||||||
| 					.pipeThrough(splitStream('\n')) | 						.pipeThrough(new TextDecoderStream()) | ||||||
| 					.getReader(); | 						.pipeThrough(splitStream('\n')) | ||||||
|  | 						.getReader(); | ||||||
| 
 | 
 | ||||||
| 				while (true) { | 					while (true) { | ||||||
| 					const { value, done } = await reader.read(); | 						const { value, done } = await reader.read(); | ||||||
| 					if (done || stopResponseFlag || _chatId !== $chatId) { | 						if (done || stopResponseFlag || _chatId !== $chatId) { | ||||||
| 						responseMessage.done = true; | 							responseMessage.done = true; | ||||||
| 						messages = messages; | 							messages = messages; | ||||||
| 						break; | 							break; | ||||||
| 					} | 						} | ||||||
| 
 | 
 | ||||||
| 					try { | 						try { | ||||||
| 						let lines = value.split('\n'); | 							let lines = value.split('\n'); | ||||||
| 
 | 
 | ||||||
| 						for (const line of lines) { | 							for (const line of lines) { | ||||||
| 							if (line !== '') { | 								if (line !== '') { | ||||||
| 								console.log(line); | 									console.log(line); | ||||||
| 								if (line === 'data: [DONE]') { | 									if (line === 'data: [DONE]') { | ||||||
| 									responseMessage.done = true; | 										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; | 										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); | ||||||
| 						} | 						} | ||||||
| 					} catch (error) { | 
 | ||||||
|  | 						if ($settings.notificationEnabled && !document.hasFocus()) { | ||||||
|  | 							const notification = new Notification(`OpenAI ${model}`, { | ||||||
|  | 								body: responseMessage.content, | ||||||
|  | 								icon: '/favicon.png' | ||||||
|  | 							}); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if ($settings.responseAutoCopy) { | ||||||
|  | 							copyToClipboard(responseMessage.content); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if (autoScroll) { | ||||||
|  | 							window.scrollTo({ top: document.body.scrollHeight }); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						await $db.updateChatById(_chatId, { | ||||||
|  | 							title: title === '' ? 'New Chat' : title, | ||||||
|  | 							models: selectedModels, | ||||||
|  | 							system: $settings.system ?? undefined, | ||||||
|  | 							options: { | ||||||
|  | 								seed: $settings.seed ?? undefined, | ||||||
|  | 								temperature: $settings.temperature ?? undefined, | ||||||
|  | 								repeat_penalty: $settings.repeat_penalty ?? undefined, | ||||||
|  | 								top_k: $settings.top_k ?? undefined, | ||||||
|  | 								top_p: $settings.top_p ?? undefined, | ||||||
|  | 								num_ctx: $settings.num_ctx ?? undefined, | ||||||
|  | 								...($settings.options ?? {}) | ||||||
|  | 							}, | ||||||
|  | 							messages: messages, | ||||||
|  | 							history: history | ||||||
|  | 						}); | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					if (res !== null) { | ||||||
|  | 						const error = await res.json(); | ||||||
| 						console.log(error); | 						console.log(error); | ||||||
|  | 						if ('detail' in error) { | ||||||
|  | 							toast.error(error.detail); | ||||||
|  | 							responseMessage.content = error.detail; | ||||||
|  | 						} else { | ||||||
|  | 							if ('message' in error.error) { | ||||||
|  | 								toast.error(error.error.message); | ||||||
|  | 								responseMessage.content = error.error.message; | ||||||
|  | 							} else { | ||||||
|  | 								toast.error(error.error); | ||||||
|  | 								responseMessage.content = error.error; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						toast.error(`Uh-oh! There was an issue connecting to ${model}.`); | ||||||
|  | 						responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`; | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 					if (autoScroll) { | 					responseMessage.error = true; | ||||||
| 						window.scrollTo({ top: document.body.scrollHeight }); | 					responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`; | ||||||
| 					} | 					responseMessage.done = true; | ||||||
| 
 | 					messages = messages; | ||||||
| 					await $db.updateChatById(_chatId, { |  | ||||||
| 						title: title === '' ? 'New Chat' : title, |  | ||||||
| 						models: selectedModels, |  | ||||||
| 						system: $settings.system ?? undefined, |  | ||||||
| 						options: { |  | ||||||
| 							seed: $settings.seed ?? undefined, |  | ||||||
| 							temperature: $settings.temperature ?? undefined, |  | ||||||
| 							repeat_penalty: $settings.repeat_penalty ?? undefined, |  | ||||||
| 							top_k: $settings.top_k ?? undefined, |  | ||||||
| 							top_p: $settings.top_p ?? undefined, |  | ||||||
| 							num_ctx: $settings.num_ctx ?? undefined, |  | ||||||
| 							...($settings.options ?? {}) |  | ||||||
| 						}, |  | ||||||
| 						messages: messages, |  | ||||||
| 						history: history |  | ||||||
| 					}); |  | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				stopResponseFlag = false; | 				stopResponseFlag = false; | ||||||
| 
 |  | ||||||
| 				await tick(); | 				await tick(); | ||||||
| 
 | 
 | ||||||
| 				if ($settings.notificationEnabled && !document.hasFocus()) { |  | ||||||
| 					const notification = new Notification(`OpenAI ${model}`, { |  | ||||||
| 						body: responseMessage.content, |  | ||||||
| 						icon: '/favicon.png' |  | ||||||
| 					}); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if ($settings.responseAutoCopy) { |  | ||||||
| 					copyToClipboard(responseMessage.content); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				if (autoScroll) { | 				if (autoScroll) { | ||||||
| 					window.scrollTo({ top: document.body.scrollHeight }); | 					window.scrollTo({ top: document.body.scrollHeight }); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek