diff --git a/apps/shinkai-desktop/src/components/playground-tool/components/remove-tool-button.tsx b/apps/shinkai-desktop/src/components/playground-tool/components/remove-tool-button.tsx index 9daa278c9..7d57a2bc4 100644 --- a/apps/shinkai-desktop/src/components/playground-tool/components/remove-tool-button.tsx +++ b/apps/shinkai-desktop/src/components/playground-tool/components/remove-tool-button.tsx @@ -24,7 +24,6 @@ export default function RemoveToolButton({ toolKey }: { toolKey: string }) { return ( +
+ + +
); } + +function ManageToolSourceModal({ + xShinkaiAppId, + xShinkaiToolId, +}: CustomToolHeaders) { + const auth = useAuth((state) => state.auth); + const { t } = useTranslation(); + + const { data: assets, isSuccess: isGetAllToolAssetsSuccess } = + useGetAllToolAssets({ + nodeAddress: auth?.node_address ?? '', + token: auth?.api_v2_key ?? '', + xShinkaiAppId, + xShinkaiToolId, + }); + + const { mutateAsync: uploadAssets } = useUploadAssetsTool({ + onError: (error) => { + toast.error('Failed uploading source:', { + description: error.response?.data?.message ?? error.message, + }); + }, + }); + + const { mutateAsync: removeAsset } = useRemoveAssetTool({ + onError: (error) => { + toast.error('Failed removing source:', { + description: error.response?.data?.message ?? error.message, + }); + }, + }); + + const { getRootProps: getRootFileProps, getInputProps: getInputFileProps } = + useDropzone({ + multiple: true, + maxFiles: 5, + onDrop: async (acceptedFiles) => { + await uploadAssets({ + nodeAddress: auth?.node_address ?? '', + token: auth?.api_v2_key ?? '', + files: acceptedFiles, + xShinkaiAppId, + xShinkaiToolId, + }); + }, + }); + + return ( + + + + + + + + +
+ Manage Sources + + Add knowledge directly to your tool. It is used to provide context + to the large language model. + +
+ +
+
+
+ +
+

{t('common.clickToUpload')}

+ +

+ Supports {allowedFileExtensions.join(', ')} +

+
+ + +
+ +
5, + )} + > + {isGetAllToolAssetsSuccess && assets.length === 0 && ( + + No source files uploaded yet. + + )} + {isGetAllToolAssetsSuccess && + assets.map((asset) => ( +
+
+
+ {getFileExt(asset) && fileIconMap[getFileExt(asset)] ? ( + + ) : ( + + )} +
+ {decodeURIComponent(asset)} +
+ +
+ ))} +
+
+
+ ); +} diff --git a/apps/shinkai-desktop/src/components/playground-tool/tool-code-editor.tsx b/apps/shinkai-desktop/src/components/playground-tool/tool-code-editor.tsx index f44da5501..d47c95950 100644 --- a/apps/shinkai-desktop/src/components/playground-tool/tool-code-editor.tsx +++ b/apps/shinkai-desktop/src/components/playground-tool/tool-code-editor.tsx @@ -116,7 +116,7 @@ const ToolCodeEditor = forwardRef< } >(({ value, onUpdate, language, name, readOnly, style }, ref) => ( 0 && { label: 'Keyword', - value: tool.keywords, + value: tool.keywords.join(', '), }, ] .filter((item) => !!item) @@ -223,7 +226,7 @@ export default function DenoTool({ render={({ field }) => ( )} @@ -242,7 +245,7 @@ export default function DenoTool({ )} -
+
{isPlaygroundTool && ( */} {/*)}*/} -
+
diff --git a/apps/shinkai-desktop/src/components/tools/python-tool.tsx b/apps/shinkai-desktop/src/components/tools/python-tool.tsx index c62cd26b2..7690bc70e 100644 --- a/apps/shinkai-desktop/src/components/tools/python-tool.tsx +++ b/apps/shinkai-desktop/src/components/tools/python-tool.tsx @@ -14,7 +14,10 @@ import { Switch, TextField, } from '@shinkai_network/shinkai-ui'; -import { formatText } from '@shinkai_network/shinkai-ui/helpers'; +import { + formatCamelCaseText, + formatText, +} from '@shinkai_network/shinkai-ui/helpers'; import { cn } from '@shinkai_network/shinkai-ui/utils'; import { save } from '@tauri-apps/plugin-dialog'; import * as fs from '@tauri-apps/plugin-fs'; @@ -194,7 +197,7 @@ export default function PythonTool({ }, tool.keywords.length > 0 && { label: 'Keyword', - value: tool.keywords, + value: tool.keywords.join(', '), }, ] .filter((item) => !!item) @@ -223,7 +226,7 @@ export default function PythonTool({ render={({ field }) => ( )} @@ -242,7 +245,7 @@ export default function PythonTool({
)} -
+
{isPlaygroundTool && ( */} {/*)}*/} -
+
diff --git a/libs/shinkai-message-ts/src/api/general/types.ts b/libs/shinkai-message-ts/src/api/general/types.ts index 1178e34a5..72587137b 100644 --- a/libs/shinkai-message-ts/src/api/general/types.ts +++ b/libs/shinkai-message-ts/src/api/general/types.ts @@ -9,6 +9,12 @@ export type CheckHealthResponse = { }; export type Token = { token: string }; + +export type CustomToolHeaders = { + xShinkaiAppId: string; + xShinkaiToolId: string; +}; + export type WalletBalance = { amount: string; asset: { diff --git a/libs/shinkai-message-ts/src/api/tools/index.ts b/libs/shinkai-message-ts/src/api/tools/index.ts index 564bf2fcd..e2a8841ff 100644 --- a/libs/shinkai-message-ts/src/api/tools/index.ts +++ b/libs/shinkai-message-ts/src/api/tools/index.ts @@ -2,6 +2,8 @@ import { httpClient } from '../../http-client'; import { urlJoin } from '../../utils/url-join'; import { AddToolRequest, + AddToolRequestRequest, + AddToolRequestResponse, CreatePromptRequest, CreatePromptResponse, CreateToolCodeRequest, @@ -14,6 +16,7 @@ import { ExportToolRequest, ExportToolResponse, GetAllPromptsResponse, + GetAllToolAssetsResponse, GetPlaygroundToolRequest, GetPlaygroundToolResponse, GetPlaygroundToolsResponse, @@ -25,6 +28,7 @@ import { ImportToolResponse, PayInvoiceRequest, RemovePlaygroundToolRequest, + RemoveToolRequestRequest, SaveToolCodeRequest, SaveToolCodeResponse, SearchPromptsResponse, @@ -405,3 +409,95 @@ export const getShinkaiFileProtocol = async ( ); return response.data as GetShinkaiFileProtocolResponse; }; + +export const getAllToolAssets = async ( + nodeAddress: string, + bearerToken: string, + xShinkaiAppId: string, + xShinkaiToolId: string, +) => { + const response = await httpClient.get( + urlJoin(nodeAddress, '/v2/list_tool_asset'), + { + headers: { + Authorization: `Bearer ${bearerToken}`, + 'x-shinkai-app-id': xShinkaiAppId, + 'x-shinkai-tool-id': xShinkaiToolId, + }, + responseType: 'json', + }, + ); + return response.data as GetAllToolAssetsResponse; +}; + +export const uploadAssetTool = async ( + nodeAddress: string, + bearerToken: string, + payload: AddToolRequestRequest, + xShinkaiAppId: string, + xShinkaiToolId: string, +) => { + const fileData = await payload.file.arrayBuffer(); + + const formData = new FormData(); + formData.append('file_name', payload.filename); + formData.append('file', new Blob([fileData])); + + const response = await httpClient.post( + urlJoin(nodeAddress, '/v2/tool_asset'), + formData, + { + headers: { + Authorization: `Bearer ${bearerToken}`, + 'x-shinkai-app-id': xShinkaiAppId, + 'x-shinkai-tool-id': xShinkaiToolId, + }, + responseType: 'json', + }, + ); + return response.data as AddToolRequestResponse; +}; + +export const uploadAssetsToTool = async ( + nodeAddress: string, + bearerToken: string, + xShinkaiAppId: string, + xShinkaiToolId: string, + files: File[], +) => { + for (const file of files) { + await uploadAssetTool( + nodeAddress, + bearerToken, + { + filename: encodeURIComponent(file.name), + file, + }, + xShinkaiAppId, + xShinkaiToolId, + ); + } + return { success: true }; +}; + +export const removeToolAsset = async ( + nodeAddress: string, + bearerToken: string, + payload: RemoveToolRequestRequest, + xShinkaiAppId: string, + xShinkaiToolId: string, +) => { + const response = await httpClient.delete( + urlJoin(nodeAddress, '/v2/tool_asset'), + { + headers: { + Authorization: `Bearer ${bearerToken}`, + 'x-shinkai-app-id': xShinkaiAppId, + 'x-shinkai-tool-id': xShinkaiToolId, + }, + params: { file_name: payload.filename }, + responseType: 'json', + }, + ); + return response.data; +}; diff --git a/libs/shinkai-message-ts/src/api/tools/types.ts b/libs/shinkai-message-ts/src/api/tools/types.ts index 979c3e715..be332505c 100644 --- a/libs/shinkai-message-ts/src/api/tools/types.ts +++ b/libs/shinkai-message-ts/src/api/tools/types.ts @@ -319,3 +319,18 @@ export type ExportToolRequest = { export type ExportToolResponse = Blob; export type GetShinkaiFileProtocolRequest = { file: string }; export type GetShinkaiFileProtocolResponse = Blob; + +export type GetAllToolAssetsResponse = string[]; +export type AddToolRequestRequest = { + filename: string; + file: File; +}; +export type AddToolRequestResponse = { + file: number; + file_name: string; + message: string; + status: string; +}; +export type RemoveToolRequestRequest = { + filename: string; +}; diff --git a/libs/shinkai-node-state/src/v2/constants.ts b/libs/shinkai-node-state/src/v2/constants.ts index b9e7de3b9..9c96d24dd 100644 --- a/libs/shinkai-node-state/src/v2/constants.ts +++ b/libs/shinkai-node-state/src/v2/constants.ts @@ -39,6 +39,7 @@ export enum FunctionKeyV2 { GET_RECURRING_TASK = 'GET_RECURRING_TASK', GET_RECURRING_TASK_LOGS = 'GET_RECURRING_TASK_LOGS', GET_SHINKAI_FILE_PROTOCOL = 'GET_SHINKAI_FILE_PROTOCOL', + GET_ALL_TOOL_ASSETS = 'GET_ALL_TOOL_ASSETS', } export const DEFAULT_CHAT_CONFIG = { diff --git a/libs/shinkai-node-state/src/v2/mutations/removeAssetTool/index.ts b/libs/shinkai-node-state/src/v2/mutations/removeAssetTool/index.ts new file mode 100644 index 000000000..83b02bac1 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/mutations/removeAssetTool/index.ts @@ -0,0 +1,21 @@ +import { removeToolAsset as removeToolAssetApi } from '@shinkai_network/shinkai-message-ts/api/tools/index'; + +import { RemoveAssetToToolInput } from './types'; + +export const removeToolAsset = async ({ + nodeAddress, + token, + filename, + xShinkaiAppId, + xShinkaiToolId, +}: RemoveAssetToToolInput) => { + const response = await removeToolAssetApi( + nodeAddress, + token, + { filename }, + xShinkaiAppId, + xShinkaiToolId, + ); + + return response; +}; diff --git a/libs/shinkai-node-state/src/v2/mutations/removeAssetTool/types.ts b/libs/shinkai-node-state/src/v2/mutations/removeAssetTool/types.ts new file mode 100644 index 000000000..281cdf4d7 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/mutations/removeAssetTool/types.ts @@ -0,0 +1,14 @@ +import { + CustomToolHeaders, + Token, +} from '@shinkai_network/shinkai-message-ts/api/general/types'; + +export type RemoveAssetToToolInput = Token & + CustomToolHeaders & { + nodeAddress: string; + filename: string; + }; + +export type RemoveAssetToToolOutput = { + success: boolean; +}; diff --git a/libs/shinkai-node-state/src/v2/mutations/removeAssetTool/useRemoveAssetTool.ts b/libs/shinkai-node-state/src/v2/mutations/removeAssetTool/useRemoveAssetTool.ts new file mode 100644 index 000000000..739545366 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/mutations/removeAssetTool/useRemoveAssetTool.ts @@ -0,0 +1,30 @@ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { FunctionKeyV2 } from '../../constants'; +import { APIError } from '../../types'; +import { removeToolAsset } from '.'; +import { RemoveAssetToToolInput, RemoveAssetToToolOutput } from './types'; + +type Options = UseMutationOptions< + RemoveAssetToToolOutput, + APIError, + RemoveAssetToToolInput +>; + +export const useRemoveAssetTool = (options?: Options) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: removeToolAsset, + ...options, + onSuccess: (response, variables, context) => { + queryClient.invalidateQueries({ + queryKey: [FunctionKeyV2.GET_ALL_TOOL_ASSETS], + }); + + if (options?.onSuccess) { + options.onSuccess(response, variables, context); + } + }, + }); +}; diff --git a/libs/shinkai-node-state/src/v2/mutations/uploadAssetsTool/index.ts b/libs/shinkai-node-state/src/v2/mutations/uploadAssetsTool/index.ts new file mode 100644 index 000000000..815830976 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/mutations/uploadAssetsTool/index.ts @@ -0,0 +1,21 @@ +import { uploadAssetsToTool as uploadAssetsToToolApi } from '@shinkai_network/shinkai-message-ts/api/tools/index'; + +import { UploadAssetsToToolInput } from './types'; + +export const uploadAssetsToTool = async ({ + nodeAddress, + token, + files, + xShinkaiAppId, + xShinkaiToolId, +}: UploadAssetsToToolInput) => { + const response = await uploadAssetsToToolApi( + nodeAddress, + token, + xShinkaiAppId, + xShinkaiToolId, + files, + ); + + return response; +}; diff --git a/libs/shinkai-node-state/src/v2/mutations/uploadAssetsTool/types.ts b/libs/shinkai-node-state/src/v2/mutations/uploadAssetsTool/types.ts new file mode 100644 index 000000000..aad6111cd --- /dev/null +++ b/libs/shinkai-node-state/src/v2/mutations/uploadAssetsTool/types.ts @@ -0,0 +1,14 @@ +import { + CustomToolHeaders, + Token, +} from '@shinkai_network/shinkai-message-ts/api/general/types'; + +export type UploadAssetsToToolInput = Token & + CustomToolHeaders & { + nodeAddress: string; + files: File[]; + }; + +export type UploadAssetsToToolOutput = { + success: boolean; +}; diff --git a/libs/shinkai-node-state/src/v2/mutations/uploadAssetsTool/useUploadAssetsTool.ts b/libs/shinkai-node-state/src/v2/mutations/uploadAssetsTool/useUploadAssetsTool.ts new file mode 100644 index 000000000..8d3ced0c5 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/mutations/uploadAssetsTool/useUploadAssetsTool.ts @@ -0,0 +1,30 @@ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { FunctionKeyV2 } from '../../constants'; +import { APIError } from '../../types'; +import { uploadAssetsToTool } from '.'; +import { UploadAssetsToToolInput, UploadAssetsToToolOutput } from './types'; + +type Options = UseMutationOptions< + UploadAssetsToToolOutput, + APIError, + UploadAssetsToToolInput +>; + +export const useUploadAssetsTool = (options?: Options) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: uploadAssetsToTool, + ...options, + onSuccess: (response, variables, context) => { + queryClient.invalidateQueries({ + queryKey: [FunctionKeyV2.GET_ALL_TOOL_ASSETS], + }); + + if (options?.onSuccess) { + options.onSuccess(response, variables, context); + } + }, + }); +}; diff --git a/libs/shinkai-node-state/src/v2/queries/getAllToolAssets/index.ts b/libs/shinkai-node-state/src/v2/queries/getAllToolAssets/index.ts new file mode 100644 index 000000000..b15154803 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/queries/getAllToolAssets/index.ts @@ -0,0 +1,18 @@ +import { getAllToolAssets as getAllToolAssetsApi } from '@shinkai_network/shinkai-message-ts/api/tools/index'; + +import type { GetAllToolAssetsInput } from './types'; + +export const getAllToolAssets = async ({ + nodeAddress, + token, + xShinkaiAppId, + xShinkaiToolId, +}: GetAllToolAssetsInput) => { + const result = await getAllToolAssetsApi( + nodeAddress, + token, + xShinkaiAppId, + xShinkaiToolId, + ); + return result; +}; diff --git a/libs/shinkai-node-state/src/v2/queries/getAllToolAssets/types.ts b/libs/shinkai-node-state/src/v2/queries/getAllToolAssets/types.ts new file mode 100644 index 000000000..b00117fa6 --- /dev/null +++ b/libs/shinkai-node-state/src/v2/queries/getAllToolAssets/types.ts @@ -0,0 +1,12 @@ +import { + CustomToolHeaders, + Token, +} from '@shinkai_network/shinkai-message-ts/api/general/types'; +import { GetAllToolAssetsResponse } from '@shinkai_network/shinkai-message-ts/api/tools/types'; + +export type GetAllToolAssetsInput = Token & + CustomToolHeaders & { + nodeAddress: string; + }; + +export type GetAllToolAssetsOutput = GetAllToolAssetsResponse; diff --git a/libs/shinkai-node-state/src/v2/queries/getAllToolAssets/useGetAllToolAssets.ts b/libs/shinkai-node-state/src/v2/queries/getAllToolAssets/useGetAllToolAssets.ts new file mode 100644 index 000000000..ba4cbdade --- /dev/null +++ b/libs/shinkai-node-state/src/v2/queries/getAllToolAssets/useGetAllToolAssets.ts @@ -0,0 +1,29 @@ +import { QueryObserverOptions, useQuery } from '@tanstack/react-query'; + +import { FunctionKeyV2 } from '../../constants'; +import { getAllToolAssets } from './index'; +import { GetAllToolAssetsInput, GetAllToolAssetsOutput } from './types'; + +export type UseGetAllToolAssets = [ + FunctionKeyV2.GET_ALL_TOOL_ASSETS, + GetAllToolAssetsInput, +]; +type Options = QueryObserverOptions< + GetAllToolAssetsOutput, + Error, + GetAllToolAssetsOutput, + GetAllToolAssetsOutput, + UseGetAllToolAssets +>; + +export const useGetAllToolAssets = ( + input: GetAllToolAssetsInput, + options?: Omit, +) => { + const response = useQuery({ + queryKey: [FunctionKeyV2.GET_ALL_TOOL_ASSETS, input], + queryFn: () => getAllToolAssets(input), + ...options, + }); + return response; +}; diff --git a/libs/shinkai-ui/src/assets/icons/general.tsx b/libs/shinkai-ui/src/assets/icons/general.tsx index 7a7bb8ef3..56f493b64 100644 --- a/libs/shinkai-ui/src/assets/icons/general.tsx +++ b/libs/shinkai-ui/src/assets/icons/general.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import { cn } from '../../utils'; import { fileIconMap } from './file'; @@ -1258,3 +1256,43 @@ export const AIAgentIcon = ({ className }: { className?: string }) => ( /> ); +export const ToolAssetsIcon = ({ className }: { className?: string }) => ( + + + + + + + +); diff --git a/libs/shinkai-ui/src/helpers/format-text.ts b/libs/shinkai-ui/src/helpers/format-text.ts index 927f2c501..c945080c6 100644 --- a/libs/shinkai-ui/src/helpers/format-text.ts +++ b/libs/shinkai-ui/src/helpers/format-text.ts @@ -9,3 +9,15 @@ export const formatText = (text: string) => { return result.charAt(0).toUpperCase() + result.slice(1); }; + +export const formatCamelCaseText = (text: string) => { + const words = text.split(/(?=[A-Z])/); + + const formattedWords = words.map((word) => { + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }); + + const result = formattedWords.join(' '); + + return result.charAt(0).toUpperCase() + result.slice(1); +};