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
af4caec4
"src/git@developer.sourcefind.cn:chenpangpang/open-webui.git" did not exist on "ccf08fb91e3960b1b8457306ac231d18dc7f21e2"
Commit
af4caec4
authored
Mar 21, 2024
by
Timothy J. Baek
Browse files
refac: ollama gguf upload
parent
c54fb922
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
269 additions
and
177 deletions
+269
-177
backend/apps/ollama/main.py
backend/apps/ollama/main.py
+183
-3
backend/apps/web/routers/utils.py
backend/apps/web/routers/utils.py
+0
-149
src/lib/apis/ollama/index.ts
src/lib/apis/ollama/index.ts
+65
-0
src/lib/components/chat/Settings/Models.svelte
src/lib/components/chat/Settings/Models.svelte
+21
-25
No files found.
backend/apps/ollama/main.py
View file @
af4caec4
from
fastapi
import
FastAPI
,
Request
,
Response
,
HTTPException
,
Depends
,
status
from
fastapi
import
(
FastAPI
,
Request
,
Response
,
HTTPException
,
Depends
,
status
,
UploadFile
,
File
,
BackgroundTasks
,
)
from
fastapi.middleware.cors
import
CORSMiddleware
from
fastapi.middleware.cors
import
CORSMiddleware
from
fastapi.responses
import
StreamingResponse
from
fastapi.responses
import
StreamingResponse
from
fastapi.concurrency
import
run_in_threadpool
from
fastapi.concurrency
import
run_in_threadpool
from
pydantic
import
BaseModel
,
ConfigDict
from
pydantic
import
BaseModel
,
ConfigDict
import
os
import
random
import
random
import
requests
import
requests
import
json
import
json
import
uuid
import
uuid
import
aiohttp
import
aiohttp
import
asyncio
import
asyncio
import
aiofiles
from
urllib.parse
import
urlparse
from
typing
import
Optional
,
List
,
Union
from
apps.web.models.users
import
Users
from
apps.web.models.users
import
Users
from
constants
import
ERROR_MESSAGES
from
constants
import
ERROR_MESSAGES
from
utils.utils
import
decode_token
,
get_current_user
,
get_admin_user
from
utils.utils
import
decode_token
,
get_current_user
,
get_admin_user
from
config
import
OLLAMA_BASE_URLS
,
MODEL_FILTER_ENABLED
,
MODEL_FILTER_LIST
from
utils.misc
import
calculate_sha256
from
typing
import
Optional
,
List
,
Union
from
config
import
OLLAMA_BASE_URLS
,
MODEL_FILTER_ENABLED
,
MODEL_FILTER_LIST
,
UPLOAD_DIR
app
=
FastAPI
()
app
=
FastAPI
()
...
@@ -897,6 +913,170 @@ async def generate_openai_chat_completion(
...
@@ -897,6 +913,170 @@ async def generate_openai_chat_completion(
)
)
class
UrlForm
(
BaseModel
):
url
:
str
class
UploadBlobForm
(
BaseModel
):
filename
:
str
def
parse_huggingface_url
(
hf_url
):
try
:
# Parse the URL
parsed_url
=
urlparse
(
hf_url
)
# Get the path and split it into components
path_components
=
parsed_url
.
path
.
split
(
"/"
)
# Extract the desired output
user_repo
=
"/"
.
join
(
path_components
[
1
:
3
])
model_file
=
path_components
[
-
1
]
return
model_file
except
ValueError
:
return
None
async
def
download_file_stream
(
ollama_url
,
file_url
,
file_path
,
file_name
,
chunk_size
=
1024
*
1024
):
done
=
False
if
os
.
path
.
exists
(
file_path
):
current_size
=
os
.
path
.
getsize
(
file_path
)
else
:
current_size
=
0
headers
=
{
"Range"
:
f
"bytes=
{
current_size
}
-"
}
if
current_size
>
0
else
{}
timeout
=
aiohttp
.
ClientTimeout
(
total
=
600
)
# Set the timeout
async
with
aiohttp
.
ClientSession
(
timeout
=
timeout
)
as
session
:
async
with
session
.
get
(
file_url
,
headers
=
headers
)
as
response
:
total_size
=
int
(
response
.
headers
.
get
(
"content-length"
,
0
))
+
current_size
with
open
(
file_path
,
"ab+"
)
as
file
:
async
for
data
in
response
.
content
.
iter_chunked
(
chunk_size
):
current_size
+=
len
(
data
)
file
.
write
(
data
)
done
=
current_size
==
total_size
progress
=
round
((
current_size
/
total_size
)
*
100
,
2
)
yield
f
'data: {{"progress":
{
progress
}
, "completed":
{
current_size
}
, "total":
{
total_size
}
}}
\n\n
'
if
done
:
file
.
seek
(
0
)
hashed
=
calculate_sha256
(
file
)
file
.
seek
(
0
)
url
=
f
"
{
ollama_url
}
/api/blobs/sha256:
{
hashed
}
"
response
=
requests
.
post
(
url
,
data
=
file
)
if
response
.
ok
:
res
=
{
"done"
:
done
,
"blob"
:
f
"sha256:
{
hashed
}
"
,
"name"
:
file_name
,
}
os
.
remove
(
file_path
)
yield
f
"data:
{
json
.
dumps
(
res
)
}
\n\n
"
else
:
raise
"Ollama: Could not create blob, Please try again."
# url = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf"
@
app
.
post
(
"/models/download"
)
@
app
.
post
(
"/models/download/{url_idx}"
)
async
def
download_model
(
form_data
:
UrlForm
,
url_idx
:
Optional
[
int
]
=
None
,
):
if
url_idx
==
None
:
url_idx
=
0
url
=
app
.
state
.
OLLAMA_BASE_URLS
[
url_idx
]
file_name
=
parse_huggingface_url
(
form_data
.
url
)
if
file_name
:
file_path
=
f
"
{
UPLOAD_DIR
}
/
{
file_name
}
"
return
StreamingResponse
(
download_file_stream
(
url
,
form_data
.
url
,
file_path
,
file_name
)
)
else
:
return
None
@
app
.
post
(
"/models/upload"
)
@
app
.
post
(
"/models/upload/{url_idx}"
)
def
upload_model
(
file
:
UploadFile
=
File
(...),
url_idx
:
Optional
[
int
]
=
None
):
if
url_idx
==
None
:
url_idx
=
0
ollama_url
=
app
.
state
.
OLLAMA_BASE_URLS
[
url_idx
]
file_path
=
f
"
{
UPLOAD_DIR
}
/
{
file
.
filename
}
"
# Save file in chunks
with
open
(
file_path
,
"wb+"
)
as
f
:
for
chunk
in
file
.
file
:
f
.
write
(
chunk
)
def
file_process_stream
():
nonlocal
ollama_url
total_size
=
os
.
path
.
getsize
(
file_path
)
chunk_size
=
1024
*
1024
try
:
with
open
(
file_path
,
"rb"
)
as
f
:
total
=
0
done
=
False
while
not
done
:
chunk
=
f
.
read
(
chunk_size
)
if
not
chunk
:
done
=
True
continue
total
+=
len
(
chunk
)
progress
=
round
((
total
/
total_size
)
*
100
,
2
)
res
=
{
"progress"
:
progress
,
"total"
:
total_size
,
"completed"
:
total
,
}
yield
f
"data:
{
json
.
dumps
(
res
)
}
\n\n
"
if
done
:
f
.
seek
(
0
)
hashed
=
calculate_sha256
(
f
)
f
.
seek
(
0
)
url
=
f
"
{
ollama_url
}
/blobs/sha256:
{
hashed
}
"
response
=
requests
.
post
(
url
,
data
=
f
)
if
response
.
ok
:
res
=
{
"done"
:
done
,
"blob"
:
f
"sha256:
{
hashed
}
"
,
"name"
:
file
.
filename
,
}
os
.
remove
(
file_path
)
yield
f
"data:
{
json
.
dumps
(
res
)
}
\n\n
"
else
:
raise
Exception
(
"Ollama: Could not create blob, Please try again."
)
except
Exception
as
e
:
res
=
{
"error"
:
str
(
e
)}
yield
f
"data:
{
json
.
dumps
(
res
)
}
\n\n
"
return
StreamingResponse
(
file_process_stream
(),
media_type
=
"text/event-stream"
)
@
app
.
api_route
(
"/{path:path}"
,
methods
=
[
"GET"
,
"POST"
,
"PUT"
,
"DELETE"
])
@
app
.
api_route
(
"/{path:path}"
,
methods
=
[
"GET"
,
"POST"
,
"PUT"
,
"DELETE"
])
async
def
deprecated_proxy
(
path
:
str
,
request
:
Request
,
user
=
Depends
(
get_current_user
)):
async
def
deprecated_proxy
(
path
:
str
,
request
:
Request
,
user
=
Depends
(
get_current_user
)):
url
=
app
.
state
.
OLLAMA_BASE_URLS
[
0
]
url
=
app
.
state
.
OLLAMA_BASE_URLS
[
0
]
...
...
backend/apps/web/routers/utils.py
View file @
af4caec4
...
@@ -21,155 +21,6 @@ from constants import ERROR_MESSAGES
...
@@ -21,155 +21,6 @@ from constants import ERROR_MESSAGES
router
=
APIRouter
()
router
=
APIRouter
()
class
UploadBlobForm
(
BaseModel
):
filename
:
str
from
urllib.parse
import
urlparse
def
parse_huggingface_url
(
hf_url
):
try
:
# Parse the URL
parsed_url
=
urlparse
(
hf_url
)
# Get the path and split it into components
path_components
=
parsed_url
.
path
.
split
(
"/"
)
# Extract the desired output
user_repo
=
"/"
.
join
(
path_components
[
1
:
3
])
model_file
=
path_components
[
-
1
]
return
model_file
except
ValueError
:
return
None
async
def
download_file_stream
(
url
,
file_path
,
file_name
,
chunk_size
=
1024
*
1024
):
done
=
False
if
os
.
path
.
exists
(
file_path
):
current_size
=
os
.
path
.
getsize
(
file_path
)
else
:
current_size
=
0
headers
=
{
"Range"
:
f
"bytes=
{
current_size
}
-"
}
if
current_size
>
0
else
{}
timeout
=
aiohttp
.
ClientTimeout
(
total
=
600
)
# Set the timeout
async
with
aiohttp
.
ClientSession
(
timeout
=
timeout
)
as
session
:
async
with
session
.
get
(
url
,
headers
=
headers
)
as
response
:
total_size
=
int
(
response
.
headers
.
get
(
"content-length"
,
0
))
+
current_size
with
open
(
file_path
,
"ab+"
)
as
file
:
async
for
data
in
response
.
content
.
iter_chunked
(
chunk_size
):
current_size
+=
len
(
data
)
file
.
write
(
data
)
done
=
current_size
==
total_size
progress
=
round
((
current_size
/
total_size
)
*
100
,
2
)
yield
f
'data: {{"progress":
{
progress
}
, "completed":
{
current_size
}
, "total":
{
total_size
}
}}
\n\n
'
if
done
:
file
.
seek
(
0
)
hashed
=
calculate_sha256
(
file
)
file
.
seek
(
0
)
url
=
f
"
{
OLLAMA_BASE_URLS
[
0
]
}
/api/blobs/sha256:
{
hashed
}
"
response
=
requests
.
post
(
url
,
data
=
file
)
if
response
.
ok
:
res
=
{
"done"
:
done
,
"blob"
:
f
"sha256:
{
hashed
}
"
,
"name"
:
file_name
,
}
os
.
remove
(
file_path
)
yield
f
"data:
{
json
.
dumps
(
res
)
}
\n\n
"
else
:
raise
"Ollama: Could not create blob, Please try again."
@
router
.
get
(
"/download"
)
async
def
download
(
url
:
str
,
):
# url = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf"
file_name
=
parse_huggingface_url
(
url
)
if
file_name
:
file_path
=
f
"
{
UPLOAD_DIR
}
/
{
file_name
}
"
return
StreamingResponse
(
download_file_stream
(
url
,
file_path
,
file_name
),
media_type
=
"text/event-stream"
,
)
else
:
return
None
@
router
.
post
(
"/upload"
)
def
upload
(
file
:
UploadFile
=
File
(...)):
file_path
=
f
"
{
UPLOAD_DIR
}
/
{
file
.
filename
}
"
# Save file in chunks
with
open
(
file_path
,
"wb+"
)
as
f
:
for
chunk
in
file
.
file
:
f
.
write
(
chunk
)
def
file_process_stream
():
total_size
=
os
.
path
.
getsize
(
file_path
)
chunk_size
=
1024
*
1024
try
:
with
open
(
file_path
,
"rb"
)
as
f
:
total
=
0
done
=
False
while
not
done
:
chunk
=
f
.
read
(
chunk_size
)
if
not
chunk
:
done
=
True
continue
total
+=
len
(
chunk
)
progress
=
round
((
total
/
total_size
)
*
100
,
2
)
res
=
{
"progress"
:
progress
,
"total"
:
total_size
,
"completed"
:
total
,
}
yield
f
"data:
{
json
.
dumps
(
res
)
}
\n\n
"
if
done
:
f
.
seek
(
0
)
hashed
=
calculate_sha256
(
f
)
f
.
seek
(
0
)
url
=
f
"
{
OLLAMA_BASE_URLS
[
0
]
}
/blobs/sha256:
{
hashed
}
"
response
=
requests
.
post
(
url
,
data
=
f
)
if
response
.
ok
:
res
=
{
"done"
:
done
,
"blob"
:
f
"sha256:
{
hashed
}
"
,
"name"
:
file
.
filename
,
}
os
.
remove
(
file_path
)
yield
f
"data:
{
json
.
dumps
(
res
)
}
\n\n
"
else
:
raise
Exception
(
"Ollama: Could not create blob, Please try again."
)
except
Exception
as
e
:
res
=
{
"error"
:
str
(
e
)}
yield
f
"data:
{
json
.
dumps
(
res
)
}
\n\n
"
return
StreamingResponse
(
file_process_stream
(),
media_type
=
"text/event-stream"
)
@
router
.
get
(
"/gravatar"
)
@
router
.
get
(
"/gravatar"
)
async
def
get_gravatar
(
async
def
get_gravatar
(
email
:
str
,
email
:
str
,
...
...
src/lib/apis/ollama/index.ts
View file @
af4caec4
...
@@ -390,6 +390,71 @@ export const pullModel = async (token: string, tagName: string, urlIdx: string |
...
@@ -390,6 +390,71 @@ export const pullModel = async (token: string, tagName: string, urlIdx: string |
return
res
;
return
res
;
};
};
export
const
downloadModel
=
async
(
token
:
string
,
download_url
:
string
,
urlIdx
:
string
|
null
=
null
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
OLLAMA_API_BASE_URL
}
/models/download
${
urlIdx
!==
null
?
`/
${
urlIdx
}
`
:
''
}
`
,
{
method
:
'
POST
'
,
headers
:
{
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
url
:
download_url
})
}
).
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
;
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
uploadModel
=
async
(
token
:
string
,
file
:
File
,
urlIdx
:
string
|
null
=
null
)
=>
{
let
error
=
null
;
const
formData
=
new
FormData
();
formData
.
append
(
'
file
'
,
file
);
const
res
=
await
fetch
(
`
${
OLLAMA_API_BASE_URL
}
/models/upload
${
urlIdx
!==
null
?
`/
${
urlIdx
}
`
:
''
}
`
,
{
method
:
'
POST
'
,
headers
:
{
Authorization
:
`Bearer
${
token
}
`
},
body
:
formData
}
).
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
;
if
(
'
detail
'
in
err
)
{
error
=
err
.
detail
;
}
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
// export const pullModel = async (token: string, tagName: string) => {
// export const pullModel = async (token: string, tagName: string) => {
// return await fetch(`${OLLAMA_API_BASE_URL}/pull`, {
// return await fetch(`${OLLAMA_API_BASE_URL}/pull`, {
// method: 'POST',
// method: 'POST',
...
...
src/lib/components/chat/Settings/Models.svelte
View file @
af4caec4
...
@@ -5,9 +5,11 @@
...
@@ -5,9 +5,11 @@
import {
import {
createModel,
createModel,
deleteModel,
deleteModel,
downloadModel,
getOllamaUrls,
getOllamaUrls,
getOllamaVersion,
getOllamaVersion,
pullModel
pullModel,
uploadModel
} from '$lib/apis/ollama';
} from '$lib/apis/ollama';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_NAME, models, user } from '$lib/stores';
import { WEBUI_NAME, models, user } from '$lib/stores';
...
@@ -60,7 +62,7 @@
...
@@ -60,7 +62,7 @@
let pullProgress = null;
let pullProgress = null;
let modelUploadMode = 'file';
let modelUploadMode = 'file';
let modelInputFile
= ''
;
let modelInputFile
: File[] | null = null
;
let modelFileUrl = '';
let modelFileUrl = '';
let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`;
let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`;
let modelFileDigest = '';
let modelFileDigest = '';
...
@@ -191,30 +193,23 @@
...
@@ -191,30 +193,23 @@
let name = '';
let name = '';
if (modelUploadMode === 'file') {
if (modelUploadMode === 'file') {
const file = modelInputFile[0];
const file = modelInputFile ? modelInputFile[0] : null;
const formData = new FormData();
formData.append('file', file);
if (file) {
fileResponse = uploadModel(localStorage.token, file, selectedOllamaUrlIdx).catch(
fileResponse = await fetch(`${WEBUI_API_BASE_URL}/utils/upload`, {
(error) => {
method: 'POST',
toast.error(error);
headers: {
return null;
...($user && { Authorization: `Bearer ${localStorage.token}` })
}
},
);
body: formData
}
}).catch((error) => {
console.log(error);
return null;
});
} else {
} else {
fileResponse =
await fetch(`${WEBUI_API_BASE_URL}/utils/download?url=${modelFileUrl}`, {
fileResponse =
downloadModel(localStorage.token, modelFileUrl, selectedOllamaUrlIdx).catch(
method: 'GET',
(error) => {
headers: {
toast.error(error);
...($user && { Authorization: `Bearer ${localStorage.token}` })
return null;
}
}
}).catch((error) => {
);
console.log(error);
return null;
});
}
}
if (fileResponse && fileResponse.ok) {
if (fileResponse && fileResponse.ok) {
...
@@ -318,7 +313,8 @@
...
@@ -318,7 +313,8 @@
}
}
modelFileUrl = '';
modelFileUrl = '';
modelInputFile = '';
modelUploadInputElement.value = '';
modelInputFile = null;
modelTransferring = false;
modelTransferring = false;
uploadProgress = null;
uploadProgress = null;
...
...
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