Commit 921eef03 authored by Timothy J. Baek's avatar Timothy J. Baek
Browse files

feat: multi-user support w/ RBAC

parent 31e38df0
from flask import Flask, request, Response
from flask import Flask, request, Response, jsonify
from flask_cors import CORS
......@@ -6,7 +6,10 @@ import requests
import json
from config import OLLAMA_API_BASE_URL
from apps.web.models.users import Users
from constants import ERROR_MESSAGES
from utils import extract_token_from_auth_header
from config import OLLAMA_API_BASE_URL, OLLAMA_WEBUI_AUTH
app = Flask(__name__)
CORS(
......@@ -28,6 +31,21 @@ def proxy(path):
data = request.get_data()
headers = dict(request.headers)
if OLLAMA_WEBUI_AUTH:
if "Authorization" in headers:
token = extract_token_from_auth_header(headers["Authorization"])
user = Users.get_user_by_token(token)
if user:
print(user)
pass
else:
return jsonify({"detail": ERROR_MESSAGES.UNAUTHORIZED}), 401
else:
return jsonify({"detail": ERROR_MESSAGES.UNAUTHORIZED}), 401
else:
pass
# Make a request to the target server
target_response = requests.request(
method=request.method,
......
from fastapi import FastAPI, Request, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from apps.web.routers import auths
from config import OLLAMA_WEBUI_VERSION, OLLAMA_WEBUI_AUTH
app = FastAPI()
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auths.router, prefix="/auths", tags=["auths"])
@app.get("/")
async def get_status():
return {"status": True, "version": OLLAMA_WEBUI_VERSION, "auth": OLLAMA_WEBUI_AUTH}
from pydantic import BaseModel
from typing import List, Union, Optional
import time
import uuid
from apps.web.models.users import UserModel, Users
from utils import (
verify_password,
get_password_hash,
bearer_scheme,
create_token,
)
import config
DB = config.DB
####################
# DB MODEL
####################
class AuthModel(BaseModel):
id: str
email: str
password: str
active: bool = True
####################
# Forms
####################
class Token(BaseModel):
token: str
token_type: str
class UserResponse(BaseModel):
id: str
email: str
name: str
role: str
class SigninResponse(Token, UserResponse):
pass
class SigninForm(BaseModel):
email: str
password: str
class SignupForm(BaseModel):
name: str
email: str
password: str
class AuthsTable:
def __init__(self, db):
self.db = db
self.table = db.auths
def insert_new_auth(
self, email: str, password: str, name: str, role: str = "user"
) -> Optional[UserModel]:
print("insert_new_auth")
id = str(uuid.uuid4())
auth = AuthModel(
**{"id": id, "email": email, "password": password, "active": True}
)
result = self.table.insert_one(auth.model_dump())
user = Users.insert_new_user(id, name, email, role)
print(result, user)
if result and user:
return user
else:
return None
def authenticate_user(self, email: str, password: str) -> Optional[UserModel]:
print("authenticate_user")
auth = self.table.find_one({"email": email, "active": True})
if auth:
if verify_password(password, auth["password"]):
user = self.db.users.find_one({"id": auth["id"]})
return UserModel(**user)
else:
return None
else:
return None
Auths = AuthsTable(DB)
from pydantic import BaseModel
from typing import List, Union, Optional
from pymongo import ReturnDocument
import time
from utils import decode_token
from config import DB
####################
# User DB Schema
####################
class UserModel(BaseModel):
id: str
name: str
email: str
role: str = "user"
created_at: int # timestamp in epoch
####################
# Forms
####################
class UsersTable:
def __init__(self, db):
self.db = db
self.table = db.users
def insert_new_user(
self, id: str, name: str, email: str, role: str = "user"
) -> Optional[UserModel]:
user = UserModel(
**{
"id": id,
"name": name,
"email": email,
"role": role,
"created_at": int(time.time()),
}
)
result = self.table.insert_one(user.model_dump())
if result:
return user
else:
return None
def get_user_by_email(self, email: str) -> Optional[UserModel]:
user = self.table.find_one({"email": email}, {"_id": False})
if user:
return UserModel(**user)
else:
return None
def get_user_by_token(self, token: str) -> Optional[UserModel]:
data = decode_token(token)
if data != None and "email" in data:
return self.get_user_by_email(data["email"])
else:
return None
def get_users(self, skip: int = 0, limit: int = 50) -> Optional[UserModel]:
return [
UserModel(**user)
for user in list(self.table.find({}, {"_id": False}))
.skip(skip)
.limit(limit)
]
Users = UsersTable(DB)
from fastapi import Response
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import List, Union
from fastapi import APIRouter
from pydantic import BaseModel
import time
import uuid
from constants import ERROR_MESSAGES
from utils import (
get_password_hash,
bearer_scheme,
create_token,
)
from apps.web.models.auths import (
SigninForm,
SignupForm,
UserResponse,
SigninResponse,
Auths,
)
from apps.web.models.users import Users
import config
router = APIRouter()
DB = config.DB
############################
# GetSessionUser
############################
@router.get("/", response_model=UserResponse)
async def get_session_user(cred=Depends(bearer_scheme)):
token = cred.credentials
user = Users.get_user_by_token(token)
if user:
return {
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
}
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
)
############################
# SignIn
############################
@router.post("/signin", response_model=SigninResponse)
async def signin(form_data: SigninForm):
user = Auths.authenticate_user(form_data.email.lower(), form_data.password)
if user:
token = create_token(data={"email": user.email})
return {
"token": token,
"token_type": "Bearer",
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
}
else:
raise HTTPException(400, detail=ERROR_MESSAGES.DEFAULT())
############################
# SignUp
############################
@router.post("/signup", response_model=SigninResponse)
async def signup(form_data: SignupForm):
if not Users.get_user_by_email(form_data.email.lower()):
try:
hashed = get_password_hash(form_data.password)
user = Auths.insert_new_auth(form_data.email, hashed, form_data.name)
if user:
token = create_token(data={"email": user.email})
# response.set_cookie(key='token', value=token, httponly=True)
return {
"token": token,
"token_type": "Bearer",
"id": user.id,
"email": user.email,
"name": user.name,
"role": user.role,
}
else:
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
except Exception as err:
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
else:
raise HTTPException(400, detail=ERROR_MESSAGES.DEFAULT())
import sys
import os
from dotenv import load_dotenv, find_dotenv
from pymongo import MongoClient
from secrets import token_bytes
from base64 import b64encode
import os
load_dotenv(find_dotenv())
####################################
# ENV (dev,test,prod)
####################################
ENV = os.environ.get("ENV", "dev")
####################################
# OLLAMA_API_BASE_URL
####################################
OLLAMA_API_BASE_URL = os.environ.get(
"OLLAMA_API_BASE_URL", "http://localhost:11434/api"
)
......@@ -13,3 +24,42 @@ OLLAMA_API_BASE_URL = os.environ.get(
if ENV == "prod":
if OLLAMA_API_BASE_URL == "/ollama/api":
OLLAMA_API_BASE_URL = "http://host.docker.internal:11434/api"
####################################
# OLLAMA_WEBUI_VERSION
####################################
OLLAMA_WEBUI_VERSION = os.environ.get("OLLAMA_WEBUI_VERSION", "v1.0.0-alpha.9")
####################################
# OLLAMA_WEBUI_AUTH
####################################
OLLAMA_WEBUI_AUTH = (
True if os.environ.get("OLLAMA_WEBUI_AUTH", "TRUE") == "TRUE" else False
)
if OLLAMA_WEBUI_AUTH:
####################################
# OLLAMA_WEBUI_DB
####################################
OLLAMA_WEBUI_DB_URL = os.environ.get(
"OLLAMA_WEBUI_DB_URL", "mongodb://root:root@localhost:27017/"
)
DB_CLIENT = MongoClient(f"{OLLAMA_WEBUI_DB_URL}?authSource=admin")
DB = DB_CLIENT["ollama-webui"]
####################################
# OLLAMA_WEBUI_JWT_SECRET_KEY
####################################
OLLAMA_WEBUI_JWT_SECRET_KEY = os.environ.get(
"OLLAMA_WEBUI_JWT_SECRET_KEY", "t0p-s3cr3t"
)
if ENV == "prod":
if OLLAMA_WEBUI_JWT_SECRET_KEY == "":
OLLAMA_WEBUI_JWT_SECRET_KEY = str(b64encode(token_bytes(32)).decode())
from enum import Enum
class MESSAGES(str, Enum):
DEFAULT = lambda msg="": f"{msg if msg else ''}"
class ERROR_MESSAGES(str, Enum):
DEFAULT = lambda err="": f"Something went wrong :/\n{err if err else ''}"
UNAUTHORIZED = "401 Unauthorized"
NOT_FOUND = "We could not find what you're looking for :/"
USER_NOT_FOUND = "We could not find what you're looking for :/"
MALICIOUS = "Unusual activities detected, please try again in a few minutes."
import time
import sys
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi import HTTPException
from starlette.exceptions import HTTPException as StarletteHTTPException
from fastapi.middleware.wsgi import WSGIMiddleware
from fastapi.middleware.cors import CORSMiddleware
from starlette.exceptions import HTTPException as StarletteHTTPException
from apps.ollama.main import app as ollama_app
from apps.web.main import app as webui_app
import time
class SPAStaticFiles(StaticFiles):
......@@ -47,5 +45,6 @@ async def check_url(request: Request, call_next):
return response
app.mount("/api/v1", webui_app)
app.mount("/ollama/api", WSGIMiddleware(ollama_app))
app.mount("/", SPAStaticFiles(directory="../build", html=True), name="spa-static-files")
from fastapi.security import HTTPBasicCredentials, HTTPBearer
from pydantic import BaseModel
from typing import Union, Optional
from passlib.context import CryptContext
from datetime import datetime, timedelta
import requests
import jwt
import config
JWT_SECRET_KEY = config.OLLAMA_WEBUI_JWT_SECRET_KEY
ALGORITHM = "HS256"
##############
# Auth Utils
##############
bearer_scheme = HTTPBearer()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return (
pwd_context.verify(plain_password, hashed_password) if hashed_password else None
)
def get_password_hash(password):
return pwd_context.hash(password)
def create_token(data: dict, expires_delta: Union[timedelta, None] = None) -> str:
payload = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
payload.update({"exp": expire})
encoded_jwt = jwt.encode(payload, JWT_SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_token(token: str) -> Optional[dict]:
try:
decoded = jwt.decode(token, JWT_SECRET_KEY, options={"verify_signature": False})
return decoded
except Exception as e:
return None
def extract_token_from_auth_header(auth_header: str):
return auth_header[len("Bearer ") :]
def verify_token(request):
try:
bearer = request.headers["authorization"]
if bearer:
token = bearer[len("Bearer ") :]
decoded = jwt.decode(
token, JWT_SECRET_KEY, options={"verify_signature": False}
)
return decoded
else:
return None
except Exception as e:
return None
......@@ -22,6 +22,15 @@ services:
restart: unless-stopped
image: ollama/ollama:latest
ollama-webui-db:
image: mongo
container_name: ollama-webui-db
restart: always
# Make sure to change the username/password!
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
ollama-webui:
build:
context: .
......@@ -32,10 +41,12 @@ services:
container_name: ollama-webui
depends_on:
- ollama
- ollama-webui-db
ports:
- 3000:8080
environment:
- "OLLAMA_API_BASE_URL=http://ollama:11434/api"
- "OLLAMA_WEBUI_DB_URL=mongodb://root:example@ollama-webui-db:27017/"
extra_hosts:
- host.docker.internal:host-gateway
restart: unless-stopped
......
......@@ -4,8 +4,13 @@
font-display: swap;
}
@font-face {
font-family: 'Mona Sans';
src: url('/assets/fonts/Mona-Sans.woff2');
font-display: swap;
}
html {
@apply bg-gray-800;
word-break: break-word;
}
......
......@@ -2,9 +2,10 @@
import sha256 from 'js-sha256';
import Modal from '../common/Modal.svelte';
import { WEB_UI_VERSION, API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
import { WEB_UI_VERSION, OLLAMA_API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
import toast from 'svelte-french-toast';
import { onMount } from 'svelte';
import { config, user } from '$lib/stores';
export let show = false;
export let saveSettings: Function;
......@@ -119,7 +120,8 @@
const res = await fetch(`${API_BASE_URL}/pull`, {
method: 'POST',
headers: {
'Content-Type': 'text/event-stream'
'Content-Type': 'text/event-stream',
...($user && { Authorization: `Bearer ${localStorage.token}` })
},
body: JSON.stringify({
name: modelTag
......@@ -175,7 +177,8 @@
const res = await fetch(`${API_BASE_URL}/delete`, {
method: 'DELETE',
headers: {
'Content-Type': 'text/event-stream'
'Content-Type': 'text/event-stream',
...($user && { Authorization: `Bearer ${localStorage.token}` })
},
body: JSON.stringify({
name: deleteModelTag
......@@ -992,7 +995,7 @@
<div class=" mb-2.5 text-sm font-medium">Ollama Web UI Version</div>
<div class="flex w-full">
<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
{WEB_UI_VERSION}
{$config && $config.version ? $config.version : WEB_UI_VERSION}
</div>
</div>
</div>
......
<script lang="ts">
import { goto } from '$app/navigation';
import { user } from '$lib/stores';
import { onMount } from 'svelte';
let show = false;
......@@ -22,9 +24,9 @@
let chatTitleEditIdx = null;
let chatTitle = '';
let _chats = chats.map((item, idx) => chats[chats.length - 1 - idx]);
let showDropdown = false;
onMount(() => {});
let _chats = chats.map((item, idx) => chats[chats.length - 1 - idx]);
$: if (chats) {
_chats = chats.map((item, idx) => chats[chats.length - 1 - idx]);
......@@ -98,6 +100,7 @@
<button
class="flex-grow flex justify-between rounded-md px-3 py-1.5 my-2 hover:bg-gray-900 transition"
on:click={() => {
// goto('/');
createNewChat();
}}
>
......@@ -163,6 +166,7 @@
? 'bg-gray-900'
: ''} transition whitespace-nowrap text-ellipsis"
on:click={() => {
// goto(`/c/${chat.id}`);
if (chat.id !== chatTitleEditIdx) {
chatTitleEditIdx = null;
chatTitle = '';
......@@ -380,8 +384,33 @@
</div>
<div class=" self-center">Clear conversations</div>
</button>
{#if $user !== undefined}
<button
class=" flex rounded-md py-3 px-3.5 w-full hover:bg-gray-900 transition"
on:focus={() => {
showDropdown = true;
}}
on:focusout={() => {
setTimeout(() => {
showDropdown = false;
}, 100);
}}
>
<div class=" self-center mr-3">
<img src="/user.png" class=" max-w-[30px] object-cover rounded-full" />
</div>
<div class=" self-center font-semibold">{$user.name}</div>
</button>
{#if showDropdown}
<div
id="dropdownDots"
class="absolute z-10 bottom-[4.5rem] rounded-lg shadow w-[240px] bg-gray-900"
>
<div class="py-2 w-full">
<button
class="flex py-2.5 px-3.5 w-full hover:bg-gray-800 transition"
on:click={() => {
openSettings();
}}
......@@ -410,6 +439,73 @@
<div class=" self-center font-medium">Settings</div>
</button>
</div>
<hr class=" dark:border-gray-700 m-0 p-0" />
<div class="py-2 w-full">
<button
class="flex py-2.5 px-3.5 w-full hover:bg-gray-800 transition"
on:click={() => {
localStorage.removeItem('token');
location.href = '/';
}}
>
<div class=" self-center mr-3">
<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="M6 10a.75.75 0 01.75-.75h9.546l-1.048-.943a.75.75 0 111.004-1.114l2.5 2.25a.75.75 0 010 1.114l-2.5 2.25a.75.75 0 11-1.004-1.114l1.048-.943H6.75A.75.75 0 016 10z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center font-medium">Sign Out</div>
</button>
</div>
</div>
{/if}
{:else}
<button
class=" flex rounded-md py-3 px-3.5 w-full hover:bg-gray-900 transition"
on:click={() => {
openSettings();
}}
>
<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-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</div>
<div class=" self-center font-medium">Settings</div>
</button>
{/if}
</div>
</div>
</div>
......
import { browser } from '$app/environment';
import { dev, browser } from '$app/environment';
import { PUBLIC_API_BASE_URL } from '$env/static/public';
export const API_BASE_URL =
export const OLLAMA_API_BASE_URL =
PUBLIC_API_BASE_URL === ''
? browser
? `http://${location.hostname}:11434/api`
? `http://${location.hostname}:8080/ollama/api`
: `http://localhost:11434/api`
: PUBLIC_API_BASE_URL;
export const WEB_UI_VERSION = 'v1.0.0-alpha.8';
export const WEBUI_API_BASE_URL = dev ? `http://${location.hostname}:8080/api/v1` : `/api/v1`;
export const WEB_UI_VERSION = 'v1.0.0-alpha-static';
// Source: https://kit.svelte.dev/docs/modules#$env-static-public
// This feature, akin to $env/static/private, exclusively incorporates environment variables
......
import { writable } from 'svelte/store';
export const config = writable(undefined);
export const user = writable(undefined);
<script>
import { config, user } from '$lib/stores';
import { goto } from '$app/navigation';
if ($config && $config.auth && $user === undefined) {
goto('/auth');
}
</script>
{#if $config !== undefined}
<slot />
{/if}
......@@ -10,17 +10,17 @@
import 'katex/dist/katex.min.css';
import toast from 'svelte-french-toast';
import { API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
import { OLLAMA_API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
import { onMount, tick } from 'svelte';
import Navbar from '$lib/components/layout/Navbar.svelte';
import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
import Suggestions from '$lib/components/chat/Suggestions.svelte';
import { user } from '$lib/stores';
let API_BASE_URL = BUILD_TIME_API_BASE_URL;
let db;
// let selectedModel = '';
let selectedModels = [''];
let settings = {
system: null,
......@@ -619,7 +619,8 @@
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(settings.authHeader && { Authorization: settings.authHeader })
...(settings.authHeader && { Authorization: settings.authHeader }),
...($user && { Authorization: `Bearer ${localStorage.token}` })
}
})
.then(async (res) => {
......@@ -628,7 +629,11 @@
})
.catch((error) => {
console.log(error);
if ('detail' in error) {
toast.error(error.detail);
} else {
toast.error('Server connection failed');
}
return null;
});
......@@ -687,13 +692,6 @@
}
})
);
// if (selectedModel.includes('gpt-')) {
// await sendPromptOpenAI(userPrompt, parentId);
// } else {
// await sendPromptOllama(userPrompt, parentId);
// }
console.log(history);
};
......@@ -724,7 +722,8 @@
method: 'POST',
headers: {
'Content-Type': 'text/event-stream',
...(settings.authHeader && { Authorization: settings.authHeader })
...(settings.authHeader && { Authorization: settings.authHeader }),
...($user && { Authorization: `Bearer ${localStorage.token}` })
},
body: JSON.stringify({
model: model,
......@@ -779,6 +778,8 @@
responseMessage.content += data.response;
messages = messages;
}
} else if ('detail' in data) {
throw data;
} else {
responseMessage.done = true;
responseMessage.context = data.context;
......@@ -791,6 +792,10 @@
}
} catch (error) {
console.log(error);
if ('detail' in error) {
toast.error(error.detail);
}
break;
}
if (autoScroll) {
......@@ -817,7 +822,7 @@
window.scrollTo({ top: document.body.scrollHeight });
}
if (messages.length == 2) {
if (messages.length == 2 && messages.at(1).content !== '') {
await generateChatTitle(chatId, userPrompt);
}
};
......@@ -1034,7 +1039,8 @@
method: 'POST',
headers: {
'Content-Type': 'text/event-stream',
...(settings.authHeader && { Authorization: settings.authHeader })
...(settings.authHeader && { Authorization: settings.authHeader }),
...($user && { Authorization: `Bearer ${localStorage.token}` })
},
body: JSON.stringify({
model: selectedModels[0],
......@@ -1047,6 +1053,9 @@
return res.json();
})
.catch((error) => {
if ('detail' in error) {
toast.error(error.detail);
}
console.log(error);
return null;
});
......
<script>
import { Toaster } from 'svelte-french-toast';
import { onMount, tick } from 'svelte';
import { config, user } from '$lib/stores';
import { goto } from '$app/navigation';
import { WEBUI_API_BASE_URL } from '$lib/constants';
import toast, { Toaster } from 'svelte-french-toast';
import '../app.css';
import '../tailwind.css';
let loaded = false;
onMount(async () => {
const webBackendStatus = await fetch(`${WEBUI_API_BASE_URL}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((error) => {
console.log(error);
return null;
});
console.log(webBackendStatus);
await config.set(webBackendStatus);
if (webBackendStatus) {
if (webBackendStatus.auth) {
if (localStorage.token) {
const res = await fetch(`${WEBUI_API_BASE_URL}/auths`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((error) => {
console.log(error);
toast.error(error.detail);
return null;
});
await user.set(res);
} else {
goto('/auth');
}
}
}
await tick();
loaded = true;
});
</script>
<svelte:head>
<title>Ollama</title>
</svelte:head>
<slot />
<Toaster />
{#if $config !== undefined && loaded}
<slot />
{/if}
<script>
import { goto } from '$app/navigation';
import { WEBUI_API_BASE_URL } from '$lib/constants';
import { config, user } from '$lib/stores';
import toast from 'svelte-french-toast';
let mode = 'signin';
let name = '';
let email = '';
let password = '';
const signInHandler = async () => {
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signin`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((error) => {
console.log(error);
toast.error(error.detail);
return null;
});
if (res) {
console.log(res);
toast.success(`You're now logged in. Redirecting you to the main page."`);
localStorage.token = res.token;
await user.set(res);
goto('/');
}
};
const signUpHandler = async () => {
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: name,
email: email,
password: password
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((error) => {
console.log(error);
toast.error(error.detail);
return null;
});
if (res) {
console.log(res);
toast.success(`Account creation successful. Redirecting you to the main page."`);
localStorage.token = res.token;
await user.set(res);
goto('/');
}
};
if ($config === null || !$config.auth || ($config.auth && $user !== undefined)) {
goto('/');
}
</script>
{#if $config && $config.auth}
<div class="fixed m-10 z-50">
<div class="flex space-x-2">
<div class=" self-center">
<img src="/ollama.png" class=" w-8" />
</div>
</div>
</div>
<div class=" bg-white min-h-screen w-full flex justify-center font-mona">
<div class="hidden lg:flex lg:flex-1 px-10 md:px-16 w-full bg-yellow-50 justify-center">
<div class=" my-auto pb-16 text-left">
<div>
<div class=" font-bold text-yellow-600 text-4xl">
Get up and running with <br />large language models, locally.
</div>
<div class="mt-2 text-yellow-600 text-xl">
Run Llama 2, Code Llama, and other models. Customize and create your own.
</div>
</div>
</div>
</div>
<div class="w-full max-w-xl px-10 md:px-16 bg-white min-h-screen w-full flex flex-col">
<div class=" my-auto pb-10 w-full">
<form
class=" flex flex-col justify-center"
on:submit|preventDefault={() => {
if (mode === 'signin') {
signInHandler();
} else {
signUpHandler();
}
}}
>
<div class=" text-2xl md:text-3xl font-semibold">
{mode === 'signin' ? 'Sign in' : 'Sign up'} to Ollama Web UI
</div>
<hr class="my-8" />
<div class="flex flex-col space-y-4">
{#if mode === 'signup'}
<div>
<div class=" text-sm font-bold text-left mb-2">Name</div>
<input
bind:value={name}
type="text"
class=" border px-5 py-4 rounded-2xl w-full text-sm"
autocomplete="name"
required
/>
</div>
{/if}
<div>
<div class=" text-sm font-bold text-left mb-2">Email</div>
<input
bind:value={email}
type="email"
class=" border px-5 py-4 rounded-2xl w-full text-sm"
autocomplete="email"
required
/>
</div>
<div>
<div class=" text-sm font-bold text-left mb-2">Password</div>
<input
bind:value={password}
type="password"
class=" border px-5 py-4 rounded-2xl w-full text-sm"
autocomplete="current-password"
required
/>
</div>
</div>
<div class="mt-8">
<button
class=" bg-gray-900 hover:bg-gray-800 w-full rounded-full text-white font-semibold text-sm py-5 transition"
type="submit"
>
{mode === 'signin' ? 'Sign In' : 'Create Account'}
</button>
<div class=" mt-4 text-sm text-center">
{mode === 'signin' ? `Don't have an account?` : `Already have an account?`}
<button
class=" font-medium underline"
type="button"
on:click={() => {
if (mode === 'signin') {
mode = 'signup';
} else {
mode = 'signin';
}
}}
>
{mode === 'signin' ? `Sign up` : `Sign In`}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<!-- <div class=" bg-white min-h-screen w-full flex flex-col">
<div class=" mt-6 mx-6">
<div class="flex space-x-2">
<div class=" self-center text-2xl font-semibold">Ollama</div>
<div class=" self-center">
<img src="/ollama.png" class=" w-5" />
</div>
</div>
</div>
<div class=" my-auto pb-36 w-full px-4">
<div class=" text-center flex flex-col justify-center">
<div class=" text-xl md:text-2xl font-bold">Get Started</div>
<div
class=" mt-4 flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-1 px-3 justify-center"
>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Log in
</button>
<button class=" flex-1 px-4 py-3.5 bg-blue-700 text-white font-medium rounded-lg">
Sign up
</button>
</div>
</div>
</div>
</div> -->
<style>
.font-mona {
font-family: 'Mona Sans';
}
</style>
{/if}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment