فهرست منبع

feat: tools integration

Timothy J. Baek 10 ماه پیش
والد
کامیت
b434ebf3ad

+ 2 - 2
backend/apps/webui/models/tools.py

@@ -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(
             **{
             **{

+ 16 - 5
backend/apps/webui/routers/tools.py

@@ -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,

+ 1 - 0
backend/constants.py

@@ -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."

+ 18 - 5
src/lib/components/workspace/Tools.svelte

@@ -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={() => {
-						// deletePrompt(prompt.command);
-						// deleteTool
+					on:click={async () => {
+						const res = await deleteToolById(localStorage.token, tool.id).catch((error) => {
+							toast.error(error);
+							return null;
+						});
+
+						if (res) {
+							toast.success('Tool deleted successfully');
+							tools.set(await getTools(localStorage.token));
+						}
 					}}
 					}}
 				>
 				>
 					<svg
 					<svg

+ 22 - 22
src/lib/components/workspace/Tools/CodeEditor.svelte

@@ -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,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,
-            'appid': api_key,
-            'units': 'metric'  # Optional: Use 'imperial' for Fahrenheit
+            "q": city,
+            "appid": api_key,
+            "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']
-            temperature = data['main']['temp']
-            humidity = data['main']['humidity']
-            wind_speed = data['wind']['speed']
-            
-            return (f"Weather in {city}:\n"
-                    f"Description: {weather_description}\n"
-                    f"Temperature: {temperature}°C\n"
-                    f"Humidity: {humidity}%\n"
-                    f"Wind Speed: {wind_speed} m/s")
+
+            weather_description = data["weather"][0]["description"]
+            temperature = data["main"]["temp"]
+            humidity = data["main"]["humidity"]
+            wind_speed = data["wind"]["speed"]
+
+            return f"Weather in {city}: {temperature}°C"
         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 () => {

+ 14 - 7
src/lib/components/workspace/Tools/ToolkitEditor.svelte

@@ -8,15 +8,18 @@
 
 
 	const dispatch = createEventDispatcher();
 	const dispatch = createEventDispatcher();
 
 
+	let formElement = null;
+
 	let loading = false;
 	let loading = false;
 
 
-	let id = '';
-	let name = '';
-	let meta = {
+	export let edit = false;
+
+	export let id = '';
+	export let name = '';
+	export let meta = {
 		description: ''
 		description: ''
 	};
 	};
-
-	let content = '';
+	export 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
-							submitHandler();
+							if (formElement) {
+								formElement.requestSubmit();
+							}
 						}}
 						}}
 					/>
 					/>
 				</div>
 				</div>

+ 20 - 0
src/routes/(app)/workspace/tools/create/+page.svelte

@@ -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>
 
 

+ 53 - 1
src/routes/(app)/workspace/tools/edit/+page.svelte

@@ -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}