From 5e4dc98f4490808df0e234ac23137f44c75c546a Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 4 Jan 2024 16:49:34 -0800 Subject: [PATCH 1/8] feat: openai backend support --- backend/apps/openai/main.py | 113 ++++++++++++++++++++++++++++++++++++ backend/config.py | 8 +++ 2 files changed, 121 insertions(+) create mode 100644 backend/apps/openai/main.py diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py new file mode 100644 index 00000000..0a12137a --- /dev/null +++ b/backend/apps/openai/main.py @@ -0,0 +1,113 @@ +from fastapi import FastAPI, Request, Response, HTTPException, Depends +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import StreamingResponse + +import requests +import json +from pydantic import BaseModel + +from apps.web.models.users import Users +from constants import ERROR_MESSAGES +from utils.utils import decode_token, get_current_user +from config import OPENAI_API_BASE_URL, OPENAI_API_KEY + +app = FastAPI() +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.state.OPENAI_API_BASE_URL = OPENAI_API_BASE_URL +app.state.OPENAI_API_KEY = OPENAI_API_KEY + + +class UrlUpdateForm(BaseModel): + url: str + + +class KeyUpdateForm(BaseModel): + key: str + + +@app.get("/url") +async def get_openai_url(user=Depends(get_current_user)): + if user and user.role == "admin": + return {"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL} + else: + raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + + +@app.post("/url/update") +async def update_openai_url(form_data: UrlUpdateForm, user=Depends(get_current_user)): + if user and user.role == "admin": + app.state.OPENAI_API_BASE_URL = form_data.url + return {"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL} + else: + raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + + +@app.get("/key") +async def get_openai_key(user=Depends(get_current_user)): + if user and user.role == "admin": + return {"OPENAI_API_KEY": app.state.OPENAI_API_KEY} + else: + raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + + +@app.post("/key/update") +async def update_openai_key(form_data: KeyUpdateForm, user=Depends(get_current_user)): + if user and user.role == "admin": + app.state.OPENAI_API_KEY = form_data.key + return {"OPENAI_API_KEY": app.state.OPENAI_API_KEY} + else: + raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + + +@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) +async def proxy(path: str, request: Request, user=Depends(get_current_user)): + target_url = f"{app.state.OPENAI_API_BASE_URL}/{path}" + + body = await request.body() + headers = dict(request.headers) + + if user.role not in ["user", "admin"]: + raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + + headers.pop("Host", None) + headers.pop("Authorization", None) + headers.pop("Origin", None) + headers.pop("Referer", None) + + headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}" + + try: + r = requests.request( + method=request.method, + url=target_url, + data=body, + headers=headers, + stream=True, + ) + + r.raise_for_status() + + return StreamingResponse( + r.iter_content(chunk_size=8192), + status_code=r.status_code, + headers=dict(r.headers), + ) + except Exception as e: + print(e) + error_detail = "Ollama WebUI: Server Connection Error" + if r is not None: + try: + res = r.json() + if "error" in res: + error_detail = f"External: {res['error']}" + except: + error_detail = f"External: {e}" + + raise HTTPException(status_code=r.status_code, detail=error_detail) diff --git a/backend/config.py b/backend/config.py index 8e100fe5..90900b9d 100644 --- a/backend/config.py +++ b/backend/config.py @@ -27,6 +27,14 @@ if ENV == "prod": if OLLAMA_API_BASE_URL == "/ollama/api": OLLAMA_API_BASE_URL = "http://host.docker.internal:11434/api" + +#################################### +# OPENAI_API +#################################### + +OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "") +OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "https://api.openai.com/v1") + #################################### # WEBUI_VERSION #################################### From 17c66fde0f84b1859f27eee93f08ce6d48b7ff2e Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 4 Jan 2024 18:38:03 -0800 Subject: [PATCH 2/8] feat: openai compatible api support --- backend/apps/openai/main.py | 47 +++-- backend/config.py | 5 +- backend/constants.py | 1 + backend/main.py | 4 +- example.env | 12 +- src/lib/apis/openai/index.ts | 174 ++++++++++++++++++- src/lib/components/chat/SettingsModal.svelte | 73 ++++---- src/lib/constants.ts | 7 +- src/routes/(app)/+layout.svelte | 22 ++- 9 files changed, 260 insertions(+), 85 deletions(-) diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index 0a12137a..c6d06a9e 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -1,6 +1,6 @@ from fastapi import FastAPI, Request, Response, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import StreamingResponse +from fastapi.responses import StreamingResponse, JSONResponse import requests import json @@ -69,18 +69,18 @@ async def update_openai_key(form_data: KeyUpdateForm, user=Depends(get_current_u @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) async def proxy(path: str, request: Request, user=Depends(get_current_user)): target_url = f"{app.state.OPENAI_API_BASE_URL}/{path}" - - body = await request.body() - headers = dict(request.headers) + print(target_url, app.state.OPENAI_API_KEY) if user.role not in ["user", "admin"]: raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + if app.state.OPENAI_API_KEY == "": + raise HTTPException(status_code=401, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND) - headers.pop("Host", None) - headers.pop("Authorization", None) - headers.pop("Origin", None) - headers.pop("Referer", None) + body = await request.body() + # headers = dict(request.headers) + # print(headers) + headers = {} headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}" try: @@ -94,11 +94,32 @@ async def proxy(path: str, request: Request, user=Depends(get_current_user)): r.raise_for_status() - return StreamingResponse( - r.iter_content(chunk_size=8192), - status_code=r.status_code, - headers=dict(r.headers), - ) + # Check if response is SSE + if "text/event-stream" in r.headers.get("Content-Type", ""): + return StreamingResponse( + r.iter_content(chunk_size=8192), + status_code=r.status_code, + headers=dict(r.headers), + ) + else: + # For non-SSE, read the response and return it + # response_data = ( + # r.json() + # if r.headers.get("Content-Type", "") + # == "application/json" + # else r.text + # ) + + response_data = r.json() + + print(type(response_data)) + + if "openai" in app.state.OPENAI_API_BASE_URL and path == "models": + response_data["data"] = list( + filter(lambda model: "gpt" in model["id"], response_data["data"]) + ) + + return response_data except Exception as e: print(e) error_detail = "Ollama WebUI: Server Connection Error" diff --git a/backend/config.py b/backend/config.py index 90900b9d..4e0f9e97 100644 --- a/backend/config.py +++ b/backend/config.py @@ -33,7 +33,10 @@ if ENV == "prod": #################################### OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "") -OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "https://api.openai.com/v1") +OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "") + +if OPENAI_API_BASE_URL == "": + OPENAI_API_BASE_URL = "https://api.openai.com/v1" #################################### # WEBUI_VERSION diff --git a/backend/constants.py b/backend/constants.py index 0817445b..e51ecdda 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -33,4 +33,5 @@ class ERROR_MESSAGES(str, Enum): ) NOT_FOUND = "We could not find what you're looking for :/" USER_NOT_FOUND = "We could not find what you're looking for :/" + API_KEY_NOT_FOUND = "Oops! It looks like there's a hiccup. The API key is missing. Please make sure to provide a valid API key to access this feature." MALICIOUS = "Unusual activities detected, please try again in a few minutes." diff --git a/backend/main.py b/backend/main.py index 5e3b7e83..a97e6e85 100644 --- a/backend/main.py +++ b/backend/main.py @@ -6,6 +6,8 @@ from fastapi.middleware.cors import CORSMiddleware from starlette.exceptions import HTTPException as StarletteHTTPException from apps.ollama.main import app as ollama_app +from apps.openai.main import app as openai_app + from apps.web.main import app as webui_app import time @@ -46,7 +48,7 @@ async def check_url(request: Request, call_next): app.mount("/api/v1", webui_app) -# app.mount("/ollama/api", WSGIMiddleware(ollama_app)) app.mount("/ollama/api", ollama_app) +app.mount("/openai/api", openai_app) app.mount("/", SPAStaticFiles(directory="../build", html=True), name="spa-static-files") diff --git a/example.env b/example.env index 9c628b42..74d52223 100644 --- a/example.env +++ b/example.env @@ -1,12 +1,6 @@ -# If you're serving both the frontend and backend (Recommended) -# Set the public API base URL for seamless communication -PUBLIC_API_BASE_URL='/ollama/api' - -# If you're serving only the frontend (Not recommended and not fully supported) -# Comment above and Uncomment below -# You can use the default value or specify a custom path, e.g., '/api' -# PUBLIC_API_BASE_URL='http://{location.hostname}:11434/api' - # Ollama URL for the backend to connect # The path '/ollama/api' will be redirected to the specified backend URL OLLAMA_API_BASE_URL='http://localhost:11434/api' + +OPENAI_API_BASE_URL='' +OPENAI_API_KEY='' \ No newline at end of file diff --git a/src/lib/apis/openai/index.ts b/src/lib/apis/openai/index.ts index c144ae89..c1135fee 100644 --- a/src/lib/apis/openai/index.ts +++ b/src/lib/apis/openai/index.ts @@ -1,4 +1,176 @@ -export const getOpenAIModels = async ( +import { OPENAI_API_BASE_URL } from '$lib/constants'; + +export const getOpenAIUrl = async (token: string = '') => { + let error = null; + + const res = await fetch(`${OPENAI_API_BASE_URL}/url`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + if ('detail' in err) { + error = err.detail; + } else { + error = 'Server connection failed'; + } + return null; + }); + + if (error) { + throw error; + } + + return res.OPENAI_API_BASE_URL; +}; + +export const updateOpenAIUrl = async (token: string = '', url: string) => { + let error = null; + + const res = await fetch(`${OPENAI_API_BASE_URL}/url/update`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + }, + body: JSON.stringify({ + url: url + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + if ('detail' in err) { + error = err.detail; + } else { + error = 'Server connection failed'; + } + return null; + }); + + if (error) { + throw error; + } + + return res.OPENAI_API_BASE_URL; +}; + +export const getOpenAIKey = async (token: string = '') => { + let error = null; + + const res = await fetch(`${OPENAI_API_BASE_URL}/key`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + if ('detail' in err) { + error = err.detail; + } else { + error = 'Server connection failed'; + } + return null; + }); + + if (error) { + throw error; + } + + return res.OPENAI_API_KEY; +}; + +export const updateOpenAIKey = async (token: string = '', key: string) => { + let error = null; + + const res = await fetch(`${OPENAI_API_BASE_URL}/key/update`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + }, + body: JSON.stringify({ + key: key + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + if ('detail' in err) { + error = err.detail; + } else { + error = 'Server connection failed'; + } + return null; + }); + + if (error) { + throw error; + } + + return res.OPENAI_API_KEY; +}; + +export const getOpenAIModels = async (token: string = '') => { + let error = null; + + const res = await fetch(`${OPENAI_API_BASE_URL}/models`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = `OpenAI: ${err?.error?.message ?? 'Network Problem'}`; + return []; + }); + + if (error) { + throw error; + } + + const models = Array.isArray(res) ? res : res?.data ?? null; + + return models + ? models + .map((model) => ({ name: model.id, external: true })) + .sort((a, b) => { + return a.name.localeCompare(b.name); + }) + : models; +}; + +export const getOpenAIModelsDirect = async ( base_url: string = 'https://api.openai.com/v1', api_key: string = '' ) => { diff --git a/src/lib/components/chat/SettingsModal.svelte b/src/lib/components/chat/SettingsModal.svelte index 174ffc17..edb3e840 100644 --- a/src/lib/components/chat/SettingsModal.svelte +++ b/src/lib/components/chat/SettingsModal.svelte @@ -24,6 +24,13 @@ import { updateUserPassword } from '$lib/apis/auths'; import { goto } from '$app/navigation'; import Page from '../../../routes/(app)/+page.svelte'; + import { + getOpenAIKey, + getOpenAIModels, + getOpenAIUrl, + updateOpenAIKey, + updateOpenAIUrl + } from '$lib/apis/openai'; export let show = false; @@ -153,6 +160,13 @@ } }; + const updateOpenAIHandler = async () => { + OPENAI_API_BASE_URL = await updateOpenAIUrl(localStorage.token, OPENAI_API_BASE_URL); + OPENAI_API_KEY = await updateOpenAIKey(localStorage.token, OPENAI_API_KEY); + + await models.set(await getModels()); + }; + const toggleTheme = async () => { if (theme === 'dark') { theme = 'light'; @@ -484,7 +498,7 @@ }; const getModels = async (type = 'all') => { - let models = []; + const models = []; models.push( ...(await getOllamaModels(localStorage.token).catch((error) => { toast.error(error); @@ -493,43 +507,13 @@ ); // If OpenAI API Key exists - if (type === 'all' && $settings.OPENAI_API_KEY) { - const OPENAI_API_BASE_URL = $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'; + if (type === 'all' && OPENAI_API_KEY) { + const openAIModels = await getOpenAIModels(localStorage.token).catch((error) => { + console.log(error); + return null; + }); - // Validate OPENAI_API_KEY - const openaiModelRes = await fetch(`${OPENAI_API_BASE_URL}/models`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${$settings.OPENAI_API_KEY}` - } - }) - .then(async (res) => { - if (!res.ok) throw await res.json(); - return res.json(); - }) - .catch((error) => { - console.log(error); - toast.error(`OpenAI: ${error?.error?.message ?? 'Network Problem'}`); - return null; - }); - - const openAIModels = Array.isArray(openaiModelRes) - ? openaiModelRes - : openaiModelRes?.data ?? null; - - models.push( - ...(openAIModels - ? [ - { name: 'hr' }, - ...openAIModels - .map((model) => ({ name: model.id, external: true })) - .filter((model) => - OPENAI_API_BASE_URL.includes('openai') ? model.name.includes('gpt') : true - ) - ] - : []) - ); + models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : [])); } return models; @@ -564,6 +548,8 @@ console.log('settings', $user.role === 'admin'); if ($user.role === 'admin') { API_BASE_URL = await getOllamaAPIUrl(localStorage.token); + OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token); + OPENAI_API_KEY = await getOpenAIKey(localStorage.token); } let settings = JSON.parse(localStorage.getItem('settings') ?? '{}'); @@ -584,9 +570,6 @@ options = { ...options, ...settings.options }; options.stop = (settings?.options?.stop ?? []).join(','); - OPENAI_API_KEY = settings.OPENAI_API_KEY ?? ''; - OPENAI_API_BASE_URL = settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'; - titleAutoGenerate = settings.titleAutoGenerate ?? true; speechAutoSend = settings.speechAutoSend ?? false; responseAutoCopy = settings.responseAutoCopy ?? false; @@ -1415,10 +1398,12 @@
{ - saveSettings({ - OPENAI_API_KEY: OPENAI_API_KEY !== '' ? OPENAI_API_KEY : undefined, - OPENAI_API_BASE_URL: OPENAI_API_BASE_URL !== '' ? OPENAI_API_BASE_URL : undefined - }); + updateOpenAIHandler(); + + // saveSettings({ + // OPENAI_API_KEY: OPENAI_API_KEY !== '' ? OPENAI_API_KEY : undefined, + // OPENAI_API_BASE_URL: OPENAI_API_BASE_URL !== '' ? OPENAI_API_BASE_URL : undefined + // }); show = false; }} > diff --git a/src/lib/constants.ts b/src/lib/constants.ts index c22ae207..27744197 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,11 +1,10 @@ import { dev } from '$app/environment'; -export const OLLAMA_API_BASE_URL = dev - ? `http://${location.hostname}:8080/ollama/api` - : '/ollama/api'; - export const WEBUI_BASE_URL = dev ? `http://${location.hostname}:8080` : ``; + export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`; +export const OLLAMA_API_BASE_URL = `${WEBUI_BASE_URL}/ollama/api`; +export const OPENAI_API_BASE_URL = `${WEBUI_BASE_URL}/openai/api`; export const WEB_UI_VERSION = 'v1.0.0-alpha-static'; diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 013638cb..c264592e 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -37,19 +37,17 @@ return []; })) ); - // If OpenAI API Key exists - if ($settings.OPENAI_API_KEY) { - const openAIModels = await getOpenAIModels( - $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1', - $settings.OPENAI_API_KEY - ).catch((error) => { - console.log(error); - toast.error(error); - return null; - }); - models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : [])); - } + // $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1', + // $settings.OPENAI_API_KEY + + const openAIModels = await getOpenAIModels(localStorage.token).catch((error) => { + console.log(error); + return null; + }); + + models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : [])); + return models; }; From c0b099da4fe06dff9a7686e3cbc329048da6f263 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 4 Jan 2024 18:54:00 -0800 Subject: [PATCH 3/8] feat: openai frontend refac --- backend/apps/openai/main.py | 1 + src/lib/apis/openai/index.ts | 23 +++++++ src/routes/(app)/+page.svelte | 96 ++++++++++++--------------- src/routes/(app)/c/[id]/+page.svelte | 97 ++++++++++++---------------- 4 files changed, 109 insertions(+), 108 deletions(-) diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index c6d06a9e..03d4621b 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -82,6 +82,7 @@ async def proxy(path: str, request: Request, user=Depends(get_current_user)): headers = {} headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}" + headers["Content-Type"] = "application/json" try: r = requests.request( diff --git a/src/lib/apis/openai/index.ts b/src/lib/apis/openai/index.ts index c1135fee..dcd92710 100644 --- a/src/lib/apis/openai/index.ts +++ b/src/lib/apis/openai/index.ts @@ -206,3 +206,26 @@ export const getOpenAIModelsDirect = async ( return a.name.localeCompare(b.name); }); }; + +export const generateOpenAIChatCompletion = async (token: string = '', body: object) => { + let error = null; + + const res = await fetch(`${OPENAI_API_BASE_URL}/chat/completions`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body) + }).catch((err) => { + console.log(err); + error = err; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 8fab09a2..f1c2a804 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -16,6 +16,7 @@ import ModelSelector from '$lib/components/chat/ModelSelector.svelte'; import Navbar from '$lib/components/layout/Navbar.svelte'; import { createNewChat, getChatList, updateChatById } from '$lib/apis/chats'; + import { generateOpenAIChatCompletion } from '$lib/apis/openai'; let stopResponseFlag = false; let autoScroll = true; @@ -345,60 +346,47 @@ window.scrollTo({ top: document.body.scrollHeight }); - const res = await fetch( - `${$settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'}/chat/completions`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${$settings.OPENAI_API_KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: model, - stream: true, - messages: [ - $settings.system - ? { - role: 'system', - content: $settings.system - } - : undefined, - ...messages - ] - .filter((message) => message) - .map((message) => ({ - role: message.role, - ...(message.files - ? { - content: [ - { - type: 'text', - text: message.content - }, - ...message.files - .filter((file) => file.type === 'image') - .map((file) => ({ - type: 'image_url', - image_url: { - url: file.url - } - })) - ] - } - : { content: message.content }) - })), - seed: $settings?.options?.seed ?? undefined, - stop: $settings?.options?.stop ?? undefined, - temperature: $settings?.options?.temperature ?? undefined, - top_p: $settings?.options?.top_p ?? undefined, - num_ctx: $settings?.options?.num_ctx ?? undefined, - frequency_penalty: $settings?.options?.repeat_penalty ?? undefined, - max_tokens: $settings?.options?.num_predict ?? undefined - }) - } - ).catch((err) => { - console.log(err); - return null; + const res = await generateOpenAIChatCompletion(localStorage.token, { + model: model, + stream: true, + messages: [ + $settings.system + ? { + role: 'system', + content: $settings.system + } + : undefined, + ...messages + ] + .filter((message) => message) + .map((message) => ({ + role: message.role, + ...(message.files + ? { + content: [ + { + type: 'text', + text: message.content + }, + ...message.files + .filter((file) => file.type === 'image') + .map((file) => ({ + type: 'image_url', + image_url: { + url: file.url + } + })) + ] + } + : { content: message.content }) + })), + seed: $settings?.options?.seed ?? undefined, + stop: $settings?.options?.stop ?? undefined, + temperature: $settings?.options?.temperature ?? undefined, + top_p: $settings?.options?.top_p ?? undefined, + num_ctx: $settings?.options?.num_ctx ?? undefined, + frequency_penalty: $settings?.options?.repeat_penalty ?? undefined, + max_tokens: $settings?.options?.num_predict ?? undefined }); if (res && res.ok) { diff --git a/src/routes/(app)/c/[id]/+page.svelte b/src/routes/(app)/c/[id]/+page.svelte index 61b1c768..f76b91cd 100644 --- a/src/routes/(app)/c/[id]/+page.svelte +++ b/src/routes/(app)/c/[id]/+page.svelte @@ -9,6 +9,8 @@ import { models, modelfiles, user, settings, chats, chatId } from '$lib/stores'; import { generateChatCompletion, generateTitle } from '$lib/apis/ollama'; + import { generateOpenAIChatCompletion } from '$lib/apis/openai'; + import { copyToClipboard, splitStream } from '$lib/utils'; import MessageInput from '$lib/components/chat/MessageInput.svelte'; @@ -362,60 +364,47 @@ window.scrollTo({ top: document.body.scrollHeight }); - const res = await fetch( - `${$settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'}/chat/completions`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${$settings.OPENAI_API_KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: model, - stream: true, - messages: [ - $settings.system - ? { - role: 'system', - content: $settings.system - } - : undefined, - ...messages - ] - .filter((message) => message) - .map((message) => ({ - role: message.role, - ...(message.files - ? { - content: [ - { - type: 'text', - text: message.content - }, - ...message.files - .filter((file) => file.type === 'image') - .map((file) => ({ - type: 'image_url', - image_url: { - url: file.url - } - })) - ] - } - : { content: message.content }) - })), - seed: $settings?.options?.seed ?? undefined, - stop: $settings?.options?.stop ?? undefined, - temperature: $settings?.options?.temperature ?? undefined, - top_p: $settings?.options?.top_p ?? undefined, - num_ctx: $settings?.options?.num_ctx ?? undefined, - frequency_penalty: $settings?.options?.repeat_penalty ?? undefined, - max_tokens: $settings?.options?.num_predict ?? undefined - }) - } - ).catch((err) => { - console.log(err); - return null; + const res = await generateOpenAIChatCompletion(localStorage.token, { + model: model, + stream: true, + messages: [ + $settings.system + ? { + role: 'system', + content: $settings.system + } + : undefined, + ...messages + ] + .filter((message) => message) + .map((message) => ({ + role: message.role, + ...(message.files + ? { + content: [ + { + type: 'text', + text: message.content + }, + ...message.files + .filter((file) => file.type === 'image') + .map((file) => ({ + type: 'image_url', + image_url: { + url: file.url + } + })) + ] + } + : { content: message.content }) + })), + seed: $settings?.options?.seed ?? undefined, + stop: $settings?.options?.stop ?? undefined, + temperature: $settings?.options?.temperature ?? undefined, + top_p: $settings?.options?.top_p ?? undefined, + num_ctx: $settings?.options?.num_ctx ?? undefined, + frequency_penalty: $settings?.options?.repeat_penalty ?? undefined, + max_tokens: $settings?.options?.num_predict ?? undefined }); if (res && res.ok) { From e278ca9ef7f7662cf596ef0fc6159ddbdf84307d Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 4 Jan 2024 18:55:15 -0800 Subject: [PATCH 4/8] chore: dockerfile update --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 7080d73b..504cfff6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,10 @@ ARG OLLAMA_API_BASE_URL='/ollama/api' ENV ENV=prod ENV OLLAMA_API_BASE_URL $OLLAMA_API_BASE_URL + +ENV OPENAI_API_BASE_URL "" +ENV OPENAI_API_KEY "" + ENV WEBUI_JWT_SECRET_KEY "SECRET_KEY" WORKDIR /app From 8c4528366fcfe1d308008d1bc0e5f89ee39f88b0 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 4 Jan 2024 19:01:03 -0800 Subject: [PATCH 5/8] feat: disable external tab for users with user role --- src/lib/components/chat/SettingsModal.svelte | 48 ++++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/lib/components/chat/SettingsModal.svelte b/src/lib/components/chat/SettingsModal.svelte index edb3e840..e13f37db 100644 --- a/src/lib/components/chat/SettingsModal.svelte +++ b/src/lib/components/chat/SettingsModal.svelte @@ -692,31 +692,31 @@
Models
- {/if} - + + {/if}