Skip to content

Commit

Permalink
feat: implement import + export tools (#557)
Browse files Browse the repository at this point in the history
* feat: import + export tools

* feat: export network tools

* fixes

* clean up
  • Loading branch information
paulclindo authored Dec 6, 2024
1 parent b3872cb commit 8f4bc32
Show file tree
Hide file tree
Showing 15 changed files with 492 additions and 94 deletions.
2 changes: 1 addition & 1 deletion apps/shinkai-desktop/src/components/agent/add-agent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ function AddAgentPage() {
tool.tool_router_key,
],
);
console.log(tool.config, 'tool.config');

return;
}
toast.error(
Expand Down
98 changes: 83 additions & 15 deletions apps/shinkai-desktop/src/components/tools/deno-tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DenoShinkaiTool,
ShinkaiTool,
} from '@shinkai_network/shinkai-message-ts/api/tools/types';
import { useExportTool } from '@shinkai_network/shinkai-node-state/v2/mutations/exportTool/useExportTool';
import { useUpdateTool } from '@shinkai_network/shinkai-node-state/v2/mutations/updateTool/useUpdateTool';
import {
Button,
Expand All @@ -15,14 +16,17 @@ import {
} from '@shinkai_network/shinkai-ui';
import { 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';
import { BaseDirectory } from '@tauri-apps/plugin-fs';
import { DownloadIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { Link, useParams } from 'react-router-dom';
import { toast } from 'sonner';
import { z } from 'zod';

import { SubpageLayout } from '../../pages/layout/simple-layout';
import { useAuth } from '../../store/auth';

const jsToolSchema = z.object({
config: z.array(
z.object({
Expand All @@ -44,6 +48,7 @@ export default function DenoTool({
isPlaygroundTool?: boolean;
}) {
const auth = useAuth((state) => state.auth);
const { toolKey } = useParams();

const { t } = useTranslation();
const { mutateAsync: updateTool, isPending } = useUpdateTool({
Expand All @@ -55,8 +60,52 @@ export default function DenoTool({
toast.success('Tool configuration updated successfully');
}
},
onError: (error) => {
toast.error('Failed to update tool', {
description: error.response?.data?.message ?? error.message,
});
},
});
const { toolKey } = useParams();

const { mutateAsync: exportTool, isPending: isExportingTool } = useExportTool(
{
onSuccess: async (response, variables) => {
const toolName = variables.toolKey.split(':::')?.[1] ?? 'untitled_tool';
const file = new Blob([response ?? ''], {
type: 'application/octet-stream',
});

const arrayBuffer = await file.arrayBuffer();
const content = new Uint8Array(arrayBuffer);

const savePath = await save({
defaultPath: `${toolName}.zip`,
filters: [
{
name: 'Zip File',
extensions: ['zip'],
},
],
});

if (!savePath) {
toast.info('File saving cancelled');
return;
}

await fs.writeFile(savePath, content, {
baseDir: BaseDirectory.Download,
});

toast.success('Tool exported successfully');
},
onError: (error) => {
toast.error('Failed to export tool', {
description: error.response?.data?.message ?? error.message,
});
},
},
);

const form = useForm<JsToolFormSchema>({
resolver: zodResolver(jsToolSchema),
Expand Down Expand Up @@ -99,6 +148,23 @@ export default function DenoTool({

return (
<SubpageLayout alignLeft title={formatText(tool.name)}>
<Button
className="absolute right-0 top-9 flex h-[30px] items-center gap-2 rounded-lg bg-gray-500 text-xs"
disabled={isExportingTool}
isLoading={isExportingTool}
onClick={() => {
exportTool({
toolKey: toolKey ?? '',
nodeAddress: auth?.node_address ?? '',
token: auth?.api_v2_key ?? '',
});
}}
size="auto"
variant="outline"
>
<DownloadIcon className="h-4 w-4" />
Export
</Button>
<div className="flex flex-col">
<div className="mb-4 flex items-center justify-between gap-1">
<p className="text-sm text-white">Enabled</p>
Expand Down Expand Up @@ -175,19 +241,21 @@ export default function DenoTool({
</Form>
</div>
)}
{isPlaygroundTool && (
<Link
className={cn(
buttonVariants({
size: 'sm',
variant: 'outline',
}),
)}
to={`/tools/edit/${toolKey}`}
>
Go Playground
</Link>
)}
<div className="space-y-4 py-4">
{isPlaygroundTool && (
<Link
className={cn(
buttonVariants({
size: 'sm',
variant: 'outline',
}),
)}
to={`/tools/edit/${toolKey}`}
>
Go Playground
</Link>
)}
</div>
</div>
</SubpageLayout>
);
Expand Down
179 changes: 119 additions & 60 deletions apps/shinkai-desktop/src/components/tools/network-tool.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,56 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useTranslation } from '@shinkai_network/shinkai-i18n';
// import { zodResolver } from '@hookform/resolvers/zod';
// import { useTranslation } from '@shinkai_network/shinkai-i18n';
import {
NetworkShinkaiTool,
ShinkaiTool,
} from '@shinkai_network/shinkai-message-ts/api/tools/types';
import { useExportTool } from '@shinkai_network/shinkai-node-state/v2/mutations/exportTool/useExportTool';
import { useUpdateTool } from '@shinkai_network/shinkai-node-state/v2/mutations/updateTool/useUpdateTool';
import {
Button,
buttonVariants,
Form,
FormField,
// Button,
// buttonVariants,
// Form,
// FormField,
Switch,
TextField,
// TextField,
} from '@shinkai_network/shinkai-ui';
import { formatText } from '@shinkai_network/shinkai-ui/helpers';
import { cn } from '@shinkai_network/shinkai-ui/utils';
import { useForm } from 'react-hook-form';
import { Link, useParams } from 'react-router-dom';
import { save } from '@tauri-apps/plugin-dialog';
import * as fs from '@tauri-apps/plugin-fs';
import { BaseDirectory } from '@tauri-apps/plugin-fs';
import { DownloadIcon } from 'lucide-react';
// import { cn } from '@shinkai_network/shinkai-ui/utils';
// import { useForm } from 'react-hook-form';
import { useParams } from 'react-router-dom';
import { toast } from 'sonner';
import { z } from 'zod';

// import { z } from 'zod';
import { SubpageLayout } from '../../pages/layout/simple-layout';
import { useAuth } from '../../store/auth';

const jsToolSchema = z.object({
config: z.array(
z.object({
key_name: z.string(),
key_value: z.string().optional(),
required: z.boolean(),
}),
),
});
type JsToolFormSchema = z.infer<typeof jsToolSchema>;
// const jsToolSchema = z.object({
// config: z.array(
// z.object({
// key_name: z.string(),
// key_value: z.string().optional(),
// required: z.boolean(),
// }),
// ),
// });
// type JsToolFormSchema = z.infer<typeof jsToolSchema>;

export default function NetworkTool({
tool,
isEnabled,
// isPlaygroundTool,
}: {
tool: NetworkShinkaiTool;
isEnabled: boolean;
// isPlaygroundTool?: boolean;
}) {
const auth = useAuth((state) => state.auth);
const { toolKey } = useParams();

const { t } = useTranslation();
const { mutateAsync: updateTool, isPending } = useUpdateTool({
const { mutateAsync: updateTool } = useUpdateTool({
onSuccess: (_, variables) => {
if (
'config' in variables.toolPayload &&
Expand All @@ -56,49 +60,104 @@ export default function NetworkTool({
}
},
});
const { toolKey } = useParams();
const { mutateAsync: exportTool, isPending: isExportingTool } = useExportTool(
{
onSuccess: async (response, variables) => {
const toolName = variables.toolKey.split(':::')?.[1] ?? 'untitled_tool';
const file = new Blob([response ?? ''], {
type: 'application/octet-stream',
});

const form = useForm<JsToolFormSchema>({
resolver: zodResolver(jsToolSchema),
defaultValues: {
// config: tool.config.map((conf) => ({
// key_name: conf.BasicConfig.key_name,
// key_value: conf.BasicConfig.key_value ?? '',
// required: conf.BasicConfig.required,
// })),
},
});
const arrayBuffer = await file.arrayBuffer();
const content = new Uint8Array(arrayBuffer);

const savePath = await save({
defaultPath: `${toolName}.zip`,
filters: [
{
name: 'Zip File',
extensions: ['zip'],
},
],
});

const onSubmit = async (data: JsToolFormSchema) => {
let enabled = isEnabled;
if (!savePath) {
toast.info('File saving cancelled');
return;
}

await fs.writeFile(savePath, content, {
baseDir: BaseDirectory.Download,
});

toast.success('Tool exported successfully');
},
onError: (error) => {
toast.error('Failed to export tool', {
description: error.response?.data?.message ?? error.message,
});
},
},
);

if (
data.config.every(
(conf) => !conf.required || (conf.required && conf.key_value !== ''),
)
) {
enabled = true;
}
// const form = useForm<JsToolFormSchema>({
// resolver: zodResolver(jsToolSchema),
// defaultValues: {
// // config: tool.config.map((conf) => ({
// // key_name: conf.BasicConfig.key_name,
// // key_value: conf.BasicConfig.key_value ?? '',
// // required: conf.BasicConfig.required,
// // })),
// },
// });

await updateTool({
toolKey: toolKey ?? '',
toolType: 'Network',
toolPayload: {
config: data.config.map((conf) => ({
BasicConfig: {
key_name: conf.key_name,
key_value: conf.key_value,
},
})),
} as ShinkaiTool,
isToolEnabled: enabled,
nodeAddress: auth?.node_address ?? '',
token: auth?.api_v2_key ?? '',
});
};
// const onSubmit = async (data: JsToolFormSchema) => {
// let enabled = isEnabled;
//
// if (
// data.config.every(
// (conf) => !conf.required || (conf.required && conf.key_value !== ''),
// )
// ) {
// enabled = true;
// }
//
// await updateTool({
// toolKey: toolKey ?? '',
// toolType: 'Network',
// toolPayload: {
// config: data.config.map((conf) => ({
// BasicConfig: {
// key_name: conf.key_name,
// key_value: conf.key_value,
// },
// })),
// } as ShinkaiTool,
// isToolEnabled: enabled,
// nodeAddress: auth?.node_address ?? '',
// token: auth?.api_v2_key ?? '',
// });
// };

return (
<SubpageLayout alignLeft title={formatText(tool.name)}>
<Button
className="absolute right-0 top-9 flex h-[30px] items-center gap-2 rounded-lg bg-gray-500 text-xs"
disabled={isExportingTool}
isLoading={isExportingTool}
onClick={() => {
exportTool({
toolKey: toolKey ?? '',
nodeAddress: auth?.node_address ?? '',
token: auth?.api_v2_key ?? '',
});
}}
size="sm"
variant="outline"
>
<DownloadIcon className="h-4 w-4" />
Export
</Button>
<div className="flex flex-col">
<div className="mb-4 flex items-center justify-between gap-1">
<p className="text-sm text-white">Enabled</p>
Expand Down
2 changes: 1 addition & 1 deletion apps/shinkai-desktop/src/pages/create-tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,7 @@ export function ToolSelectionModal({
)
: [...field.value, tool.tool_router_key],
);
console.log(tool.config, 'tool.config');

return;
}
toast.error('Tool configuration is required', {
Expand Down
2 changes: 1 addition & 1 deletion apps/shinkai-desktop/src/pages/layout/simple-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const SubpageLayout = ({
}) => {
const { t } = useTranslation();
return (
<div className={cn('relative mx-auto max-w-xl py-10', className)}>
<div className={cn('relative mx-auto max-w-2xl py-10', className)}>
<Link
className={cn('absolute left-4', alignLeft && 'left-0')}
to={-1 as To}
Expand Down
Loading

0 comments on commit 8f4bc32

Please sign in to comment.