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
b434ebf3
Commit
b434ebf3
authored
Jun 10, 2024
by
Timothy J. Baek
Browse files
feat: tools integration
parent
c5683dd2
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
146 additions
and
42 deletions
+146
-42
backend/apps/webui/models/tools.py
backend/apps/webui/models/tools.py
+2
-2
backend/apps/webui/routers/tools.py
backend/apps/webui/routers/tools.py
+16
-5
backend/constants.py
backend/constants.py
+1
-0
src/lib/components/workspace/Tools.svelte
src/lib/components/workspace/Tools.svelte
+18
-5
src/lib/components/workspace/Tools/CodeEditor.svelte
src/lib/components/workspace/Tools/CodeEditor.svelte
+22
-22
src/lib/components/workspace/Tools/ToolkitEditor.svelte
src/lib/components/workspace/Tools/ToolkitEditor.svelte
+14
-7
src/routes/(app)/workspace/tools/create/+page.svelte
src/routes/(app)/workspace/tools/create/+page.svelte
+20
-0
src/routes/(app)/workspace/tools/edit/+page.svelte
src/routes/(app)/workspace/tools/edit/+page.svelte
+53
-1
No files found.
backend/apps/webui/models/tools.py
View file @
b434ebf3
...
@@ -41,7 +41,7 @@ class ToolModel(BaseModel):
...
@@ -41,7 +41,7 @@ class ToolModel(BaseModel):
user_id
:
str
user_id
:
str
name
:
str
name
:
str
content
:
str
content
:
str
specs
:
dict
specs
:
List
[
dict
]
meta
:
ToolMeta
meta
:
ToolMeta
updated_at
:
int
# timestamp in epoch
updated_at
:
int
# timestamp in epoch
created_at
:
int
# timestamp in epoch
created_at
:
int
# timestamp in epoch
...
@@ -74,7 +74,7 @@ class ToolsTable:
...
@@ -74,7 +74,7 @@ class ToolsTable:
self
.
db
.
create_tables
([
Tool
])
self
.
db
.
create_tables
([
Tool
])
def
insert_new_tool
(
def
insert_new_tool
(
self
,
user_id
:
str
,
form_data
:
ToolForm
,
specs
:
dict
self
,
user_id
:
str
,
form_data
:
ToolForm
,
specs
:
List
[
dict
]
)
->
Optional
[
ToolModel
]:
)
->
Optional
[
ToolModel
]:
tool
=
ToolModel
(
tool
=
ToolModel
(
**
{
**
{
...
...
backend/apps/webui/routers/tools.py
View file @
b434ebf3
...
@@ -52,7 +52,18 @@ def load_toolkit_module_from_path(tools_id, tools_path):
...
@@ -52,7 +52,18 @@ def load_toolkit_module_from_path(tools_id, tools_path):
@
router
.
get
(
"/"
,
response_model
=
List
[
ToolResponse
])
@
router
.
get
(
"/"
,
response_model
=
List
[
ToolResponse
])
async
def
get_toolkits
(
user
=
Depends
(
get_current_user
)):
async
def
get_toolkits
(
user
=
Depends
(
get_current_user
)):
toolkits
=
[
ToolResponse
(
**
toolkit
)
for
toolkit
in
Tools
.
get_tools
()]
toolkits
=
[
toolkit
for
toolkit
in
Tools
.
get_tools
()]
return
toolkits
############################
# ExportToolKits
############################
@
router
.
get
(
"/export"
,
response_model
=
List
[
ToolModel
])
async
def
get_toolkits
(
user
=
Depends
(
get_current_user
)):
toolkits
=
[
toolkit
for
toolkit
in
Tools
.
get_tools
()]
return
toolkits
return
toolkits
...
@@ -77,7 +88,7 @@ async def create_new_toolkit(form_data: ToolForm, user=Depends(get_admin_user)):
...
@@ -77,7 +88,7 @@ async def create_new_toolkit(form_data: ToolForm, user=Depends(get_admin_user)):
toolkit
=
Tools
.
insert_new_tool
(
user
.
id
,
form_data
,
specs
)
toolkit
=
Tools
.
insert_new_tool
(
user
.
id
,
form_data
,
specs
)
if
toolkit
:
if
toolkit
:
return
ToolResponse
(
**
toolkit
)
return
toolkit
else
:
else
:
raise
HTTPException
(
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
...
@@ -91,7 +102,7 @@ async def create_new_toolkit(form_data: ToolForm, user=Depends(get_admin_user)):
...
@@ -91,7 +102,7 @@ async def create_new_toolkit(form_data: ToolForm, user=Depends(get_admin_user)):
else
:
else
:
raise
HTTPException
(
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
detail
=
ERROR_MESSAGES
.
NAME_TAG
_TAKEN
,
detail
=
ERROR_MESSAGES
.
ID
_TAKEN
,
)
)
...
@@ -105,7 +116,7 @@ async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)):
...
@@ -105,7 +116,7 @@ async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)):
toolkit
=
Tools
.
get_tool_by_id
(
id
)
toolkit
=
Tools
.
get_tool_by_id
(
id
)
if
toolkit
:
if
toolkit
:
return
ToolResponse
(
**
toolkit
)
return
toolkit
else
:
else
:
raise
HTTPException
(
raise
HTTPException
(
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
status_code
=
status
.
HTTP_401_UNAUTHORIZED
,
...
@@ -137,7 +148,7 @@ async def update_toolkit_by_id(
...
@@ -137,7 +148,7 @@ async def update_toolkit_by_id(
)
)
if
toolkit
:
if
toolkit
:
return
ToolResponse
(
**
toolkit
)
return
toolkit
else
:
else
:
raise
HTTPException
(
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
...
...
backend/constants.py
View file @
b434ebf3
...
@@ -32,6 +32,7 @@ class ERROR_MESSAGES(str, Enum):
...
@@ -32,6 +32,7 @@ class ERROR_MESSAGES(str, Enum):
COMMAND_TAKEN
=
"Uh-oh! This command is already registered. Please choose another command string."
COMMAND_TAKEN
=
"Uh-oh! This command is already registered. Please choose another command string."
FILE_EXISTS
=
"Uh-oh! This file is already registered. Please choose another file."
FILE_EXISTS
=
"Uh-oh! This file is already registered. Please choose another file."
ID_TAKEN
=
"Uh-oh! This id is already registered. Please choose another id string."
MODEL_ID_TAKEN
=
"Uh-oh! This model id is already registered. Please choose another model id string."
MODEL_ID_TAKEN
=
"Uh-oh! This model id is already registered. Please choose another model id string."
NAME_TAG_TAKEN
=
"Uh-oh! This name tag is already registered. Please choose another name tag string."
NAME_TAG_TAKEN
=
"Uh-oh! This name tag is already registered. Please choose another name tag string."
...
...
src/lib/components/workspace/Tools.svelte
View file @
b434ebf3
...
@@ -8,6 +8,7 @@
...
@@ -8,6 +8,7 @@
import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
import { goto } from '$app/navigation';
import { goto } from '$app/navigation';
import { deleteToolById, getTools } from '$lib/apis/tools';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
...
@@ -78,7 +79,12 @@
...
@@ -78,7 +79,12 @@
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
<a href={`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`}>
<a href={`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`}>
<div class=" flex-1 self-center pl-5">
<div class=" flex-1 self-center pl-5">
<div class=" font-bold">{tool.name}</div>
<div class=" font-bold flex items-center gap-1.5">
<div>
{tool.name}
</div>
<div class=" text-gray-500 text-xs font-medium">{tool.id}</div>
</div>
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
{tool.meta.description}
{tool.meta.description}
</div>
</div>
...
@@ -89,7 +95,7 @@
...
@@ -89,7 +95,7 @@
<a
<a
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
type="button"
href={`/workspace/tools/edit?
comman
d=${encodeURIComponent(tool.id)}`}
href={`/workspace/tools/edit?
i
d=${encodeURIComponent(tool.id)}`}
>
>
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
...
@@ -134,9 +140,16 @@
...
@@ -134,9 +140,16 @@
<button
<button
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
type="button"
on:click={() => {
on:click={async () => {
// deletePrompt(prompt.command);
const res = await deleteToolById(localStorage.token, tool.id).catch((error) => {
// deleteTool
toast.error(error);
return null;
});
if (res) {
toast.success('Tool deleted successfully');
tools.set(await getTools(localStorage.token));
}
}}
}}
>
>
<svg
<svg
...
...
src/lib/components/workspace/Tools/CodeEditor.svelte
View file @
b434ebf3
...
@@ -7,9 +7,10 @@
...
@@ -7,9 +7,10 @@
export let value = '';
export let value = '';
let codeEditor;
let codeEditor;
let boilerplate = `
from datetime import datetime
let boilerplate = `
import os
import requests
import requests
import os
from datetime import datetime
class Tools:
class Tools:
def __init__(self):
def __init__(self):
...
@@ -27,7 +28,9 @@ class Tools:
...
@@ -27,7 +28,9 @@ class Tools:
"""
"""
value = os.getenv(variable_name)
value = os.getenv(variable_name)
if value is not None:
if value is not None:
return f"The value of the environment variable '{variable_name}' is '{value}'"
return (
f"The value of the environment variable '{variable_name}' is '{value}'"
)
else:
else:
return f"The environment variable '{variable_name}' does not exist."
return f"The environment variable '{variable_name}' does not exist."
...
@@ -62,38 +65,35 @@ class Tools:
...
@@ -62,38 +65,35 @@ class Tools:
:param city: The name of the city to get the weather for.
:param city: The name of the city to get the weather for.
:return: The current weather information or an error message.
:return: The current weather information or an error message.
"""
"""
api_key = os.getenv(
'
OPENWEATHER_API_KEY
'
)
api_key = os.getenv(
"
OPENWEATHER_API_KEY
"
)
if not api_key:
if not api_key:
return "API key is not set in the environment variable 'OPENWEATHER_API_KEY'."
return (
"API key is not set in the environment variable 'OPENWEATHER_API_KEY'."
)
base_url = "http://api.openweathermap.org/data/2.5/weather"
base_url = "http://api.openweathermap.org/data/2.5/weather"
params = {
params = {
'q'
: city,
"q"
: city,
'
appid
'
: api_key,
"
appid
"
: api_key,
'
units
'
:
'
metric
'
# Optional: Use 'imperial' for Fahrenheit
"
units
"
:
"
metric
",
# Optional: Use 'imperial' for Fahrenheit
}
}
try:
try:
response = requests.get(base_url, params=params)
response = requests.get(base_url, params=params)
response.raise_for_status() # Raise HTTPError for bad responses (4xx and 5xx)
response.raise_for_status() # Raise HTTPError for bad responses (4xx and 5xx)
data = response.json()
data = response.json()
if data.get(
'
cod
'
) != 200:
if data.get(
"
cod
"
) != 200:
return f"Error fetching weather data: {data.get('message')}"
return f"Error fetching weather data: {data.get('message')}"
weather_description = data['weather'][0]['description']
weather_description = data["weather"][0]["description"]
temperature = data['main']['temp']
temperature = data["main"]["temp"]
humidity = data['main']['humidity']
humidity = data["main"]["humidity"]
wind_speed = data['wind']['speed']
wind_speed = data["wind"]["speed"]
return (f"Weather in {city}:\n"
return f"Weather in {city}: {temperature}°C"
f"Description: {weather_description}\n"
f"Temperature: {temperature}°C\n"
f"Humidity: {humidity}%\n"
f"Wind Speed: {wind_speed} m/s")
except requests.RequestException as e:
except requests.RequestException as e:
return f"Error fetching weather data: {str(e)}"
return f"Error fetching weather data: {str(e)}"
`;
`;
export const formatHandler = async () => {
export const formatHandler = async () => {
...
...
src/lib/components/workspace/Tools/ToolkitEditor.svelte
View file @
b434ebf3
...
@@ -8,15 +8,18 @@
...
@@ -8,15 +8,18 @@
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
let formElement = null;
let loading = false;
let loading = false;
let id = '';
export let edit = false;
let name = '';
let meta = {
export let id = '';
export let name = '';
export let meta = {
description: ''
description: ''
};
};
export let content = '';
let content = '';
$: if (name) {
$: if (name) {
id = name.replace(/\s+/g, '_').toLowerCase();
id = name.replace(/\s+/g, '_').toLowerCase();
...
@@ -49,6 +52,7 @@
...
@@ -49,6 +52,7 @@
<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
<div class="mx-auto w-full md:px-0 h-full">
<div class="mx-auto w-full md:px-0 h-full">
<form
<form
bind:this={formElement}
class=" flex flex-col max-h-[100dvh] h-full"
class=" flex flex-col max-h-[100dvh] h-full"
on:submit|preventDefault={() => {
on:submit|preventDefault={() => {
submitHandler();
submitHandler();
...
@@ -60,6 +64,7 @@
...
@@ -60,6 +64,7 @@
on:click={() => {
on:click={() => {
goto('/workspace/tools');
goto('/workspace/tools');
}}
}}
type="button"
>
>
<div class=" self-center">
<div class=" self-center">
<svg
<svg
...
@@ -96,6 +101,7 @@
...
@@ -96,6 +101,7 @@
placeholder="Toolkit ID (e.g. my_toolkit)"
placeholder="Toolkit ID (e.g. my_toolkit)"
bind:value={id}
bind:value={id}
required
required
disabled={edit}
/>
/>
</div>
</div>
<input
<input
...
@@ -112,8 +118,9 @@
...
@@ -112,8 +118,9 @@
bind:value={content}
bind:value={content}
bind:this={codeEditor}
bind:this={codeEditor}
on:save={() => {
on:save={() => {
// submit form
if (formElement) {
submitHandler();
formElement.requestSubmit();
}
}}
}}
/>
/>
</div>
</div>
...
...
src/routes/(app)/workspace/tools/create/+page.svelte
View file @
b434ebf3
<script>
<script>
import { goto } from '$app/navigation';
import { createNewTool, getTools } from '$lib/apis/tools';
import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
import { tools } from '$lib/stores';
import { toast } from 'svelte-sonner';
const saveHandler = async (data) => {
const saveHandler = async (data) => {
console.log(data);
console.log(data);
const res = await createNewTool(localStorage.token, {
id: data.id,
name: data.name,
meta: data.meta,
content: data.content
}).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success('Tool created successfully');
tools.set(await getTools(localStorage.token));
await goto('/workspace/tools');
}
};
};
</script>
</script>
...
...
src/routes/(app)/workspace/tools/edit/+page.svelte
View file @
b434ebf3
<script>
<script>
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { getToolById, getTools, updateToolById } from '$lib/apis/tools';
import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
import { tools } from '$lib/stores';
import { onMount } from 'svelte';
import { toast } from 'svelte-sonner';
let tool = null;
const saveHandler = async (data) => {
console.log(data);
const res = await updateToolById(localStorage.token, tool.id, {
id: data.id,
name: data.name,
meta: data.meta,
content: data.content
}).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success('Tool updated successfully');
tools.set(await getTools(localStorage.token));
await goto('/workspace/tools');
}
};
onMount(async () => {
console.log('mounted');
const id = $page.url.searchParams.get('id');
if (id) {
tool = await getToolById(localStorage.token, id).catch((error) => {
toast.error(error);
goto('/workspace/tools');
return null;
});
}
});
</script>
</script>
<ToolkitEditor />
{#if tool}
<ToolkitEditor
edit={true}
id={tool.id}
name={tool.name}
meta={tool.meta}
content={tool.content}
on:save={(e) => {
saveHandler(e.detail);
}}
/>
{/if}
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