Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
8ed5759d
Unverified
Commit
8ed5759d
authored
Mar 06, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Mar 06, 2024
Browse files
Merge pull request #1071 from open-webui/dev
0.1.110
parents
bb98c10a
6c4e368a
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
321 additions
and
150 deletions
+321
-150
.dockerignore
.dockerignore
+0
-1
CHANGELOG.md
CHANGELOG.md
+12
-0
Dockerfile
Dockerfile
+2
-0
backend/apps/ollama/main.py
backend/apps/ollama/main.py
+12
-11
backend/apps/openai/main.py
backend/apps/openai/main.py
+173
-83
backend/apps/rag/main.py
backend/apps/rag/main.py
+1
-1
backend/config.py
backend/config.py
+16
-0
backend/constants.py
backend/constants.py
+2
-0
backend/requirements.txt
backend/requirements.txt
+2
-0
package.json
package.json
+1
-1
src/app.css
src/app.css
+4
-0
src/lib/apis/openai/index.ts
src/lib/apis/openai/index.ts
+14
-14
src/lib/components/chat/Settings/Connections.svelte
src/lib/components/chat/Settings/Connections.svelte
+79
-35
src/lib/components/documents/AddDocModal.svelte
src/lib/components/documents/AddDocModal.svelte
+3
-1
src/routes/(app)/+layout.svelte
src/routes/(app)/+layout.svelte
+0
-3
No files found.
.dockerignore
View file @
8ed5759d
...
@@ -7,7 +7,6 @@ node_modules
...
@@ -7,7 +7,6 @@ node_modules
/package
/package
.env
.env
.env.*
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
vite.config.ts.timestamp-*
__pycache__
__pycache__
...
...
CHANGELOG.md
View file @
8ed5759d
...
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
...
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on
[
Keep a Changelog
](
https://keepachangelog.com/en/1.1.0/
)
,
The format is based on
[
Keep a Changelog
](
https://keepachangelog.com/en/1.1.0/
)
,
and this project adheres to
[
Semantic Versioning
](
https://semver.org/spec/v2.0.0.html
)
.
and this project adheres to
[
Semantic Versioning
](
https://semver.org/spec/v2.0.0.html
)
.
## [0.1.110] - 2024-03-06
### Added
-
**🌐 Multiple OpenAI Servers Support**
: Enjoy seamless integration with multiple OpenAI-compatible APIs, now supported natively.
### Fixed
-
**🔍 OCR Issue**
: Resolved PDF parsing issue caused by OCR malfunction.
-
**🚫 RAG Issue**
: Fixed the RAG functionality, ensuring it operates smoothly.
-
**📄 "Add Docs" Model Button**
: Addressed the non-functional behavior of the "Add Docs" model button.
## [0.1.109] - 2024-03-06
## [0.1.109] - 2024-03-06
### Added
### Added
...
...
Dockerfile
View file @
8ed5759d
...
@@ -53,6 +53,8 @@ WORKDIR /app/backend
...
@@ -53,6 +53,8 @@ WORKDIR /app/backend
# install python dependencies
# install python dependencies
COPY
./backend/requirements.txt ./requirements.txt
COPY
./backend/requirements.txt ./requirements.txt
RUN
apt-get update
&&
apt-get
install
ffmpeg libsm6 libxext6
-y
RUN
pip3
install
torch torchvision torchaudio
--index-url
https://download.pytorch.org/whl/cpu
--no-cache-dir
RUN
pip3
install
torch torchvision torchaudio
--index-url
https://download.pytorch.org/whl/cpu
--no-cache-dir
RUN
pip3
install
-r
requirements.txt
--no-cache-dir
RUN
pip3
install
-r
requirements.txt
--no-cache-dir
...
...
backend/apps/ollama/main.py
View file @
8ed5759d
...
@@ -222,7 +222,7 @@ async def pull_model(
...
@@ -222,7 +222,7 @@ async def pull_model(
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"POST"
,
method
=
"POST"
,
url
=
f
"
{
url
}
/api/pull"
,
url
=
f
"
{
url
}
/api/pull"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
stream
=
True
,
stream
=
True
,
)
)
...
@@ -294,7 +294,7 @@ async def push_model(
...
@@ -294,7 +294,7 @@ async def push_model(
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"POST"
,
method
=
"POST"
,
url
=
f
"
{
url
}
/api/push"
,
url
=
f
"
{
url
}
/api/push"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
)
)
r
.
raise_for_status
()
r
.
raise_for_status
()
...
@@ -356,7 +356,7 @@ async def create_model(
...
@@ -356,7 +356,7 @@ async def create_model(
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"POST"
,
method
=
"POST"
,
url
=
f
"
{
url
}
/api/create"
,
url
=
f
"
{
url
}
/api/create"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
stream
=
True
,
stream
=
True
,
)
)
...
@@ -419,7 +419,7 @@ async def copy_model(
...
@@ -419,7 +419,7 @@ async def copy_model(
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"POST"
,
method
=
"POST"
,
url
=
f
"
{
url
}
/api/copy"
,
url
=
f
"
{
url
}
/api/copy"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
)
)
r
.
raise_for_status
()
r
.
raise_for_status
()
...
@@ -466,7 +466,7 @@ async def delete_model(
...
@@ -466,7 +466,7 @@ async def delete_model(
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"DELETE"
,
method
=
"DELETE"
,
url
=
f
"
{
url
}
/api/delete"
,
url
=
f
"
{
url
}
/api/delete"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
)
)
r
.
raise_for_status
()
r
.
raise_for_status
()
...
@@ -506,7 +506,7 @@ async def show_model_info(form_data: ModelNameForm, user=Depends(get_current_use
...
@@ -506,7 +506,7 @@ async def show_model_info(form_data: ModelNameForm, user=Depends(get_current_use
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"POST"
,
method
=
"POST"
,
url
=
f
"
{
url
}
/api/show"
,
url
=
f
"
{
url
}
/api/show"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
)
)
r
.
raise_for_status
()
r
.
raise_for_status
()
...
@@ -558,7 +558,7 @@ async def generate_embeddings(
...
@@ -558,7 +558,7 @@ async def generate_embeddings(
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"POST"
,
method
=
"POST"
,
url
=
f
"
{
url
}
/api/embeddings"
,
url
=
f
"
{
url
}
/api/embeddings"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
)
)
r
.
raise_for_status
()
r
.
raise_for_status
()
...
@@ -644,7 +644,7 @@ async def generate_completion(
...
@@ -644,7 +644,7 @@ async def generate_completion(
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"POST"
,
method
=
"POST"
,
url
=
f
"
{
url
}
/api/generate"
,
url
=
f
"
{
url
}
/api/generate"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
stream
=
True
,
stream
=
True
,
)
)
...
@@ -714,7 +714,7 @@ async def generate_chat_completion(
...
@@ -714,7 +714,7 @@ async def generate_chat_completion(
r
=
None
r
=
None
print
(
form_data
.
model_dump_json
(
exclude_none
=
True
))
print
(
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
)
def
get_request
():
def
get_request
():
nonlocal
form_data
nonlocal
form_data
...
@@ -744,7 +744,7 @@ async def generate_chat_completion(
...
@@ -744,7 +744,7 @@ async def generate_chat_completion(
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"POST"
,
method
=
"POST"
,
url
=
f
"
{
url
}
/api/chat"
,
url
=
f
"
{
url
}
/api/chat"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
stream
=
True
,
stream
=
True
,
)
)
...
@@ -756,6 +756,7 @@ async def generate_chat_completion(
...
@@ -756,6 +756,7 @@ async def generate_chat_completion(
headers
=
dict
(
r
.
headers
),
headers
=
dict
(
r
.
headers
),
)
)
except
Exception
as
e
:
except
Exception
as
e
:
print
(
e
)
raise
e
raise
e
try
:
try
:
...
@@ -843,7 +844,7 @@ async def generate_openai_chat_completion(
...
@@ -843,7 +844,7 @@ async def generate_openai_chat_completion(
r
=
requests
.
request
(
r
=
requests
.
request
(
method
=
"POST"
,
method
=
"POST"
,
url
=
f
"
{
url
}
/v1/chat/completions"
,
url
=
f
"
{
url
}
/v1/chat/completions"
,
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
),
data
=
form_data
.
model_dump_json
(
exclude_none
=
True
)
.
encode
()
,
stream
=
True
,
stream
=
True
,
)
)
...
...
backend/apps/openai/main.py
View file @
8ed5759d
...
@@ -3,7 +3,10 @@ from fastapi.middleware.cors import CORSMiddleware
...
@@ -3,7 +3,10 @@ from fastapi.middleware.cors import CORSMiddleware
from
fastapi.responses
import
StreamingResponse
,
JSONResponse
,
FileResponse
from
fastapi.responses
import
StreamingResponse
,
JSONResponse
,
FileResponse
import
requests
import
requests
import
aiohttp
import
asyncio
import
json
import
json
from
pydantic
import
BaseModel
from
pydantic
import
BaseModel
...
@@ -15,7 +18,9 @@ from utils.utils import (
...
@@ -15,7 +18,9 @@ from utils.utils import (
get_verified_user
,
get_verified_user
,
get_admin_user
,
get_admin_user
,
)
)
from
config
import
OPENAI_API_BASE_URL
,
OPENAI_API_KEY
,
CACHE_DIR
from
config
import
OPENAI_API_BASE_URLS
,
OPENAI_API_KEYS
,
CACHE_DIR
from
typing
import
List
,
Optional
import
hashlib
import
hashlib
from
pathlib
import
Path
from
pathlib
import
Path
...
@@ -29,116 +34,207 @@ app.add_middleware(
...
@@ -29,116 +34,207 @@ app.add_middleware(
allow_headers
=
[
"*"
],
allow_headers
=
[
"*"
],
)
)
app
.
state
.
OPENAI_API_BASE_URL
=
OPENAI_API_BASE_URL
app
.
state
.
OPENAI_API_BASE_URLS
=
OPENAI_API_BASE_URLS
app
.
state
.
OPENAI_API_KEY
=
OPENAI_API_KEY
app
.
state
.
OPENAI_API_KEYS
=
OPENAI_API_KEYS
app
.
state
.
MODELS
=
{}
@
app
.
middleware
(
"http"
)
async
def
check_url
(
request
:
Request
,
call_next
):
if
len
(
app
.
state
.
MODELS
)
==
0
:
await
get_all_models
()
else
:
pass
class
UrlUpdateForm
(
BaseModel
):
response
=
await
call_next
(
request
)
url
:
str
return
response
class
Key
UpdateForm
(
BaseModel
):
class
Urls
UpdateForm
(
BaseModel
):
key
:
str
urls
:
List
[
str
]
@
app
.
get
(
"/url"
)
class
KeysUpdateForm
(
BaseModel
):
async
def
get_openai_url
(
user
=
Depends
(
get_admin_user
)):
keys
:
List
[
str
]
return
{
"OPENAI_API_BASE_URL"
:
app
.
state
.
OPENAI_API_BASE_URL
}
@
app
.
post
(
"/url/update"
)
@
app
.
get
(
"/urls"
)
async
def
update_openai_url
(
form_data
:
UrlUpdateForm
,
user
=
Depends
(
get_admin_user
)):
async
def
get_openai_urls
(
user
=
Depends
(
get_admin_user
)):
app
.
state
.
OPENAI_API_BASE_URL
=
form_data
.
url
return
{
"OPENAI_API_BASE_URLS"
:
app
.
state
.
OPENAI_API_BASE_URLS
}
return
{
"OPENAI_API_BASE_URL"
:
app
.
state
.
OPENAI_API_BASE_URL
}
@
app
.
get
(
"/key"
)
@
app
.
post
(
"/urls/update"
)
async
def
get_openai_key
(
user
=
Depends
(
get_admin_user
)):
async
def
update_openai_urls
(
form_data
:
UrlsUpdateForm
,
user
=
Depends
(
get_admin_user
)):
return
{
"OPENAI_API_KEY"
:
app
.
state
.
OPENAI_API_KEY
}
app
.
state
.
OPENAI_API_BASE_URLS
=
form_data
.
urls
return
{
"OPENAI_API_BASE_URLS"
:
app
.
state
.
OPENAI_API_BASE_URLS
}
@
app
.
post
(
"/key/update"
)
@
app
.
get
(
"/keys"
)
async
def
update_openai_key
(
form_data
:
KeyUpdateForm
,
user
=
Depends
(
get_admin_user
)):
async
def
get_openai_keys
(
user
=
Depends
(
get_admin_user
)):
app
.
state
.
OPENAI_API_KEY
=
form_data
.
key
return
{
"OPENAI_API_KEYS"
:
app
.
state
.
OPENAI_API_KEYS
}
return
{
"OPENAI_API_KEY"
:
app
.
state
.
OPENAI_API_KEY
}
@
app
.
post
(
"/keys/update"
)
async
def
update_openai_key
(
form_data
:
KeysUpdateForm
,
user
=
Depends
(
get_admin_user
)):
app
.
state
.
OPENAI_API_KEYS
=
form_data
.
keys
return
{
"OPENAI_API_KEYS"
:
app
.
state
.
OPENAI_API_KEYS
}
@
app
.
post
(
"/audio/speech"
)
@
app
.
post
(
"/audio/speech"
)
async
def
speech
(
request
:
Request
,
user
=
Depends
(
get_verified_user
)):
async
def
speech
(
request
:
Request
,
user
=
Depends
(
get_verified_user
)):
target_url
=
f
"
{
app
.
state
.
OPENAI_API_BASE_URL
}
/audio/speech"
idx
=
None
try
:
idx
=
app
.
state
.
OPENAI_API_BASE_URLS
.
index
(
"https://api.openai.com/v1"
)
body
=
await
request
.
body
()
name
=
hashlib
.
sha256
(
body
).
hexdigest
()
SPEECH_CACHE_DIR
=
Path
(
CACHE_DIR
).
joinpath
(
"./audio/speech/"
)
SPEECH_CACHE_DIR
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
file_path
=
SPEECH_CACHE_DIR
.
joinpath
(
f
"
{
name
}
.mp3"
)
file_body_path
=
SPEECH_CACHE_DIR
.
joinpath
(
f
"
{
name
}
.json"
)
# Check if the file already exists in the cache
if
file_path
.
is_file
():
return
FileResponse
(
file_path
)
headers
=
{}
headers
[
"Authorization"
]
=
f
"Bearer
{
app
.
state
.
OPENAI_API_KEYS
[
idx
]
}
"
headers
[
"Content-Type"
]
=
"application/json"
try
:
r
=
requests
.
post
(
url
=
f
"
{
app
.
state
.
OPENAI_API_BASE_URLS
[
idx
]
}
/audio/speech"
,
data
=
body
,
headers
=
headers
,
stream
=
True
,
)
if
app
.
state
.
OPENAI_API_KEY
==
""
:
r
.
raise_for_status
()
raise
HTTPException
(
status_code
=
401
,
detail
=
ERROR_MESSAGES
.
API_KEY_NOT_FOUND
)
body
=
await
request
.
body
()
# Save the streaming content to a file
with
open
(
file_path
,
"wb"
)
as
f
:
for
chunk
in
r
.
iter_content
(
chunk_size
=
8192
):
f
.
write
(
chunk
)
name
=
hashlib
.
sha256
(
body
).
hexdigest
()
with
open
(
file_body_path
,
"w"
)
as
f
:
json
.
dump
(
json
.
loads
(
body
.
decode
(
"utf-8"
)),
f
)
SPEECH_CACHE_DIR
=
Path
(
CACHE_DIR
).
joinpath
(
"./audio/speech/"
)
# Return the saved file
SPEECH_CACHE_DIR
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
return
FileResponse
(
file_path
)
file_path
=
SPEECH_CACHE_DIR
.
joinpath
(
f
"
{
name
}
.mp3"
)
file_body_path
=
SPEECH_CACHE_DIR
.
joinpath
(
f
"
{
name
}
.json"
)
# Check if the file already exists in the cache
except
Exception
as
e
:
if
file_path
.
is_file
():
print
(
e
)
return
FileResponse
(
file_path
)
error_detail
=
"Open 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
)
except
ValueError
:
raise
HTTPException
(
status_code
=
401
,
detail
=
ERROR_MESSAGES
.
OPENAI_NOT_FOUND
)
headers
=
{}
headers
[
"Authorization"
]
=
f
"Bearer
{
app
.
state
.
OPENAI_API_KEY
}
"
headers
[
"Content-Type"
]
=
"application/json"
async
def
fetch_url
(
url
,
key
):
try
:
try
:
print
(
"openai"
)
headers
=
{
"Authorization"
:
f
"Bearer
{
key
}
"
}
r
=
requests
.
post
(
async
with
aiohttp
.
ClientSession
()
as
session
:
url
=
target_url
,
async
with
session
.
get
(
url
,
headers
=
headers
)
as
response
:
data
=
body
,
return
await
response
.
json
()
headers
=
headers
,
except
Exception
as
e
:
stream
=
True
,
# Handle connection error here
print
(
f
"Connection error:
{
e
}
"
)
return
None
def
merge_models_lists
(
model_lists
):
merged_list
=
[]
for
idx
,
models
in
enumerate
(
model_lists
):
merged_list
.
extend
(
[
{
**
model
,
"urlIdx"
:
idx
}
for
model
in
models
if
"api.openai.com"
not
in
app
.
state
.
OPENAI_API_BASE_URLS
[
idx
]
or
"gpt"
in
model
[
"id"
]
]
)
)
r
.
raise_for_status
()
r
eturn
merged_list
# Save the streaming content to a file
with
open
(
file_path
,
"wb"
)
as
f
:
for
chunk
in
r
.
iter_content
(
chunk_size
=
8192
):
f
.
write
(
chunk
)
with
open
(
file_body_path
,
"w"
)
as
f
:
async
def
get_all_models
():
json
.
dump
(
json
.
loads
(
body
.
decode
(
"utf-8"
)),
f
)
print
(
"get_all_models"
)
tasks
=
[
fetch_url
(
f
"
{
url
}
/models"
,
app
.
state
.
OPENAI_API_KEYS
[
idx
])
for
idx
,
url
in
enumerate
(
app
.
state
.
OPENAI_API_BASE_URLS
)
]
responses
=
await
asyncio
.
gather
(
*
tasks
)
responses
=
list
(
filter
(
lambda
x
:
x
is
not
None
and
"error"
not
in
x
,
responses
))
models
=
{
"data"
:
merge_models_lists
(
list
(
map
(
lambda
response
:
response
[
"data"
],
responses
))
)
}
app
.
state
.
MODELS
=
{
model
[
"id"
]:
model
for
model
in
models
[
"data"
]}
# Return the saved file
return
models
return
FileResponse
(
file_path
)
except
Exception
as
e
:
print
(
e
)
error_detail
=
"Open 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
)
# , user=Depends(get_current_user)
@
app
.
get
(
"/models"
)
@
app
.
get
(
"/models/{url_idx}"
)
async
def
get_models
(
url_idx
:
Optional
[
int
]
=
None
):
if
url_idx
==
None
:
return
await
get_all_models
()
else
:
url
=
app
.
state
.
OPENAI_API_BASE_URLS
[
url_idx
]
try
:
r
=
requests
.
request
(
method
=
"GET"
,
url
=
f
"
{
url
}
/models"
)
r
.
raise_for_status
()
response_data
=
r
.
json
()
if
"api.openai.com"
in
url
:
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
=
"Open 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
if
r
else
500
,
detail
=
error_detail
,
)
@
app
.
api_route
(
"/{path:path}"
,
methods
=
[
"GET"
,
"POST"
,
"PUT"
,
"DELETE"
])
@
app
.
api_route
(
"/{path:path}"
,
methods
=
[
"GET"
,
"POST"
,
"PUT"
,
"DELETE"
])
async
def
proxy
(
path
:
str
,
request
:
Request
,
user
=
Depends
(
get_verified_user
)):
async
def
proxy
(
path
:
str
,
request
:
Request
,
user
=
Depends
(
get_verified_user
)):
target_url
=
f
"
{
app
.
state
.
OPENAI_API_BASE_URL
}
/
{
path
}
"
idx
=
0
print
(
target_url
,
app
.
state
.
OPENAI_API_KEY
)
if
app
.
state
.
OPENAI_API_KEY
==
""
:
raise
HTTPException
(
status_code
=
401
,
detail
=
ERROR_MESSAGES
.
API_KEY_NOT_FOUND
)
body
=
await
request
.
body
()
body
=
await
request
.
body
()
# TODO: Remove below after gpt-4-vision fix from Open AI
# TODO: Remove below after gpt-4-vision fix from Open AI
# Try to decode the body of the request from bytes to a UTF-8 string (Require add max_token to fix gpt-4-vision)
# Try to decode the body of the request from bytes to a UTF-8 string (Require add max_token to fix gpt-4-vision)
try
:
try
:
body
=
body
.
decode
(
"utf-8"
)
body
=
body
.
decode
(
"utf-8"
)
body
=
json
.
loads
(
body
)
body
=
json
.
loads
(
body
)
idx
=
app
.
state
.
MODELS
[
body
.
get
(
"model"
)][
"urlIdx"
]
# Check if the model is "gpt-4-vision-preview" and set "max_tokens" to 4000
# Check if the model is "gpt-4-vision-preview" and set "max_tokens" to 4000
# This is a workaround until OpenAI fixes the issue with this model
# This is a workaround until OpenAI fixes the issue with this model
if
body
.
get
(
"model"
)
==
"gpt-4-vision-preview"
:
if
body
.
get
(
"model"
)
==
"gpt-4-vision-preview"
:
...
@@ -158,8 +254,16 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
...
@@ -158,8 +254,16 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
except
json
.
JSONDecodeError
as
e
:
except
json
.
JSONDecodeError
as
e
:
print
(
"Error loading request body into a dictionary:"
,
e
)
print
(
"Error loading request body into a dictionary:"
,
e
)
url
=
app
.
state
.
OPENAI_API_BASE_URLS
[
idx
]
key
=
app
.
state
.
OPENAI_API_KEYS
[
idx
]
target_url
=
f
"
{
url
}
/
{
path
}
"
if
key
==
""
:
raise
HTTPException
(
status_code
=
401
,
detail
=
ERROR_MESSAGES
.
API_KEY_NOT_FOUND
)
headers
=
{}
headers
=
{}
headers
[
"Authorization"
]
=
f
"Bearer
{
app
.
state
.
OPENAI_API_KEY
}
"
headers
[
"Authorization"
]
=
f
"Bearer
{
key
}
"
headers
[
"Content-Type"
]
=
"application/json"
headers
[
"Content-Type"
]
=
"application/json"
try
:
try
:
...
@@ -181,21 +285,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
...
@@ -181,21 +285,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
headers
=
dict
(
r
.
headers
),
headers
=
dict
(
r
.
headers
),
)
)
else
:
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
()
response_data
=
r
.
json
()
if
"api.openai.com"
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
return
response_data
except
Exception
as
e
:
except
Exception
as
e
:
print
(
e
)
print
(
e
)
...
...
backend/apps/rag/main.py
View file @
8ed5759d
...
@@ -425,7 +425,7 @@ def get_loader(filename: str, file_content_type: str, file_path: str):
...
@@ -425,7 +425,7 @@ def get_loader(filename: str, file_content_type: str, file_path: str):
]
]
if
file_ext
==
"pdf"
:
if
file_ext
==
"pdf"
:
loader
=
PyPDFLoader
(
file_path
)
loader
=
PyPDFLoader
(
file_path
,
extract_images
=
True
)
elif
file_ext
==
"csv"
:
elif
file_ext
==
"csv"
:
loader
=
CSVLoader
(
file_path
)
loader
=
CSVLoader
(
file_path
)
elif
file_ext
==
"rst"
:
elif
file_ext
==
"rst"
:
...
...
backend/config.py
View file @
8ed5759d
...
@@ -234,9 +234,25 @@ OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(";")]
...
@@ -234,9 +234,25 @@ OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(";")]
OPENAI_API_KEY
=
os
.
environ
.
get
(
"OPENAI_API_KEY"
,
""
)
OPENAI_API_KEY
=
os
.
environ
.
get
(
"OPENAI_API_KEY"
,
""
)
OPENAI_API_BASE_URL
=
os
.
environ
.
get
(
"OPENAI_API_BASE_URL"
,
""
)
OPENAI_API_BASE_URL
=
os
.
environ
.
get
(
"OPENAI_API_BASE_URL"
,
""
)
if
OPENAI_API_KEY
==
""
:
OPENAI_API_KEY
=
"none"
if
OPENAI_API_BASE_URL
==
""
:
if
OPENAI_API_BASE_URL
==
""
:
OPENAI_API_BASE_URL
=
"https://api.openai.com/v1"
OPENAI_API_BASE_URL
=
"https://api.openai.com/v1"
OPENAI_API_KEYS
=
os
.
environ
.
get
(
"OPENAI_API_KEYS"
,
""
)
OPENAI_API_KEYS
=
OPENAI_API_KEYS
if
OPENAI_API_KEYS
!=
""
else
OPENAI_API_KEY
OPENAI_API_KEYS
=
[
url
.
strip
()
for
url
in
OPENAI_API_KEYS
.
split
(
";"
)]
OPENAI_API_BASE_URLS
=
os
.
environ
.
get
(
"OPENAI_API_BASE_URLS"
,
""
)
OPENAI_API_BASE_URLS
=
(
OPENAI_API_BASE_URLS
if
OPENAI_API_BASE_URLS
!=
""
else
OPENAI_API_BASE_URL
)
OPENAI_API_BASE_URLS
=
[
url
.
strip
()
for
url
in
OPENAI_API_BASE_URL
.
split
(
";"
)]
####################################
####################################
# WEBUI
# WEBUI
...
...
backend/constants.py
View file @
8ed5759d
...
@@ -41,6 +41,7 @@ class ERROR_MESSAGES(str, Enum):
...
@@ -41,6 +41,7 @@ class ERROR_MESSAGES(str, Enum):
NOT_FOUND
=
"We could not find what you're looking for :/"
NOT_FOUND
=
"We could not find what you're looking for :/"
USER_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."
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."
MALICIOUS
=
"Unusual activities detected, please try again in a few minutes."
PANDOC_NOT_INSTALLED
=
"Pandoc is not installed on the server. Please contact your administrator for assistance."
PANDOC_NOT_INSTALLED
=
"Pandoc is not installed on the server. Please contact your administrator for assistance."
...
@@ -50,3 +51,4 @@ class ERROR_MESSAGES(str, Enum):
...
@@ -50,3 +51,4 @@ class ERROR_MESSAGES(str, Enum):
RATE_LIMIT_EXCEEDED
=
"API rate limit exceeded"
RATE_LIMIT_EXCEEDED
=
"API rate limit exceeded"
MODEL_NOT_FOUND
=
lambda
name
=
""
:
f
"Model '
{
name
}
' was not found"
MODEL_NOT_FOUND
=
lambda
name
=
""
:
f
"Model '
{
name
}
' was not found"
OPENAI_NOT_FOUND
=
lambda
name
=
""
:
f
"OpenAI API was not found"
backend/requirements.txt
View file @
8ed5759d
...
@@ -34,6 +34,8 @@ pandas
...
@@ -34,6 +34,8 @@ pandas
openpyxl
openpyxl
pyxlsb
pyxlsb
xlrd
xlrd
opencv-python-headless
rapidocr-onnxruntime
rapidocr-onnxruntime
faster-whisper
faster-whisper
...
...
package.json
View file @
8ed5759d
{
{
"name"
:
"open-webui"
,
"name"
:
"open-webui"
,
"version"
:
"0.1.10
9
"
,
"version"
:
"0.1.1
1
0"
,
"private"
:
true
,
"private"
:
true
,
"scripts"
:
{
"scripts"
:
{
"dev"
:
"vite dev --host"
,
"dev"
:
"vite dev --host"
,
...
...
src/app.css
View file @
8ed5759d
...
@@ -43,6 +43,10 @@ ol > li {
...
@@ -43,6 +43,10 @@ ol > li {
font-weight
:
400
;
font-weight
:
400
;
}
}
li
p
{
display
:
inline
;
}
::-webkit-scrollbar-thumb
{
::-webkit-scrollbar-thumb
{
--tw-border-opacity
:
1
;
--tw-border-opacity
:
1
;
background-color
:
rgba
(
217
,
217
,
227
,
0.8
);
background-color
:
rgba
(
217
,
217
,
227
,
0.8
);
...
...
src/lib/apis/openai/index.ts
View file @
8ed5759d
import
{
OPENAI_API_BASE_URL
}
from
'
$lib/constants
'
;
import
{
OPENAI_API_BASE_URL
}
from
'
$lib/constants
'
;
export
const
getOpenAIUrl
=
async
(
token
:
string
=
''
)
=>
{
export
const
getOpenAIUrl
s
=
async
(
token
:
string
=
''
)
=>
{
let
error
=
null
;
let
error
=
null
;
const
res
=
await
fetch
(
`
${
OPENAI_API_BASE_URL
}
/url`
,
{
const
res
=
await
fetch
(
`
${
OPENAI_API_BASE_URL
}
/url
s
`
,
{
method
:
'
GET
'
,
method
:
'
GET
'
,
headers
:
{
headers
:
{
Accept
:
'
application/json
'
,
Accept
:
'
application/json
'
,
...
@@ -29,13 +29,13 @@ export const getOpenAIUrl = async (token: string = '') => {
...
@@ -29,13 +29,13 @@ export const getOpenAIUrl = async (token: string = '') => {
throw
error
;
throw
error
;
}
}
return
res
.
OPENAI_API_BASE_URL
;
return
res
.
OPENAI_API_BASE_URL
S
;
};
};
export
const
updateOpenAIUrl
=
async
(
token
:
string
=
''
,
url
:
string
)
=>
{
export
const
updateOpenAIUrl
s
=
async
(
token
:
string
=
''
,
url
s
:
string
[]
)
=>
{
let
error
=
null
;
let
error
=
null
;
const
res
=
await
fetch
(
`
${
OPENAI_API_BASE_URL
}
/url/update`
,
{
const
res
=
await
fetch
(
`
${
OPENAI_API_BASE_URL
}
/url
s
/update`
,
{
method
:
'
POST
'
,
method
:
'
POST
'
,
headers
:
{
headers
:
{
Accept
:
'
application/json
'
,
Accept
:
'
application/json
'
,
...
@@ -43,7 +43,7 @@ export const updateOpenAIUrl = async (token: string = '', url: string) => {
...
@@ -43,7 +43,7 @@ export const updateOpenAIUrl = async (token: string = '', url: string) => {
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
},
},
body
:
JSON
.
stringify
({
body
:
JSON
.
stringify
({
url
:
url
url
s
:
url
s
})
})
})
})
.
then
(
async
(
res
)
=>
{
.
then
(
async
(
res
)
=>
{
...
@@ -64,13 +64,13 @@ export const updateOpenAIUrl = async (token: string = '', url: string) => {
...
@@ -64,13 +64,13 @@ export const updateOpenAIUrl = async (token: string = '', url: string) => {
throw
error
;
throw
error
;
}
}
return
res
.
OPENAI_API_BASE_URL
;
return
res
.
OPENAI_API_BASE_URL
S
;
};
};
export
const
getOpenAIKey
=
async
(
token
:
string
=
''
)
=>
{
export
const
getOpenAIKey
s
=
async
(
token
:
string
=
''
)
=>
{
let
error
=
null
;
let
error
=
null
;
const
res
=
await
fetch
(
`
${
OPENAI_API_BASE_URL
}
/key`
,
{
const
res
=
await
fetch
(
`
${
OPENAI_API_BASE_URL
}
/key
s
`
,
{
method
:
'
GET
'
,
method
:
'
GET
'
,
headers
:
{
headers
:
{
Accept
:
'
application/json
'
,
Accept
:
'
application/json
'
,
...
@@ -96,13 +96,13 @@ export const getOpenAIKey = async (token: string = '') => {
...
@@ -96,13 +96,13 @@ export const getOpenAIKey = async (token: string = '') => {
throw
error
;
throw
error
;
}
}
return
res
.
OPENAI_API_KEY
;
return
res
.
OPENAI_API_KEY
S
;
};
};
export
const
updateOpenAIKey
=
async
(
token
:
string
=
''
,
key
:
string
)
=>
{
export
const
updateOpenAIKey
s
=
async
(
token
:
string
=
''
,
key
s
:
string
[]
)
=>
{
let
error
=
null
;
let
error
=
null
;
const
res
=
await
fetch
(
`
${
OPENAI_API_BASE_URL
}
/key/update`
,
{
const
res
=
await
fetch
(
`
${
OPENAI_API_BASE_URL
}
/key
s
/update`
,
{
method
:
'
POST
'
,
method
:
'
POST
'
,
headers
:
{
headers
:
{
Accept
:
'
application/json
'
,
Accept
:
'
application/json
'
,
...
@@ -110,7 +110,7 @@ export const updateOpenAIKey = async (token: string = '', key: string) => {
...
@@ -110,7 +110,7 @@ export const updateOpenAIKey = async (token: string = '', key: string) => {
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
...(
token
&&
{
authorization
:
`Bearer
${
token
}
`
})
},
},
body
:
JSON
.
stringify
({
body
:
JSON
.
stringify
({
key
:
key
key
s
:
key
s
})
})
})
})
.
then
(
async
(
res
)
=>
{
.
then
(
async
(
res
)
=>
{
...
@@ -131,7 +131,7 @@ export const updateOpenAIKey = async (token: string = '', key: string) => {
...
@@ -131,7 +131,7 @@ export const updateOpenAIKey = async (token: string = '', key: string) => {
throw
error
;
throw
error
;
}
}
return
res
.
OPENAI_API_KEY
;
return
res
.
OPENAI_API_KEY
S
;
};
};
export
const
getOpenAIModels
=
async
(
token
:
string
=
''
)
=>
{
export
const
getOpenAIModels
=
async
(
token
:
string
=
''
)
=>
{
...
...
src/lib/components/chat/Settings/Connections.svelte
View file @
8ed5759d
...
@@ -4,7 +4,12 @@
...
@@ -4,7 +4,12 @@
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
import {
getOpenAIKeys,
getOpenAIUrls,
updateOpenAIKeys,
updateOpenAIUrls
} from '$lib/apis/openai';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
export let getModels: Function;
export let getModels: Function;
...
@@ -16,12 +21,14 @@
...
@@ -16,12 +21,14 @@
let OPENAI_API_KEY = '';
let OPENAI_API_KEY = '';
let OPENAI_API_BASE_URL = '';
let OPENAI_API_BASE_URL = '';
let OPENAI_API_KEYS = [''];
let OPENAI_API_BASE_URLS = [''];
let showOpenAI = false;
let showOpenAI = false;
let showLiteLLM = false;
const updateOpenAIHandler = async () => {
const updateOpenAIHandler = async () => {
OPENAI_API_BASE_URL = await updateOpenAIUrl(localStorage.token, OPENAI_API_BASE_URL);
OPENAI_API_BASE_URL
S
= await updateOpenAIUrl
s
(localStorage.token, OPENAI_API_BASE_URL
S
);
OPENAI_API_KEY = await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
OPENAI_API_KEY
S
= await updateOpenAIKey
s
(localStorage.token, OPENAI_API_KEY
S
);
await models.set(await getModels());
await models.set(await getModels());
};
};
...
@@ -43,8 +50,8 @@
...
@@ -43,8 +50,8 @@
onMount(async () => {
onMount(async () => {
if ($user.role === 'admin') {
if ($user.role === 'admin') {
OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
OPENAI_API_BASE_URL
S
= await getOpenAIUrl
s
(localStorage.token);
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
OPENAI_API_KEY
S
= await getOpenAIKey
s
(localStorage.token);
}
}
});
});
</script>
</script>
...
@@ -71,37 +78,74 @@
...
@@ -71,37 +78,74 @@
</div>
</div>
{#if showOpenAI}
{#if showOpenAI}
<div>
<div class="flex flex-col gap-1">
<div class=" mb-2.5 text-sm font-medium">API Key</div>
{#each OPENAI_API_BASE_URLS as url, idx}
<div class="flex w-full">
<div class="flex w-full gap-2">
<div class="flex-1">
<div class="flex-1">
<input
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter OpenAI API Key"
placeholder="API Base URL"
bind:value={OPENAI_API_KEY}
bind:value={url}
autocomplete="off"
autocomplete="off"
/>
/>
</div>
</div>
</div>
</div>
<div>
<div class="flex-1">
<div class=" mb-2.5 text-sm font-medium">API Base URL</div>
<input
<div class="flex w-full">
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
<div class="flex-1">
placeholder="API Key"
<input
bind:value={OPENAI_API_KEYS[idx]}
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
autocomplete="off"
placeholder="Enter OpenAI API Base URL"
/>
bind:value={OPENAI_API_BASE_URL}
</div>
autocomplete="off"
<div class="self-center flex items-center">
/>
{#if idx === 0}
<button
class="px-1"
on:click={() => {
OPENAI_API_BASE_URLS = [...OPENAI_API_BASE_URLS, ''];
OPENAI_API_KEYS = [...OPENAI_API_KEYS, ''];
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
/>
</svg>
</button>
{:else}
<button
class="px-1"
on:click={() => {
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
(url, urlIdx) => idx !== urlIdx
);
OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
</svg>
</button>
{/if}
</div>
</div>
</div>
</div>
<div class=" mb-1 text-xs text-gray-400 dark:text-gray-500">
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
WebUI will make requests to <span class=" text-gray-200">'{url}/models'</span>
WebUI will make requests to <span class=" text-gray-200"
</div>
>'{OPENAI_API_BASE_URL}/chat'</span
{/each}
>
</div>
</div>
</div>
{/if}
{/if}
</div>
</div>
...
...
src/lib/components/documents/AddDocModal.svelte
View file @
8ed5759d
...
@@ -138,7 +138,9 @@
...
@@ -138,7 +138,9 @@
<button
<button
class="w-full text-sm font-medium py-3 bg-gray-850 hover:bg-gray-800 text-center rounded-xl"
class="w-full text-sm font-medium py-3 bg-gray-850 hover:bg-gray-800 text-center rounded-xl"
type="button"
type="button"
on:click={uploadDocInputElement.click}
on:click={() => {
uploadDocInputElement.click();
}}
>
>
{#if inputFiles}
{#if inputFiles}
{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
...
...
src/routes/(app)/+layout.svelte
View file @
8ed5759d
...
@@ -97,14 +97,11 @@
...
@@ -97,14 +97,11 @@
if (localDBChats.length === 0) {
if (localDBChats.length === 0) {
await deleteDB('Chats');
await deleteDB('Chats');
}
}
console.log('localdb', localDBChats);
}
}
console.log(DB);
console.log(DB);
} catch (error) {
} catch (error) {
// IndexedDB Not Found
// IndexedDB Not Found
console.log('IDB Not Found');
}
}
console.log();
console.log();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment