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
							
								
									8e0166e6a3
								
							
						
					
					
						commit
						9a3a5716fa
					
				
							
								
								
									
										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