mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +00:00 
			
		
		
		
	chat feature added
This commit is contained in:
		
							parent
							
								
									5cd4946df2
								
							
						
					
					
						commit
						5e03670f1e
					
				
							
								
								
									
										13
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | .DS_Store | ||||||
|  | node_modules | ||||||
|  | /build | ||||||
|  | /.svelte-kit | ||||||
|  | /package | ||||||
|  | .env | ||||||
|  | .env.* | ||||||
|  | !.env.example | ||||||
|  | 
 | ||||||
|  | # Ignore files for PNPM, NPM and YARN | ||||||
|  | pnpm-lock.yaml | ||||||
|  | package-lock.json | ||||||
|  | yarn.lock | ||||||
							
								
								
									
										30
									
								
								.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | module.exports = { | ||||||
|  | 	root: true, | ||||||
|  | 	extends: [ | ||||||
|  | 		'eslint:recommended', | ||||||
|  | 		'plugin:@typescript-eslint/recommended', | ||||||
|  | 		'plugin:svelte/recommended', | ||||||
|  | 		'prettier' | ||||||
|  | 	], | ||||||
|  | 	parser: '@typescript-eslint/parser', | ||||||
|  | 	plugins: ['@typescript-eslint'], | ||||||
|  | 	parserOptions: { | ||||||
|  | 		sourceType: 'module', | ||||||
|  | 		ecmaVersion: 2020, | ||||||
|  | 		extraFileExtensions: ['.svelte'] | ||||||
|  | 	}, | ||||||
|  | 	env: { | ||||||
|  | 		browser: true, | ||||||
|  | 		es2017: true, | ||||||
|  | 		node: true | ||||||
|  | 	}, | ||||||
|  | 	overrides: [ | ||||||
|  | 		{ | ||||||
|  | 			files: ['*.svelte'], | ||||||
|  | 			parser: 'svelte-eslint-parser', | ||||||
|  | 			parserOptions: { | ||||||
|  | 				parser: '@typescript-eslint/parser' | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	] | ||||||
|  | }; | ||||||
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | .DS_Store | ||||||
|  | node_modules | ||||||
|  | /build | ||||||
|  | /.svelte-kit | ||||||
|  | /package | ||||||
|  | .env | ||||||
|  | .env.* | ||||||
|  | !.env.example | ||||||
|  | vite.config.js.timestamp-* | ||||||
|  | vite.config.ts.timestamp-* | ||||||
							
								
								
									
										13
									
								
								.prettierignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.prettierignore
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | .DS_Store | ||||||
|  | node_modules | ||||||
|  | /build | ||||||
|  | /.svelte-kit | ||||||
|  | /package | ||||||
|  | .env | ||||||
|  | .env.* | ||||||
|  | !.env.example | ||||||
|  | 
 | ||||||
|  | # Ignore files for PNPM, NPM and YARN | ||||||
|  | pnpm-lock.yaml | ||||||
|  | package-lock.json | ||||||
|  | yarn.lock | ||||||
							
								
								
									
										9
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | { | ||||||
|  | 	"useTabs": true, | ||||||
|  | 	"singleQuote": true, | ||||||
|  | 	"trailingComma": "none", | ||||||
|  | 	"printWidth": 100, | ||||||
|  | 	"plugins": ["prettier-plugin-svelte"], | ||||||
|  | 	"pluginSearchDirs": ["."], | ||||||
|  | 	"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | # syntax=docker/dockerfile:1 | ||||||
|  | 
 | ||||||
|  | FROM node:latest | ||||||
|  | 
 | ||||||
|  | WORKDIR /app | ||||||
|  | ENV ENV prod | ||||||
|  | 
 | ||||||
|  | COPY package.json package-lock.json ./  | ||||||
|  | RUN npm ci | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | COPY . . | ||||||
|  | RUN npm run build | ||||||
|  | 
 | ||||||
|  | CMD [ "node", "./build/index.js"] | ||||||
							
								
								
									
										63
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | |||||||
|  | # Ollama Web UI 👋 | ||||||
|  | 
 | ||||||
|  | ChatGPT-Style Web Interface for Ollama 🦙 | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | ## Features ⭐ | ||||||
|  | 
 | ||||||
|  | - 🖥️ **Intuitive Interface**: Our chat interface takes inspiration from ChatGPT, ensuring a user-friendly experience. | ||||||
|  | - 📱 **Responsive Design**: Enjoy a seamless experience on both desktop and mobile devices. | ||||||
|  | - ⚡ **Swift Responsiveness**: Enjoy fast and responsive performance. | ||||||
|  | - 🚀 **Effortless Setup**: Install seamlessly using Docker for a hassle-free experience. | ||||||
|  | - 🤖 **Multiple Model Support**: Seamlessly switch between different chat models for diverse interactions. | ||||||
|  | - 🌟 **Continuous Updates**: We are committed to improving Ollama Web UI with regular updates and new features. | ||||||
|  | 
 | ||||||
|  | ## How to Install 🚀 | ||||||
|  | 
 | ||||||
|  | ### Using Docker 🐳 | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | docker build -t ollama-webui . | ||||||
|  | docker run -d -p 3000:3000 --name ollama-webui --restart always ollama-webui | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Your Ollama Web UI should now be hosted at [http://localhost:3000](http://localhost:3000). Enjoy! 😄 | ||||||
|  | 
 | ||||||
|  | ## What's Next? 🚀 | ||||||
|  | 
 | ||||||
|  | ### To-Do List 📝 | ||||||
|  | 
 | ||||||
|  | Here are some exciting tasks on our to-do list: | ||||||
|  | 
 | ||||||
|  | - 📜 **Chat History**: Effortlessly access and manage your conversation history. | ||||||
|  | - 📤📥 **Import/Export Chat History**: Seamlessly move your chat data in and out of the platform. | ||||||
|  | - 🎨 **Customization**: Tailor your chat environment with personalized themes and styles. | ||||||
|  | - 📥🗑️ **Download/Delete Models**: Easily acquire or remove models directly from the web UI. | ||||||
|  | - ⚙️ **Advanced Parameters Support**: Harness the power of advanced settings for fine-tuned control. | ||||||
|  | - 📚 **Enhanced Documentation**: Elevate your setup and customization experience with improved, comprehensive documentation. | ||||||
|  | - 🌟 **User Interface Enhancement**: Elevate the user interface to deliver a smoother, more enjoyable interaction. | ||||||
|  | - 📲🖥️ **Cross-Device Responsiveness**: Seamlessly transition between desktop and mobile for a consistent experience. | ||||||
|  | - 🚀 **Integration with Messaging Platforms**: Explore possibilities for integrating with popular messaging platforms like Slack and Discord. | ||||||
|  | - 🧐 **User Testing and Feedback Gathering**: Conduct thorough user testing to gather insights and refine our offerings based on valuable user feedback. | ||||||
|  | 
 | ||||||
|  | Feel free to contribute and help us make Ollama Web UI even better! 🙌 | ||||||
|  | 
 | ||||||
|  | ## Contributors ✨ | ||||||
|  | 
 | ||||||
|  | A big shoutout to our amazing contributors who have helped make this project possible! 🙏 | ||||||
|  | 
 | ||||||
|  | - [Jeffrey Morgan (Creator of Ollama)](https://github.com/jmorganca) | ||||||
|  | - [Timothy J. Baek](https://github.com/tjbck) | ||||||
|  | 
 | ||||||
|  | ## License 📜 | ||||||
|  | 
 | ||||||
|  | This project is licensed under the [MIT License](LICENSE) - see the [LICENSE](LICENSE) file for details. 📄 | ||||||
|  | 
 | ||||||
|  | ## Support 💬 | ||||||
|  | 
 | ||||||
|  | If you have any questions, suggestions, or need assistance, please open an issue or join our [Discord community](https://discord.gg/ollama) to connect with us! 🤝 | ||||||
|  | 
 | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | Let's make Ollama Web UI even more amazing together! 💪 | ||||||
							
								
								
									
										6258
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6258
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										39
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | { | ||||||
|  | 	"name": "ollama-webui", | ||||||
|  | 	"version": "0.0.1", | ||||||
|  | 	"private": true, | ||||||
|  | 	"scripts": { | ||||||
|  | 		"dev": "vite dev", | ||||||
|  | 		"build": "vite build", | ||||||
|  | 		"preview": "vite preview", | ||||||
|  | 		"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", | ||||||
|  | 		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", | ||||||
|  | 		"lint": "prettier --plugin-search-dir . --check . && eslint .", | ||||||
|  | 		"format": "prettier --plugin-search-dir . --write ." | ||||||
|  | 	}, | ||||||
|  | 	"devDependencies": { | ||||||
|  | 		"@sveltejs/adapter-auto": "^2.0.0", | ||||||
|  | 		"@sveltejs/kit": "^1.20.4", | ||||||
|  | 		"@typescript-eslint/eslint-plugin": "^6.0.0", | ||||||
|  | 		"@typescript-eslint/parser": "^6.0.0", | ||||||
|  | 		"autoprefixer": "^10.4.16", | ||||||
|  | 		"eslint": "^8.28.0", | ||||||
|  | 		"eslint-config-prettier": "^8.5.0", | ||||||
|  | 		"eslint-plugin-svelte": "^2.30.0", | ||||||
|  | 		"postcss": "^8.4.31", | ||||||
|  | 		"prettier": "^2.8.0", | ||||||
|  | 		"prettier-plugin-svelte": "^2.10.1", | ||||||
|  | 		"svelte": "^4.0.5", | ||||||
|  | 		"svelte-check": "^3.4.3", | ||||||
|  | 		"tailwindcss": "^3.3.3", | ||||||
|  | 		"tslib": "^2.4.1", | ||||||
|  | 		"typescript": "^5.0.0", | ||||||
|  | 		"vite": "^4.4.2" | ||||||
|  | 	}, | ||||||
|  | 	"type": "module", | ||||||
|  | 	"dependencies": { | ||||||
|  | 		"@sveltejs/adapter-node": "^1.3.1", | ||||||
|  | 		"marked": "^9.1.0", | ||||||
|  | 		"svelte-french-toast": "^1.2.0" | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								postcss.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								postcss.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | export default { | ||||||
|  |   plugins: { | ||||||
|  |     tailwindcss: {}, | ||||||
|  |     autoprefixer: {}, | ||||||
|  |   }, | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								run.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								run.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | docker stop ollama-webui || true | ||||||
|  | docker rm ollama-webui || true | ||||||
|  | docker build -t ollama-webui . | ||||||
|  | docker run -d -p 3000:3000 --name ollama-webui --restart always ollama-webui | ||||||
|  | docker image prune -f | ||||||
							
								
								
									
										41
									
								
								src/app.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/app.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | @font-face { | ||||||
|  | 	font-family: 'Arimo'; | ||||||
|  | 	src: url('/assets/fonts/Arimo-Variable.ttf'); | ||||||
|  | 	font-display: swap; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | html { | ||||||
|  | 	@apply bg-gray-800; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ::-webkit-scrollbar-thumb { | ||||||
|  | 	--tw-border-opacity: 1; | ||||||
|  | 	background-color: rgba(217, 217, 227, 0.8); | ||||||
|  | 	border-color: rgba(255, 255, 255, var(--tw-border-opacity)); | ||||||
|  | 	border-radius: 9999px; | ||||||
|  | 	border-width: 1px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ::-webkit-scrollbar { | ||||||
|  | 	height: 1rem; | ||||||
|  | 	width: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ::-webkit-scrollbar-track { | ||||||
|  | 	background-color: transparent; | ||||||
|  | 	border-radius: 9999px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | select { | ||||||
|  | 	background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E"); | ||||||
|  | 	background-position: right 0.5rem center; | ||||||
|  | 	background-repeat: no-repeat; | ||||||
|  | 	background-size: 1.5em 1.5em; | ||||||
|  | 	padding-right: 2.5rem; | ||||||
|  | 	-webkit-print-color-adjust: exact; | ||||||
|  | 	print-color-adjust: exact; | ||||||
|  | 	/* for Firefox */ | ||||||
|  | 	-moz-appearance: none; | ||||||
|  | 	/* for Chrome */ | ||||||
|  | 	-webkit-appearance: none; | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | // See https://kit.svelte.dev/docs/types#app
 | ||||||
|  | // for information about these interfaces
 | ||||||
|  | declare global { | ||||||
|  | 	namespace App { | ||||||
|  | 		// interface Error {}
 | ||||||
|  | 		// interface Locals {}
 | ||||||
|  | 		// interface PageData {}
 | ||||||
|  | 		// interface Platform {}
 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export {}; | ||||||
							
								
								
									
										12
									
								
								src/app.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/app.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | 	<head> | ||||||
|  | 		<meta charset="utf-8" /> | ||||||
|  | 		<link rel="icon" href="%sveltekit.assets%/favicon.png" /> | ||||||
|  | 		<meta name="viewport" content="width=device-width" /> | ||||||
|  | 		%sveltekit.head% | ||||||
|  | 	</head> | ||||||
|  | 	<body data-sveltekit-preload-data="hover"> | ||||||
|  | 		<div style="display: contents">%sveltekit.body%</div> | ||||||
|  | 	</body> | ||||||
|  | </html> | ||||||
							
								
								
									
										19
									
								
								src/lib/components/chat/SettingsModal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/lib/components/chat/SettingsModal.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | <script> | ||||||
|  | 	import Modal from '../common/Modal.svelte'; | ||||||
|  | 	export let show = false; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <Modal bind:show> | ||||||
|  | 	<div class="mt-3 p-3 rounded-lg bg-gray-900"> | ||||||
|  | 		<label for="models" class="block mb-2 text-sm font-medium text-gray-200">Select a model</label> | ||||||
|  | 		<select | ||||||
|  | 			id="models" | ||||||
|  | 			class="border border-gray-600 bg-gray-700 text-gray-200 text-sm rounded-lg block w-full p-2.5 placeholder-gray-400" | ||||||
|  | 		> | ||||||
|  | 			<option value="US">United States</option> | ||||||
|  | 			<option value="CA">Canada</option> | ||||||
|  | 			<option value="FR">France</option> | ||||||
|  | 			<option value="DE">Germany</option> | ||||||
|  | 		</select> | ||||||
|  | 	</div> | ||||||
|  | </Modal> | ||||||
							
								
								
									
										40
									
								
								src/lib/components/common/Modal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/lib/components/common/Modal.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { onMount } from 'svelte'; | ||||||
|  | 	import { fade, blur } from 'svelte/transition'; | ||||||
|  | 
 | ||||||
|  | 	export let show = true; | ||||||
|  | 	let mounted = false; | ||||||
|  | 
 | ||||||
|  | 	onMount(() => { | ||||||
|  | 		mounted = true; | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	$: if (mounted) { | ||||||
|  | 		if (show) { | ||||||
|  | 			document.body.style.overflow = 'hidden'; | ||||||
|  | 		} else { | ||||||
|  | 			document.body.style.overflow = 'unset'; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | {#if show} | ||||||
|  | 	<!-- svelte-ignore a11y-click-events-have-key-events --> | ||||||
|  | 	<!-- svelte-ignore a11y-no-static-element-interactions --> | ||||||
|  | 	<div | ||||||
|  | 		class="fixed top-0 right-0 left-0 bottom-0 bg-stone-900/50 w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain" | ||||||
|  | 		on:click={() => { | ||||||
|  | 			show = false; | ||||||
|  | 		}} | ||||||
|  | 	> | ||||||
|  | 		<div | ||||||
|  | 			class="m-auto min-h-52 max-w-full w-[30rem] bg-stone-800 rounded-lg p-5 mx-3 shadow-3xl" | ||||||
|  | 			transition:fade={{ delay: 100, duration: 200 }} | ||||||
|  | 			on:click={(e) => { | ||||||
|  | 				e.stopPropagation(); | ||||||
|  | 			}} | ||||||
|  | 		> | ||||||
|  | 			<slot /> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | {/if} | ||||||
							
								
								
									
										33
									
								
								src/lib/components/common/Overlay.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/lib/components/common/Overlay.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | <script> | ||||||
|  | 	import Spinner from './Spinner.svelte'; | ||||||
|  | 
 | ||||||
|  | 	export let show = false; | ||||||
|  | 	export let content = ''; | ||||||
|  | 
 | ||||||
|  | 	export let opacity = 1; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <div class="relative"> | ||||||
|  | 	{#if show} | ||||||
|  | 		<div class="absolute w-full h-full flex"> | ||||||
|  | 			<div | ||||||
|  | 				class="absolute rounded" | ||||||
|  | 				style="inset: -10px; opacity: {opacity}; backdrop-filter: blur(5px);" | ||||||
|  | 			/> | ||||||
|  | 
 | ||||||
|  | 			<div class="flex w-full flex-col justify-center"> | ||||||
|  | 				<div class=" py-3"> | ||||||
|  | 					<Spinner className="ml-2" /> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				{#if content !== ''} | ||||||
|  | 					<div class="text-center text-gray-100 text-xs font-medium z-50"> | ||||||
|  | 						{content} | ||||||
|  | 					</div> | ||||||
|  | 				{/if} | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	{/if} | ||||||
|  | 
 | ||||||
|  | 	<slot /> | ||||||
|  | </div> | ||||||
							
								
								
									
										24
									
								
								src/lib/components/common/Spinner.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/lib/components/common/Spinner.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	export let className: string = 'text-white'; | ||||||
|  | 	export let theme: 'blue' | 'white' | 'black' = 'white'; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <div class="flex justify-center text-center {className}"> | ||||||
|  | 	<svg | ||||||
|  | 		class="animate-spin -ml-1 mr-3 h-5 w-5 {theme === 'blue' | ||||||
|  | 			? 'text-sky-600' | ||||||
|  | 			: theme === 'white' | ||||||
|  | 			? 'text-white' | ||||||
|  | 			: 'text-gray-600'} " | ||||||
|  | 		xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 		fill="none" | ||||||
|  | 		viewBox="0 0 24 24" | ||||||
|  | 	> | ||||||
|  | 		<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" /> | ||||||
|  | 		<path | ||||||
|  | 			class="opacity-75" | ||||||
|  | 			fill="currentColor" | ||||||
|  | 			d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" | ||||||
|  | 		/> | ||||||
|  | 	</svg> | ||||||
|  | </div> | ||||||
							
								
								
									
										243
									
								
								src/lib/components/layout/Navbar.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								src/lib/components/layout/Navbar.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,243 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	let show = false; | ||||||
|  | 	let navElement; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <div | ||||||
|  | 	class=" fixed top-0 flex flex-row justify-center bg-stone-100/5 text-gray-200 backdrop-blur-xl w-full z-30" | ||||||
|  | > | ||||||
|  | 	<div class="basis-full px-5"> | ||||||
|  | 		<nav class="py-3" id="nav"> | ||||||
|  | 			<div class="flex flex-row justify-between"> | ||||||
|  | 				<div class="pl-2"> | ||||||
|  | 					<button | ||||||
|  | 						class=" cursor-pointer p-1 flex hover:bg-gray-700 rounded-lg transition" | ||||||
|  | 						on:click={() => { | ||||||
|  | 							show = !show; | ||||||
|  | 						}} | ||||||
|  | 					> | ||||||
|  | 						<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" | ||||||
|  | 							> | ||||||
|  | 								<path | ||||||
|  | 									fill-rule="evenodd" | ||||||
|  | 									d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10zm0 5.25a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75z" | ||||||
|  | 									clip-rule="evenodd" | ||||||
|  | 								/> | ||||||
|  | 							</svg> | ||||||
|  | 						</div> | ||||||
|  | 					</button> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div class=" self-center">Ollama Web UI</div> | ||||||
|  | 
 | ||||||
|  | 				<div class="pr-2"> | ||||||
|  | 					<button | ||||||
|  | 						class=" cursor-pointer p-1 flex hover:bg-gray-700 rounded-lg transition" | ||||||
|  | 						on:click={() => { | ||||||
|  | 							location.href = location.href; | ||||||
|  | 							console.log('new chat'); | ||||||
|  | 						}} | ||||||
|  | 					> | ||||||
|  | 						<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" | ||||||
|  | 							> | ||||||
|  | 								<path | ||||||
|  | 									d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" | ||||||
|  | 								/> | ||||||
|  | 							</svg> | ||||||
|  | 						</div> | ||||||
|  | 					</button> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		</nav> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div | ||||||
|  | 	bind:this={navElement} | ||||||
|  | 	class="h-screen {show | ||||||
|  | 		? '' | ||||||
|  | 		: '-translate-x-72'} w-72 fixed top-0 left-0 z-40 transition bg-gray-900 text-gray-200 shadow-2xl text-sm | ||||||
|  |         " | ||||||
|  | > | ||||||
|  | 	<div class="p-2.5 my-auto flex flex-col justify-between h-screen"> | ||||||
|  | 		<div class=" flex justify-center space-x-2"> | ||||||
|  | 			<button | ||||||
|  | 				class=" cursor-pointer flex-grow rounded-md border border-gray-600 p-3 flex" | ||||||
|  | 				on:click={() => { | ||||||
|  | 					location.href = location.href; | ||||||
|  | 					console.log('new chat'); | ||||||
|  | 				}} | ||||||
|  | 			> | ||||||
|  | 				<div class="self-center mr-2"> | ||||||
|  | 					<svg | ||||||
|  | 						xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 						viewBox="0 0 20 20" | ||||||
|  | 						fill="currentColor" | ||||||
|  | 						class="w-5 h-5" | ||||||
|  | 					> | ||||||
|  | 						<path | ||||||
|  | 							d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" | ||||||
|  | 						/> | ||||||
|  | 					</svg> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div class=" self-center">New Chat</div> | ||||||
|  | 			</button> | ||||||
|  | 
 | ||||||
|  | 			<button | ||||||
|  | 				class=" cursor-pointer w-12 rounded-md border border-gray-600 flex" | ||||||
|  | 				on:click={() => { | ||||||
|  | 					show = !show; | ||||||
|  | 				}} | ||||||
|  | 			> | ||||||
|  | 				<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" | ||||||
|  | 					> | ||||||
|  | 						<path | ||||||
|  | 							fill-rule="evenodd" | ||||||
|  | 							d="M3 4.25A2.25 2.25 0 015.25 2h5.5A2.25 2.25 0 0113 4.25v2a.75.75 0 01-1.5 0v-2a.75.75 0 00-.75-.75h-5.5a.75.75 0 00-.75.75v11.5c0 .414.336.75.75.75h5.5a.75.75 0 00.75-.75v-2a.75.75 0 011.5 0v2A2.25 2.25 0 0110.75 18h-5.5A2.25 2.25 0 013 15.75V4.25z" | ||||||
|  | 							clip-rule="evenodd" | ||||||
|  | 						/> | ||||||
|  | 						<path | ||||||
|  | 							fill-rule="evenodd" | ||||||
|  | 							d="M19 10a.75.75 0 00-.75-.75H8.704l1.048-.943a.75.75 0 10-1.004-1.114l-2.5 2.25a.75.75 0 000 1.114l2.5 2.25a.75.75 0 101.004-1.114l-1.048-.943h9.546A.75.75 0 0019 10z" | ||||||
|  | 							clip-rule="evenodd" | ||||||
|  | 						/> | ||||||
|  | 					</svg> | ||||||
|  | 				</div> | ||||||
|  | 			</button> | ||||||
|  | 		</div> | ||||||
|  | 
 | ||||||
|  | 		<div class="my-3 flex flex-col space-y-1 overflow-y-scroll"> | ||||||
|  | 			<button class=" flex rounded-md p-4 hover:bg-gray-800 transition"> | ||||||
|  | 				<div class=" self-center mr-3"> | ||||||
|  | 					<svg | ||||||
|  | 						xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 						fill="none" | ||||||
|  | 						viewBox="0 0 24 24" | ||||||
|  | 						stroke-width="1.5" | ||||||
|  | 						stroke="currentColor" | ||||||
|  | 						class="w-4 h-4" | ||||||
|  | 					> | ||||||
|  | 						<path | ||||||
|  | 							stroke-linecap="round" | ||||||
|  | 							stroke-linejoin="round" | ||||||
|  | 							d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 011.037-.443 48.282 48.282 0 005.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" | ||||||
|  | 						/> | ||||||
|  | 					</svg> | ||||||
|  | 				</div> | ||||||
|  | 				<div class=" self-center"> | ||||||
|  | 					We're currently working on bringing you the ability to access your chat history. Stay | ||||||
|  | 					tuned for updates, and thank you for your patience! | ||||||
|  | 				</div> | ||||||
|  | 			</button> | ||||||
|  | 			<!-- {#each Array(100) as a, i} | ||||||
|  | 				<button | ||||||
|  | 					class=" flex rounded-md p-4 hover:bg-gray-800 transition whitespace-nowrap text-ellipsis" | ||||||
|  | 				> | ||||||
|  | 					<div class=" self-center mr-3"> | ||||||
|  | 						<svg | ||||||
|  | 							xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 							fill="none" | ||||||
|  | 							viewBox="0 0 24 24" | ||||||
|  | 							stroke-width="1.5" | ||||||
|  | 							stroke="currentColor" | ||||||
|  | 							class="w-4 h-4" | ||||||
|  | 						> | ||||||
|  | 							<path | ||||||
|  | 								stroke-linecap="round" | ||||||
|  | 								stroke-linejoin="round" | ||||||
|  | 								d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 011.037-.443 48.282 48.282 0 005.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" | ||||||
|  | 							/> | ||||||
|  | 						</svg> | ||||||
|  | 					</div> | ||||||
|  | 					<div class=" self-center overflow-hidden">{i} Chat History</div> | ||||||
|  | 				</button> | ||||||
|  | 			{/each} --> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <!-- <div | ||||||
|  | 	class="h-screen fixed top-0 left-0 z-30 text-sm | ||||||
|  | 	" | ||||||
|  | > | ||||||
|  | 	<div class="pl-2 pt-2"> | ||||||
|  | 		<button | ||||||
|  | 			class=" cursor-pointer p-3 flex hover:bg-gray-700 rounded-lg transition" | ||||||
|  | 			on:click={() => { | ||||||
|  | 				show = !show; | ||||||
|  | 			}} | ||||||
|  | 		> | ||||||
|  | 			<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" | ||||||
|  | 				> | ||||||
|  | 					<path | ||||||
|  | 						fill-rule="evenodd" | ||||||
|  | 						d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10zm0 5.25a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75z" | ||||||
|  | 						clip-rule="evenodd" | ||||||
|  | 					/> | ||||||
|  | 				</svg> | ||||||
|  | 			</div> | ||||||
|  | 		</button> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  |  --> | ||||||
|  | 
 | ||||||
|  | <!--  | ||||||
|  | 
 | ||||||
|  | <div class="h-screen fixed top-0 right-0 z-30 text-sm"> | ||||||
|  | 	<div class="pr-2 pt-2"> | ||||||
|  | 		<button | ||||||
|  | 			class=" cursor-pointer p-3 flex hover:bg-gray-700 rounded-lg transition" | ||||||
|  | 			on:click={() => { | ||||||
|  | 				chatHistory = {}; | ||||||
|  | 			}} | ||||||
|  | 		> | ||||||
|  | 			<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" | ||||||
|  | 					> | ||||||
|  | 						<path | ||||||
|  | 							fill-rule="evenodd" | ||||||
|  | 							d="M8.34 1.804A1 1 0 019.32 1h1.36a1 1 0 01.98.804l.295 1.473c.497.144.971.342 1.416.587l1.25-.834a1 1 0 011.262.125l.962.962a1 1 0 01.125 1.262l-.834 1.25c.245.445.443.919.587 1.416l1.473.294a1 1 0 01.804.98v1.361a1 1 0 01-.804.98l-1.473.295a6.95 6.95 0 01-.587 1.416l.834 1.25a1 1 0 01-.125 1.262l-.962.962a1 1 0 01-1.262.125l-1.25-.834a6.953 6.953 0 01-1.416.587l-.294 1.473a1 1 0 01-.98.804H9.32a1 1 0 01-.98-.804l-.295-1.473a6.957 6.957 0 01-1.416-.587l-1.25.834a1 1 0 01-1.262-.125l-.962-.962a1 1 0 01-.125-1.262l.834-1.25a6.957 6.957 0 01-.587-1.416l-1.473-.294A1 1 0 011 10.68V9.32a1 1 0 01.804-.98l1.473-.295c.144-.497.342-.971.587-1.416l-.834-1.25a1 1 0 01.125-1.262l.962-.962A1 1 0 015.38 3.03l1.25.834a6.957 6.957 0 011.416-.587l.294-1.473zM13 10a3 3 0 11-6 0 3 3 0 016 0z" | ||||||
|  | 							clip-rule="evenodd" | ||||||
|  | 						/> | ||||||
|  | 					</svg> | ||||||
|  | 
 | ||||||
|  | 				<svg | ||||||
|  | 					xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 					viewBox="0 0 20 20" | ||||||
|  | 					fill="currentColor" | ||||||
|  | 					class="w-5 h-5" | ||||||
|  | 				> | ||||||
|  | 					<path | ||||||
|  | 						d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" | ||||||
|  | 					/> | ||||||
|  | 				</svg> | ||||||
|  | 			</div> | ||||||
|  | 		</button> | ||||||
|  | 	</div> | ||||||
|  | </div> --> | ||||||
							
								
								
									
										7
									
								
								src/lib/contants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/lib/contants.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | import { browser, dev } from '$app/environment'; | ||||||
|  | 
 | ||||||
|  | export const ENDPOINT = dev | ||||||
|  | 	? 'http://127.0.0.1:11434' | ||||||
|  | 	: browser | ||||||
|  | 	? 'http://127.0.0.1:11434' | ||||||
|  | 	: 'http://host.docker.internal:11434'; | ||||||
							
								
								
									
										1
									
								
								src/lib/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/lib/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | // place files you want to import through the `$lib` alias in this folder.
 | ||||||
							
								
								
									
										14
									
								
								src/routes/+layout.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/routes/+layout.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | <script> | ||||||
|  | 	import { Toaster } from 'svelte-french-toast'; | ||||||
|  | 
 | ||||||
|  | 	import '../app.css'; | ||||||
|  | 
 | ||||||
|  | 	import '../tailwind.css'; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <svelte:head> | ||||||
|  | 	<title>Ollama</title> | ||||||
|  | </svelte:head> | ||||||
|  | 
 | ||||||
|  | <slot /> | ||||||
|  | <Toaster /> | ||||||
							
								
								
									
										24
									
								
								src/routes/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/routes/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | import { ENDPOINT } from '$lib/contants'; | ||||||
|  | import type { PageServerLoad } from './$types'; | ||||||
|  | 
 | ||||||
|  | export const load: PageServerLoad = async ({ url, fetch }) => { | ||||||
|  | 	const models = await fetch(`${ENDPOINT}/api/tags`, { | ||||||
|  | 		method: 'GET', | ||||||
|  | 		headers: { | ||||||
|  | 			Accept: 'application/json', | ||||||
|  | 			'Content-Type': 'application/json' | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 		.then(async (res) => { | ||||||
|  | 			if (!res.ok) throw await res.json(); | ||||||
|  | 			return res.json(); | ||||||
|  | 		}) | ||||||
|  | 		.catch((error) => { | ||||||
|  | 			console.log(error); | ||||||
|  | 			return null; | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 	return { | ||||||
|  | 		models: models | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
							
								
								
									
										261
									
								
								src/routes/+page.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								src/routes/+page.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,261 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import toast from 'svelte-french-toast'; | ||||||
|  | 	import Navbar from '$lib/components/layout/Navbar.svelte'; | ||||||
|  | 
 | ||||||
|  | 	import { marked } from 'marked'; | ||||||
|  | 
 | ||||||
|  | 	import type { PageData } from './$types'; | ||||||
|  | 	import { ENDPOINT } from '$lib/contants'; | ||||||
|  | 
 | ||||||
|  | 	export let data: PageData; | ||||||
|  | 	$: ({ models } = data); | ||||||
|  | 
 | ||||||
|  | 	let selectedModel = ''; | ||||||
|  | 	let prompt = ''; | ||||||
|  | 	let context = ''; | ||||||
|  | 
 | ||||||
|  | 	let chatHistory = {}; | ||||||
|  | 
 | ||||||
|  | 	let textareaElement = ''; | ||||||
|  | 
 | ||||||
|  | 	const submitPrompt = async () => { | ||||||
|  | 		console.log('submitPrompt'); | ||||||
|  | 		if (selectedModel !== '') { | ||||||
|  | 			console.log(prompt); | ||||||
|  | 
 | ||||||
|  | 			let user_prompt = prompt; | ||||||
|  | 			chatHistory[Object.keys(chatHistory).length] = { | ||||||
|  | 				role: 'user', | ||||||
|  | 				content: user_prompt | ||||||
|  | 			}; | ||||||
|  | 			prompt = ''; | ||||||
|  | 			textareaElement.style.height = ''; | ||||||
|  | 
 | ||||||
|  | 			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 | ||||||
|  | 				}) | ||||||
|  | 			}); | ||||||
|  | 
 | ||||||
|  | 			chatHistory[Object.keys(chatHistory).length] = { | ||||||
|  | 				role: 'assistant', | ||||||
|  | 				content: '' | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
|  | 			const reader = res.body.pipeThrough(new TextDecoderStream()).getReader(); | ||||||
|  | 			while (true) { | ||||||
|  | 				const { value, done } = await reader.read(); | ||||||
|  | 				if (done) break; | ||||||
|  | 
 | ||||||
|  | 				// toast.success(value); | ||||||
|  | 				try { | ||||||
|  | 					let data = JSON.parse(value); | ||||||
|  | 					console.log(data); | ||||||
|  | 
 | ||||||
|  | 					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(0, document.body.scrollHeight); | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			toast.error('Model not selected'); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	const copyToClipboard = (text) => { | ||||||
|  | 		if (!navigator.clipboard) { | ||||||
|  | 			var textArea = document.createElement('textarea'); | ||||||
|  | 			textArea.value = text; | ||||||
|  | 
 | ||||||
|  | 			// Avoid scrolling to bottom | ||||||
|  | 			textArea.style.top = '0'; | ||||||
|  | 			textArea.style.left = '0'; | ||||||
|  | 			textArea.style.position = 'fixed'; | ||||||
|  | 
 | ||||||
|  | 			document.body.appendChild(textArea); | ||||||
|  | 			textArea.focus(); | ||||||
|  | 			textArea.select(); | ||||||
|  | 
 | ||||||
|  | 			try { | ||||||
|  | 				var successful = document.execCommand('copy'); | ||||||
|  | 				var msg = successful ? 'successful' : 'unsuccessful'; | ||||||
|  | 				console.log('Fallback: Copying text command was ' + msg); | ||||||
|  | 			} catch (err) { | ||||||
|  | 				console.error('Fallback: Oops, unable to copy', err); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			document.body.removeChild(textArea); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		navigator.clipboard.writeText(text).then( | ||||||
|  | 			function () { | ||||||
|  | 				console.log('Async: Copying to clipboard was successful!'); | ||||||
|  | 				toast.success('Copying to clipboard was successful!'); | ||||||
|  | 			}, | ||||||
|  | 			function (err) { | ||||||
|  | 				console.error('Async: Could not copy text: ', err); | ||||||
|  | 			} | ||||||
|  | 		); | ||||||
|  | 	}; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <div class="app text-gray-100"> | ||||||
|  | 	<div class=" bg-gray-800 min-h-screen overflow-auto flex flex-row"> | ||||||
|  | 		<Navbar /> | ||||||
|  | 
 | ||||||
|  | 		<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-2.5 mt-14"> | ||||||
|  | 					<div class="p-3 rounded-lg bg-gray-900"> | ||||||
|  | 						<div> | ||||||
|  | 							<label for="models" class="block mb-2 text-sm font-medium text-gray-200">Model</label> | ||||||
|  | 							<select | ||||||
|  | 								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" | ||||||
|  | 								bind:value={selectedModel} | ||||||
|  | 								disabled={Object.keys(chatHistory).length != 0} | ||||||
|  | 							> | ||||||
|  | 								<option value="" selected>Select a model</option> | ||||||
|  | 
 | ||||||
|  | 								{#each models.models as model} | ||||||
|  | 									<option value={model.name}>{model.name}</option> | ||||||
|  | 								{/each} | ||||||
|  | 							</select> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div class=" h-full mb-32 w-full flex flex-col"> | ||||||
|  | 					{#if Object.keys(chatHistory).length == 0} | ||||||
|  | 						<div class="m-auto text-4xl text-gray-600 font-bold text-center">Ollama</div> | ||||||
|  | 					{:else} | ||||||
|  | 						{#each Object.keys(chatHistory) as messageIdx} | ||||||
|  | 							<div class=" w-full {chatHistory[messageIdx].role == 'user' ? '' : ' bg-gray-700'}"> | ||||||
|  | 								<div class="flex justify-between p-5 py-10 max-w-3xl mx-auto rounded-lg"> | ||||||
|  | 									<div class="space-x-7 flex"> | ||||||
|  | 										<div class=""> | ||||||
|  | 											<img | ||||||
|  | 												src="/{chatHistory[messageIdx].role == 'user' ? 'user' : 'favicon'}.png" | ||||||
|  | 												class=" max-w-[32px] object-cover rounded" | ||||||
|  | 											/> | ||||||
|  | 										</div> | ||||||
|  | 
 | ||||||
|  | 										<div class="whitespace-pre-line"> | ||||||
|  | 											{@html marked.parse(chatHistory[messageIdx].content)} | ||||||
|  | 											<!-- {} --> | ||||||
|  | 										</div> | ||||||
|  | 									</div> | ||||||
|  | 
 | ||||||
|  | 									<div> | ||||||
|  | 										{#if chatHistory[messageIdx].role != 'user' && chatHistory[messageIdx].done} | ||||||
|  | 											<button | ||||||
|  | 												class="p-1 rounded hover:bg-gray-700 transition" | ||||||
|  | 												on:click={() => { | ||||||
|  | 													copyToClipboard(chatHistory[messageIdx].content); | ||||||
|  | 												}} | ||||||
|  | 											> | ||||||
|  | 												<svg | ||||||
|  | 													xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 													fill="none" | ||||||
|  | 													viewBox="0 0 24 24" | ||||||
|  | 													stroke-width="1.5" | ||||||
|  | 													stroke="currentColor" | ||||||
|  | 													class="w-4 h-4" | ||||||
|  | 												> | ||||||
|  | 													<path | ||||||
|  | 														stroke-linecap="round" | ||||||
|  | 														stroke-linejoin="round" | ||||||
|  | 														d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" | ||||||
|  | 													/> | ||||||
|  | 												</svg> | ||||||
|  | 											</button> | ||||||
|  | 										{/if} | ||||||
|  | 									</div> | ||||||
|  | 								</div> | ||||||
|  | 							</div> | ||||||
|  | 						{/each} | ||||||
|  | 					{/if} | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 
 | ||||||
|  | 			<div class="fixed bottom-0 w-full"> | ||||||
|  | 				<!-- <hr class=" mb-3 border-gray-600" /> --> | ||||||
|  | 
 | ||||||
|  | 				<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"> | ||||||
|  | 						<form class=" flex shadow-sm relative w-full" on:submit|preventDefault={submitPrompt}> | ||||||
|  | 							<textarea | ||||||
|  | 								class="rounded-xl bg-gray-700 outline-none w-full py-3 px-5 pr-12 resize-none" | ||||||
|  | 								placeholder="Send a message" | ||||||
|  | 								bind:this={textareaElement} | ||||||
|  | 								bind:value={prompt} | ||||||
|  | 								on:keypress={(e) => { | ||||||
|  | 									if (e.keyCode == 13 && !e.shiftKey) { | ||||||
|  | 										e.preventDefault(); | ||||||
|  | 									} | ||||||
|  | 									if (prompt !== '' && e.keyCode == 13 && !e.shiftKey) { | ||||||
|  | 										submitPrompt(); | ||||||
|  | 									} | ||||||
|  | 								}} | ||||||
|  | 								rows="1" | ||||||
|  | 								on:input={() => { | ||||||
|  | 									textareaElement.style.height = ''; | ||||||
|  | 									textareaElement.style.height = Math.min(textareaElement.scrollHeight, 200) + 'px'; | ||||||
|  | 								}} | ||||||
|  | 							/> | ||||||
|  | 							<div class=" absolute right-0 bottom-0"> | ||||||
|  | 								<div class="pr-3 pb-2"> | ||||||
|  | 									<button | ||||||
|  | 										class="{prompt !== '' | ||||||
|  | 											? 'bg-emerald-600 text-gray-100 hover:bg-emerald-700 ' | ||||||
|  | 											: 'text-gray-600 disabled'} transition rounded p-2" | ||||||
|  | 										type="submit" | ||||||
|  | 									> | ||||||
|  | 										<svg | ||||||
|  | 											xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 											viewBox="0 0 16 16" | ||||||
|  | 											fill="none" | ||||||
|  | 											class="w-4 h-4" | ||||||
|  | 											><path | ||||||
|  | 												d="M.5 1.163A1 1 0 0 1 1.97.28l12.868 6.837a1 1 0 0 1 0 1.766L1.969 15.72A1 1 0 0 1 .5 14.836V10.33a1 1 0 0 1 .816-.983L8.5 8 1.316 6.653A1 1 0 0 1 .5 5.67V1.163Z" | ||||||
|  | 												fill="currentColor" | ||||||
|  | 											/></svg | ||||||
|  | 										> | ||||||
|  | 									</button> | ||||||
|  | 								</div> | ||||||
|  | 							</div> | ||||||
|  | 						</form> | ||||||
|  | 
 | ||||||
|  | 						<div class="mt-2.5 text-xs text-gray-500 text-center"> | ||||||
|  | 							LLM models may produce inaccurate information about people, places, or facts. | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 
 | ||||||
|  | 		<!-- <main class="w-full flex justify-center"> | ||||||
|  | 			<div class="max-w-lg w-screen p-5" /> | ||||||
|  | 		</main> --> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
							
								
								
									
										18
									
								
								src/tailwind.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/tailwind.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | @tailwind base; | ||||||
|  | @tailwind components; | ||||||
|  | @tailwind utilities; | ||||||
|  | 
 | ||||||
|  | @layer base { | ||||||
|  | 	html { | ||||||
|  | 		font-family: 'Arimo', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, | ||||||
|  | 			Cantarell, 'Noto Sans', sans-serif, 'Helvetica Neue', Arial, 'Apple Color Emoji', | ||||||
|  | 			'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pre { | ||||||
|  | 		font-family: 'Arimo', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, | ||||||
|  | 			Cantarell, 'Noto Sans', sans-serif, 'Helvetica Neue', Arial, 'Apple Color Emoji', | ||||||
|  | 			'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; | ||||||
|  | 		white-space: pre-wrap; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								static/assets/fonts/Arimo-Variable.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/assets/fonts/Arimo-Variable.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								static/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/favicon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 90 KiB | 
							
								
								
									
										
											BIN
										
									
								
								static/user.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/user.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.9 KiB | 
							
								
								
									
										18
									
								
								svelte.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								svelte.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | import adapter from '@sveltejs/adapter-node'; | ||||||
|  | import { vitePreprocess } from '@sveltejs/kit/vite'; | ||||||
|  | 
 | ||||||
|  | /** @type {import('@sveltejs/kit').Config} */ | ||||||
|  | const config = { | ||||||
|  | 	// Consult https://kit.svelte.dev/docs/integrations#preprocessors
 | ||||||
|  | 	// for more information about preprocessors
 | ||||||
|  | 	preprocess: vitePreprocess(), | ||||||
|  | 
 | ||||||
|  | 	kit: { | ||||||
|  | 		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
 | ||||||
|  | 		// If your environment is not supported or you settled on a specific environment, switch out the adapter.
 | ||||||
|  | 		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
 | ||||||
|  | 		adapter: adapter() | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default config; | ||||||
							
								
								
									
										24
									
								
								tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | /** @type {import('tailwindcss').Config} */ | ||||||
|  | export default { | ||||||
|  | 	content: ['./src/**/*.{html,js,svelte,ts}'], | ||||||
|  | 	theme: { | ||||||
|  | 		extend: { | ||||||
|  | 			colors: { | ||||||
|  | 				gray: { | ||||||
|  | 					50: '#f7f7f8', | ||||||
|  | 					100: '#ececf1', | ||||||
|  | 					200: '#d9d9e3', | ||||||
|  | 					300: '#c5c5d2', | ||||||
|  | 					400: '#acacbe', | ||||||
|  | 					500: '#8e8ea0', | ||||||
|  | 					600: '#565869', | ||||||
|  | 					700: '#40414f', | ||||||
|  | 					800: '#343541', | ||||||
|  | 					900: '#202123', | ||||||
|  | 					950: '#050509' | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 	plugins: [] | ||||||
|  | }; | ||||||
							
								
								
									
										17
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | { | ||||||
|  | 	"extends": "./.svelte-kit/tsconfig.json", | ||||||
|  | 	"compilerOptions": { | ||||||
|  | 		"allowJs": true, | ||||||
|  | 		"checkJs": true, | ||||||
|  | 		"esModuleInterop": true, | ||||||
|  | 		"forceConsistentCasingInFileNames": true, | ||||||
|  | 		"resolveJsonModule": true, | ||||||
|  | 		"skipLibCheck": true, | ||||||
|  | 		"sourceMap": true, | ||||||
|  | 		"strict": true | ||||||
|  | 	} | ||||||
|  | 	// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias | ||||||
|  | 	// | ||||||
|  | 	// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes | ||||||
|  | 	// from the referenced tsconfig.json - TypeScript does not merge them in | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								vite.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								vite.config.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | import { sveltekit } from '@sveltejs/kit/vite'; | ||||||
|  | import { defineConfig } from 'vite'; | ||||||
|  | 
 | ||||||
|  | export default defineConfig({ | ||||||
|  | 	plugins: [sveltekit()] | ||||||
|  | }); | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user