Merge pull request #10 from coolaj86/feat-static

ref!: enable static builds
This commit is contained in:
Timothy Jaeryang Baek 2023-10-22 14:43:42 -05:00 committed by GitHub
commit f2bdbfaa6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 861 additions and 83 deletions

View file

@ -6,6 +6,8 @@ on:
jobs: jobs:
build: build:
name: 'Fmt, Lint, & Build' name: 'Fmt, Lint, & Build'
env:
PUBLIC_API_BASE_URL: ''
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:

64
Caddyfile.localhost Normal file
View file

@ -0,0 +1,64 @@
# Run with
# caddy run --envfile ./example.env --config ./Caddyfile.localhost
#
# This is configured for
# - Automatic HTTPS (even for localhost)
# - Reverse Proxying to Ollama API Base URL (http://localhost:11434/api)
# - CORS
# - HTTP Basic Auth API Tokens (uncomment basicauth section)
# CORS Preflight (OPTIONS) + Request (GET, POST, PATCH, PUT, DELETE)
(cors-api) {
@match-cors-api-preflight method OPTIONS
handle @match-cors-api-preflight {
header {
Access-Control-Allow-Origin "{http.request.header.origin}"
Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
Access-Control-Allow-Headers "Origin, Accept, Authorization, Content-Type, X-Requested-With"
Access-Control-Allow-Credentials "true"
Access-Control-Max-Age "3600"
defer
}
respond "" 204
}
@match-cors-api-request {
not {
header Origin "{http.request.scheme}://{http.request.host}"
}
header Origin "{http.request.header.origin}"
}
handle @match-cors-api-request {
header {
Access-Control-Allow-Origin "{http.request.header.origin}"
Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
Access-Control-Allow-Headers "Origin, Accept, Authorization, Content-Type, X-Requested-With"
Access-Control-Allow-Credentials "true"
Access-Control-Max-Age "3600"
defer
}
}
}
# replace localhost with example.com or whatever
localhost {
## HTTP Basic Auth
## (uncomment to enable)
# basicauth {
# # see .example.env for how to generate tokens
# {env.OLLAMA_API_ID} {env.OLLAMA_API_TOKEN_DIGEST}
# }
handle /api/* {
# Comment to disable CORS
import cors-api
reverse_proxy localhost:11434
}
# Same-Origin Static Web Server
file_server {
root ./build/
}
}

View file

@ -1,15 +1,20 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM node:latest FROM node:latest
WORKDIR /app WORKDIR /app
ARG OLLAMA_API_BASE_URL=''
RUN echo $OLLAMA_API_BASE_URL
ENV ENV prod ENV ENV prod
ENV PUBLIC_API_BASE_URL $OLLAMA_API_BASE_URL
RUN echo $PUBLIC_API_BASE_URL
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm ci RUN npm ci
COPY . . COPY . .
RUN npm run build RUN npm run build
CMD [ "node", "./build/index.js"] CMD [ "npm", "run", "start"]

View file

@ -7,15 +7,25 @@ ChatGPT-Style Web Interface for Ollama 🦙
## Features ⭐ ## Features ⭐
- 🖥️ **Intuitive Interface**: Our chat interface takes inspiration from ChatGPT, ensuring a user-friendly experience. - 🖥️ **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. - 📱 **Responsive Design**: Enjoy a seamless experience on both desktop and mobile devices.
- ⚡ **Swift Responsiveness**: Enjoy fast and responsive performance. - ⚡ **Swift Responsiveness**: Enjoy fast and responsive performance.
- 🚀 **Effortless Setup**: Install seamlessly using Docker for a hassle-free experience. - 🚀 **Effortless Setup**: Install seamlessly using Docker for a hassle-free experience.
- 🤖 **Multiple Model Support**: Seamlessly switch between different chat models for diverse interactions. - 🤖 **Multiple Model Support**: Seamlessly switch between different chat models for diverse interactions.
- 📜 **Chat History**: Effortlessly access and manage your conversation history. - 📜 **Chat History**: Effortlessly access and manage your conversation history.
- 📤📥 **Import/Export Chat History**: Seamlessly move your chat data in and out of the platform. - 📤📥 **Import/Export Chat History**: Seamlessly move your chat data in and out of the platform.
- ⚙️ **Fine-Tuned Control with Advanced Parameters**: Gain a deeper level of control by adjusting parameters such as temperature and defining your system prompts to tailor the conversation to your specific preferences and needs. - ⚙️ **Fine-Tuned Control with Advanced Parameters**: Gain a deeper level of control by adjusting parameters such as temperature and defining your system prompts to tailor the conversation to your specific preferences and needs.
- 💻 **Code Syntax Highlighting**: Enjoy enhanced code readability with our syntax highlighting feature. - 💻 **Code Syntax Highlighting**: Enjoy enhanced code readability with our syntax highlighting feature.
- 🔗 **External Ollama Server Connection**: Link to the model when Ollama is hosted on a different server via the environment variable -e OLLAMA_ENDPOINT="http://[insert your Ollama address]".
- 🔗 **External Ollama Server Connection**: You can seamlessly connect to an external Ollama server hosted on a different address by setting the environment variable during the Docker build process. Execute the following command to include the Ollama API base URL in the Docker image: `docker build --build-arg OLLAMA_API_BASE_URL='http://localhost:11434/api' -t ollama-webui .`.
- 🌟 **Continuous Updates**: We are committed to improving Ollama Web UI with regular updates and new features. - 🌟 **Continuous Updates**: We are committed to improving Ollama Web UI with regular updates and new features.
## How to Install 🚀 ## How to Install 🚀
@ -38,22 +48,67 @@ OLLAMA_HOST=0.0.0.0 OLLAMA_ORIGINS=* ollama serve
### Using Docker 🐳 ### Using Docker 🐳
If Ollama is hosted on your local machine, run the following command:
```bash ```bash
docker build -t ollama-webui . docker build --build-arg OLLAMA_API_BASE_URL='' -t ollama-webui .
docker run -d -p 3000:3000 --add-host=host.docker.internal:host-gateway --name ollama-webui --restart always ollama-webui docker run -d -p 3000:8080 --name ollama-webui --restart always ollama-webui
``` ```
Your Ollama Web UI should now be hosted at [http://localhost:3000](http://localhost:3000). Enjoy! 😄 Your Ollama Web UI should now be hosted at [http://localhost:3000](http://localhost:3000). Enjoy! 😄
#### Connecting to Ollama on a Different Server #### Connecting to Ollama on a Different Server
If Ollama is hosted on a server other than your local machine, you can connect to it using the following environment variable: If Ollama is hosted on a server other than your local machine, change `OLLAMA_API_BASE_URL` to match:
```bash ```bash
docker build -t ollama-webui . docker build --build-arg OLLAMA_API_BASE_URL='https://example.com/api' -t ollama-webui .
docker run -d -p 3000:3000 --add-host=host.docker.internal:host-gateway -e OLLAMA_ENDPOINT="http://[insert your ollama url]" --name ollama-webui --restart always ollama-webui docker run -d -p 3000:8080 --name ollama-webui --restart always ollama-webui
``` ```
## How to Build for Static Deployment
1. Install `node`
```sh
# Mac, Linux
curl https://webi.sh/node@lts | sh
source ~/.config/envman/PATH.env
```
```pwsh
# Windows
curl.exe https://webi.ms/node@lts | powershell
```
2. Clone & Enter the project
```sh
git clone https://github.com/ollama-webui/ollama-webui.git
pushd ./ollama-webui/
```
3. Create and edit `.env`
```sh
cp -RPp example.env .env
```
4. Run in dev mode, or build the site for deployment
- Test in Dev mode:
```sh
npm run dev
```
- Build for Deploy: \
(`PUBLIC_API_BASE_URL` will overwrite the value in `.env`)
```sh
PUBLIC_API_BASE_URL='https://example.com/api' npm run build
```
5. Test the build with `caddy` (or the server of your choice)
```sh
curl https://webi.sh/caddy | sh
PUBLIC_API_BASE_URL='https://localhost/api' npm run build
caddy run --envfile .env --config ./Caddyfile.localhost
```
## What's Next? 🚀 ## What's Next? 🚀
### To-Do List 📝 ### To-Do List 📝
@ -76,6 +131,7 @@ A big shoutout to our amazing contributors who have helped make this project pos
- [Ollama Team](https://github.com/jmorganca/ollama) - [Ollama Team](https://github.com/jmorganca/ollama)
- [Timothy J. Baek](https://github.com/tjbck) - [Timothy J. Baek](https://github.com/tjbck)
- [AJ ONeal](https://github.com/coolaj86)
## License 📜 ## License 📜

8
example.env Normal file
View file

@ -0,0 +1,8 @@
# must be defined, but defaults to 'http://{location.hostname}:11434/api'
# can also use path, such as '/api'
PUBLIC_API_BASE_URL=''
OLLAMA_API_ID='my-api-token'
OLLAMA_API_TOKEN='xxxxxxxxxxxxxxxx'
# generated by passing the token to `caddy hash-password`
OLLAMA_API_TOKEN_DIGEST='$2a$14$iyyuawykR92xTHNR9lWzfu.uCct/9/xUPX3zBqLqrjAu0usNRPbyi'

668
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "http-server ./build",
"dev": "vite dev --host", "dev": "vite dev --host",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
@ -17,6 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4", "@sveltejs/kit": "^1.20.4",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/parser": "^6.0.0",
@ -39,6 +41,7 @@
"@sveltejs/adapter-node": "^1.3.1", "@sveltejs/adapter-node": "^1.3.1",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"http-server": "^14.1.1",
"idb": "^7.1.1", "idb": "^7.1.1",
"marked": "^9.1.0", "marked": "^9.1.0",
"svelte-french-toast": "^1.2.0", "svelte-french-toast": "^1.2.0",

2
run.sh
View file

@ -1,5 +1,5 @@
docker stop ollama-webui || true docker stop ollama-webui || true
docker rm ollama-webui || true docker rm ollama-webui || true
docker build -t ollama-webui . docker build -t ollama-webui .
docker run -d -p 3000:3000 --add-host=host.docker.internal:host-gateway --name ollama-webui --restart always ollama-webui docker run -d -p 3000:8080 --name ollama-webui --restart always ollama-webui
docker image prune -f docker image prune -f

View file

@ -1,7 +1,19 @@
import { browser, dev } from '$app/environment'; import { browser } from '$app/environment';
import { PUBLIC_API_BASE_URL } from '$env/static/public';
export const ENDPOINT = browser export const API_BASE_URL =
? `http://${location.hostname}:11434` PUBLIC_API_BASE_URL === ''
: dev ? browser
? 'http://127.0.0.1:11434' ? `http://${location.hostname}:11434/api`
: 'http://host.docker.internal:11434'; : `http://localhost:11434/api`
: PUBLIC_API_BASE_URL;
// Source: https://kit.svelte.dev/docs/modules#$env-static-public
// This feature, akin to $env/static/private, exclusively incorporates environment variables
// that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_).
// Consequently, these variables can be securely exposed to client-side code.
// Example of the .env configuration:
// OLLAMA_API_BASE_URL="http://localhost:11434/api"
// # Public
// PUBLIC_API_BASE_URL=$OLLAMA_API_BASE_URL

16
src/routes/+layout.js Normal file
View file

@ -0,0 +1,16 @@
// if you want to generate a static html file
// for your page.
// Documentation: https://kit.svelte.dev/docs/page-options#prerender
export const prerender = true;
// if you want to Generate a SPA
// you have to set ssr to false.
// This is not the case (so set as true or comment the line)
// Documentation: https://kit.svelte.dev/docs/page-options#ssr
// export const ssr = false;
// How to manage the trailing slashes in the URLs
// the URL for about page witll be /about with 'ignore' (default)
// the URL for about page witll be /about/ with 'always'
// https://kit.svelte.dev/docs/page-options#trailingslash
export const trailingSlash = 'ignore';

View file

@ -1,30 +0,0 @@
import { ENDPOINT } from '$lib/constants';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ url }) => {
const OLLAMA_ENDPOINT = process.env.OLLAMA_ENDPOINT;
console.log(OLLAMA_ENDPOINT);
const models = await fetch(
`${OLLAMA_ENDPOINT != undefined ? OLLAMA_ENDPOINT : 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?.models ?? [],
OLLAMA_ENDPOINT: process.env.OLLAMA_ENDPOINT
};
};

View file

@ -7,20 +7,15 @@
const { saveAs } = fileSaver; const { saveAs } = fileSaver;
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import 'highlight.js/styles/dark.min.css'; import 'highlight.js/styles/dark.min.css';
import { API_BASE_URL } from '$lib/constants';
import type { PageData } from './$types';
import { ENDPOINT as SERVER_ENDPOINT } from '$lib/constants';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import { page } from '$app/stores';
const suggestions = $page.url.searchParams.get('suggestions');
import Navbar from '$lib/components/layout/Navbar.svelte'; import Navbar from '$lib/components/layout/Navbar.svelte';
import SettingsModal from '$lib/components/chat/SettingsModal.svelte'; import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
export let data: PageData; let suggestions = ''; // $page.url.searchParams.get('suggestions');
$: ({ models, OLLAMA_ENDPOINT } = data);
let ENDPOINT; let models = [];
let textareaElement; let textareaElement;
let showSettings = false; let showSettings = false;
let db; let db;
@ -36,10 +31,25 @@
let messages = []; let messages = [];
onMount(async () => { onMount(async () => {
ENDPOINT = OLLAMA_ENDPOINT ? OLLAMA_ENDPOINT : SERVER_ENDPOINT; console.log(API_BASE_URL);
console.log(OLLAMA_ENDPOINT); const res = await fetch(`${API_BASE_URL}/tags`, {
console.log(SERVER_ENDPOINT); method: 'GET',
console.log(ENDPOINT); 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 { models: [] };
});
const data = res;
models = data.models;
let settings = localStorage.getItem('settings'); let settings = localStorage.getItem('settings');
if (settings) { if (settings) {
@ -267,7 +277,7 @@
messages = [...messages, responseMessage]; messages = [...messages, responseMessage];
window.scrollTo({ top: document.body.scrollHeight }); window.scrollTo({ top: document.body.scrollHeight });
const res = await fetch(`${ENDPOINT}/api/generate`, { const res = await fetch(`${API_BASE_URL}/generate`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'text/event-stream' 'Content-Type': 'text/event-stream'
@ -363,7 +373,7 @@
messages = [...messages, responseMessage]; messages = [...messages, responseMessage];
window.scrollTo({ top: document.body.scrollHeight }); window.scrollTo({ top: document.body.scrollHeight });
const res = await fetch(`${ENDPOINT}/api/generate`, { const res = await fetch(`${API_BASE_URL}/generate`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'text/event-stream' 'Content-Type': 'text/event-stream'
@ -443,7 +453,7 @@
const generateTitle = async (user_prompt) => { const generateTitle = async (user_prompt) => {
console.log('generateTitle'); console.log('generateTitle');
const res = await fetch(`${ENDPOINT}/api/generate`, { const res = await fetch(`${API_BASE_URL}/generate`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'text/event-stream' 'Content-Type': 'text/event-stream'

View file

@ -1,4 +1,4 @@
import adapter from '@sveltejs/adapter-node'; import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/kit/vite'; import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
@ -11,7 +11,11 @@ const config = {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // 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. // 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. // See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter() adapter: adapter({
pages: 'build',
assets: 'build',
fallback: null
})
} }
}; };