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