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
d8c112d8
"vscode:/vscode.git/clone" did not exist on "a94e4161f778e646542037ef372533e2ab1061f9"
Commit
d8c112d8
authored
Jun 23, 2024
by
Timothy J. Baek
Browse files
feat: function toggle support
parent
3034f3d3
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
346 additions
and
217 deletions
+346
-217
backend/apps/webui/main.py
backend/apps/webui/main.py
+1
-1
backend/apps/webui/models/functions.py
backend/apps/webui/models/functions.py
+50
-10
backend/apps/webui/routers/functions.py
backend/apps/webui/routers/functions.py
+77
-0
backend/apps/webui/routers/tools.py
backend/apps/webui/routers/tools.py
+50
-0
backend/main.py
backend/main.py
+0
-1
src/lib/apis/functions/index.ts
src/lib/apis/functions/index.ts
+99
-0
src/lib/components/workspace/Functions.svelte
src/lib/components/workspace/Functions.svelte
+8
-2
src/lib/components/workspace/ValvesModal.svelte
src/lib/components/workspace/ValvesModal.svelte
+61
-203
No files found.
backend/apps/webui/main.py
View file @
d8c112d8
...
...
@@ -103,7 +103,7 @@ async def get_status():
async
def
get_pipe_models
():
pipes
=
Functions
.
get_functions_by_type
(
"pipe"
)
pipes
=
Functions
.
get_functions_by_type
(
"pipe"
,
active_only
=
True
)
pipe_models
=
[]
for
pipe
in
pipes
:
...
...
backend/apps/webui/models/functions.py
View file @
d8c112d8
...
...
@@ -48,7 +48,7 @@ class FunctionModel(BaseModel):
type
:
str
content
:
str
meta
:
FunctionMeta
is_active
:
bool
is_active
:
bool
=
False
updated_at
:
int
# timestamp in epoch
created_at
:
int
# timestamp in epoch
...
...
@@ -115,17 +115,57 @@ class FunctionsTable:
except
:
return
None
def
get_functions
(
self
)
->
List
[
FunctionModel
]:
def
get_functions
(
self
,
active_only
=
False
)
->
List
[
FunctionModel
]:
if
active_only
:
return
[
FunctionModel
(
**
model_to_dict
(
function
))
for
function
in
Function
.
select
()
FunctionModel
(
**
model_to_dict
(
function
))
for
function
in
Function
.
select
().
where
(
Function
.
is_active
==
True
)
]
else
:
return
[
FunctionModel
(
**
model_to_dict
(
function
))
for
function
in
Function
.
select
()
]
def
get_functions_by_type
(
self
,
type
:
str
)
->
List
[
FunctionModel
]:
def
get_functions_by_type
(
self
,
type
:
str
,
active_only
=
False
)
->
List
[
FunctionModel
]:
if
active_only
:
return
[
FunctionModel
(
**
model_to_dict
(
function
))
for
function
in
Function
.
select
().
where
(
Function
.
type
==
type
,
Function
.
is_active
==
True
)
]
else
:
return
[
FunctionModel
(
**
model_to_dict
(
function
))
for
function
in
Function
.
select
().
where
(
Function
.
type
==
type
)
]
def
get_function_valves_by_id
(
self
,
id
:
str
)
->
Optional
[
FunctionValves
]:
try
:
function
=
Function
.
get
(
Function
.
id
==
id
)
return
FunctionValves
(
**
model_to_dict
(
function
))
except
Exception
as
e
:
print
(
f
"An error occurred:
{
e
}
"
)
return
None
def
update_function_valves_by_id
(
self
,
id
:
str
,
valves
:
dict
)
->
Optional
[
FunctionValves
]:
try
:
query
=
Function
.
update
(
**
{
"valves"
:
valves
},
updated_at
=
int
(
time
.
time
()),
).
where
(
Function
.
id
==
id
)
query
.
execute
()
function
=
Function
.
get
(
Function
.
id
==
id
)
return
FunctionValves
(
**
model_to_dict
(
function
))
except
:
return
None
def
get_user_valves_by_id_and_user_id
(
self
,
id
:
str
,
user_id
:
str
)
->
Optional
[
dict
]:
...
...
backend/apps/webui/routers/functions.py
View file @
d8c112d8
...
...
@@ -117,6 +117,56 @@ async def get_function_by_id(id: str, user=Depends(get_admin_user)):
)
############################
# GetFunctionValves
############################
@
router
.
get
(
"/id/{id}/valves"
,
response_model
=
Optional
[
dict
])
async
def
get_function_valves_by_id
(
id
:
str
,
user
=
Depends
(
get_admin_user
)):
function
=
Functions
.
get_function_by_id
(
id
)
if
function
:
try
:
valves
=
Functions
.
get_function_valves_by_id
(
id
)
return
valves
except
Exception
as
e
:
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
detail
=
ERROR_MESSAGES
.
DEFAULT
(
e
),
)
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
ERROR_MESSAGES
.
NOT_FOUND
,
)
############################
# UpdateToolValves
############################
@
router
.
post
(
"/id/{id}/valves/update"
,
response_model
=
Optional
[
dict
])
async
def
update_toolkit_valves_by_id
(
id
:
str
,
form_data
:
dict
,
user
=
Depends
(
get_admin_user
)
):
function
=
Functions
.
get_function_by_id
(
id
)
if
function
:
try
:
valves
=
Functions
.
update_function_valves_by_id
(
id
,
form_data
)
return
valves
except
Exception
as
e
:
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
detail
=
ERROR_MESSAGES
.
DEFAULT
(
e
),
)
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
ERROR_MESSAGES
.
NOT_FOUND
,
)
############################
# FunctionUserValves
############################
...
...
@@ -204,6 +254,33 @@ async def update_function_user_valves_by_id(
)
############################
# ToggleFunctionById
############################
@
router
.
post
(
"/id/{id}/toggle"
,
response_model
=
Optional
[
FunctionModel
])
async
def
toggle_function_by_id
(
id
:
str
,
user
=
Depends
(
get_admin_user
)):
function
=
Functions
.
get_function_by_id
(
id
)
if
function
:
function
=
Functions
.
update_function_by_id
(
id
,
{
"is_active"
:
not
function
.
is_active
}
)
if
function
:
return
function
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
detail
=
ERROR_MESSAGES
.
DEFAULT
(
"Error updating function"
),
)
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
ERROR_MESSAGES
.
NOT_FOUND
,
)
############################
# UpdateFunctionById
############################
...
...
backend/apps/webui/routers/tools.py
View file @
d8c112d8
...
...
@@ -123,6 +123,56 @@ async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)):
)
############################
# GetToolValves
############################
@
router
.
get
(
"/id/{id}/valves"
,
response_model
=
Optional
[
dict
])
async
def
get_toolkit_valves_by_id
(
id
:
str
,
user
=
Depends
(
get_admin_user
)):
toolkit
=
Tools
.
get_tool_by_id
(
id
)
if
toolkit
:
try
:
valves
=
Tools
.
get_tool_valves_by_id
(
id
)
return
valves
except
Exception
as
e
:
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
detail
=
ERROR_MESSAGES
.
DEFAULT
(
e
),
)
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
ERROR_MESSAGES
.
NOT_FOUND
,
)
############################
# UpdateToolValves
############################
@
router
.
post
(
"/id/{id}/valves/update"
,
response_model
=
Optional
[
dict
])
async
def
update_toolkit_valves_by_id
(
id
:
str
,
form_data
:
dict
,
user
=
Depends
(
get_admin_user
)
):
toolkit
=
Tools
.
get_tool_by_id
(
id
)
if
toolkit
:
try
:
valves
=
Tools
.
update_tool_valves_by_id
(
id
,
form_data
)
return
valves
except
Exception
as
e
:
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
detail
=
ERROR_MESSAGES
.
DEFAULT
(
e
),
)
else
:
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
detail
=
ERROR_MESSAGES
.
NOT_FOUND
,
)
############################
# ToolUserValves
############################
...
...
backend/main.py
View file @
d8c112d8
...
...
@@ -863,7 +863,6 @@ async def generate_chat_completions(form_data: dict, user=Depends(get_verified_u
pipe
=
model
.
get
(
"pipe"
)
if
pipe
:
async
def
job
():
pipe_id
=
form_data
[
"model"
]
if
"."
in
pipe_id
:
...
...
src/lib/apis/functions/index.ts
View file @
d8c112d8
...
...
@@ -192,6 +192,105 @@ export const deleteFunctionById = async (token: string, id: string) => {
return
res
;
};
export
const
toggleFunctionById
=
async
(
token
:
string
,
id
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/functions/id/
${
id
}
/toggle`
,
{
method
:
'
POST
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
then
((
json
)
=>
{
return
json
;
})
.
catch
((
err
)
=>
{
error
=
err
.
detail
;
console
.
log
(
err
);
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
getFunctionValvesById
=
async
(
token
:
string
,
id
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/functions/id/
${
id
}
/valves`
,
{
method
:
'
GET
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
then
((
json
)
=>
{
return
json
;
})
.
catch
((
err
)
=>
{
error
=
err
.
detail
;
console
.
log
(
err
);
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
updateFunctionValvesById
=
async
(
token
:
string
,
id
:
string
,
valves
:
object
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/functions/id/
${
id
}
/valves/update`
,
{
method
:
'
POST
'
,
headers
:
{
Accept
:
'
application/json
'
,
'
Content-Type
'
:
'
application/json
'
,
authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
...
valves
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
then
((
json
)
=>
{
return
json
;
})
.
catch
((
err
)
=>
{
error
=
err
.
detail
;
console
.
log
(
err
);
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
getUserValvesById
=
async
(
token
:
string
,
id
:
string
)
=>
{
let
error
=
null
;
...
...
src/lib/components/workspace/Functions.svelte
View file @
d8c112d8
...
...
@@ -13,7 +13,8 @@
deleteFunctionById,
exportFunctions,
getFunctionById,
getFunctions
getFunctions,
toggleFunctionById
} from '$lib/apis/functions';
import ArrowDownTray from '../icons/ArrowDownTray.svelte';
...
...
@@ -224,7 +225,12 @@
</FunctionMenu>
<div class=" self-center mx-1">
<Switch />
<Switch
bind:state={func.is_active}
on:change={(e) => {
toggleFunctionById(localStorage.token, func.id);
}}
/>
</div>
</div>
</div>
...
...
src/lib/components/workspace/ValvesModal.svelte
View file @
d8c112d8
...
...
@@ -5,16 +5,16 @@
import { addUser } from '$lib/apis/auths';
import Modal from '../common/Modal.svelte';
import { WEBUI_BASE_URL } from '$lib/constants';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let show = false;
export let type = 'tool';
export let id = null;
let loading = false;
let tab = '';
let inputFiles;
let _user = {
name: '',
...
...
@@ -23,96 +23,11 @@
role: 'user'
};
$: if (show) {
_user = {
name: '',
email: '',
password: '',
role: 'user'
};
}
const submitHandler = async () => {
const stopLoading = () => {
dispatch('save');
loading = false;
};
if (tab === '') {
loading = true;
const res = await addUser(
localStorage.token,
_user.name,
_user.email,
_user.password,
_user.role
).catch((error) => {
toast.error(error);
});
if (res) {
stopLoading();
show = false;
}
} else {
if (inputFiles) {
loading = true;
const file = inputFiles[0];
const reader = new FileReader();
reader.onload = async (e) => {
const csv = e.target.result;
const rows = csv.split('\n');
let userCount = 0;
for (const [idx, row] of rows.entries()) {
const columns = row.split(',').map((col) => col.trim());
console.log(idx, columns);
if (idx > 0) {
if (
columns.length === 4 &&
['admin', 'user', 'pending'].includes(columns[3].toLowerCase())
) {
const res = await addUser(
localStorage.token,
columns[0],
columns[1],
columns[2],
columns[3].toLowerCase()
).catch((error) => {
toast.error(`Row ${idx + 1}: ${error}`);
return null;
});
if (res) {
userCount = userCount + 1;
}
} else {
toast.error(`Row ${idx + 1}: invalid format.`);
}
}
}
toast.success(`Successfully imported ${userCount} users.`);
inputFiles = null;
const uploadInputElement = document.getElementById('upload-user-csv-input');
if (uploadInputElement) {
uploadInputElement.value = null;
}
stopLoading();
};
reader.readAsText(file);
} else {
toast.error($i18n.t('File not found.'));
}
}
};
</script>
...
...
@@ -147,25 +62,7 @@
submitHandler();
}}
>
<div class="flex text-center text-sm font-medium rounded-xl bg-transparent/10 p-1 mb-2">
<button
class="w-full rounded-lg p-1.5 {tab === '' ? 'bg-gray-50 dark:bg-gray-850' : ''}"
type="button"
on:click={() => {
tab = '';
}}>{$i18n.t('Form')}</button
>
<button
class="w-full rounded-lg p-1 {tab === 'import' ? 'bg-gray-50 dark:bg-gray-850' : ''}"
type="button"
on:click={() => {
tab = 'import';
}}>{$i18n.t('CSV Import')}</button
>
</div>
<div class="px-1">
{#if tab === ''}
<div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Role')}</div>
...
...
@@ -228,45 +125,6 @@
/>
</div>
</div>
{:else if tab === 'import'}
<div>
<div class="mb-3 w-full">
<input
id="upload-user-csv-input"
hidden
bind:files={inputFiles}
type="file"
accept=".csv"
/>
<button
class="w-full text-sm font-medium py-3 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
type="button"
on:click={() => {
document.getElementById('upload-user-csv-input')?.click();
}}
>
{#if inputFiles}
{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
{:else}
{$i18n.t('Click here to select a csv file.')}
{/if}
</button>
</div>
<div class=" text-xs text-gray-500">
ⓘ {$i18n.t(
'Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.'
)}
<a
class="underline dark:text-gray-200"
href="{WEBUI_BASE_URL}/static/user-import.csv"
>
{$i18n.t('Click here to download user import template file.')}
</a>
</div>
</div>
{/if}
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
...
...
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