mirror of
https://github.com/open-webui/open-webui
synced 2025-03-27 07:50:37 +00:00
feat: tools integration
This commit is contained in:
parent
c5683dd24c
commit
b434ebf3ad
@ -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:
|
|||||||
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(
|
||||||
**{
|
**{
|
||||||
|
@ -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)):
|
|||||||
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)):
|
|||||||
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)):
|
|||||||
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(
|
|||||||
)
|
)
|
||||||
|
|
||||||
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,
|
||||||
|
@ -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."
|
||||||
|
@ -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 @@
|
|||||||
<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 @@
|
|||||||
<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?command=${encodeURIComponent(tool.id)}`}
|
href={`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -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
|
||||||
|
@ -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:
|
|||||||
"""
|
"""
|
||||||
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,15 +65,17 @@ 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:
|
||||||
@ -78,22 +83,17 @@ class Tools:
|
|||||||
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 () => {
|
||||||
|
@ -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 @@
|
|||||||
<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 @@
|
|||||||
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 @@
|
|||||||
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 @@
|
|||||||
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>
|
||||||
|
@ -1,8 +1,28 @@
|
|||||||
<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>
|
||||||
|
|
||||||
|
@ -1,5 +1,57 @@
|
|||||||
<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}
|
||||||
|
Loading…
Reference in New Issue
Block a user