Skip to content

Commit

Permalink
fix: imported tool names and icons on edit page
Browse files Browse the repository at this point in the history
Fix the imported tools section of the edit page by:
- Loading remote tools to get the correct display names
- Using the full remote tool refs to select icons

This ensures tools get display names and icons that match the ones
used in the tool catalog.

Signed-off-by: Nick Hale <[email protected]>
  • Loading branch information
njhale committed Aug 23, 2024
1 parent 2f9831a commit 3d4ba2a
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 125 deletions.
15 changes: 13 additions & 2 deletions actions/gptscript.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use server';

import { Tool, Block, Text } from '@gptscript-ai/gptscript';
import { Tool, Block, Text, Program } from '@gptscript-ai/gptscript';
import { gpt } from '@/config/env';

export const rootTool = async (toolContent: string): Promise<Tool> => {
Expand All @@ -12,13 +12,24 @@ export const rootTool = async (toolContent: string): Promise<Tool> => {
return {} as Tool;
};

export const parse = async (toolContent: string): Promise<Tool[]> => {
export const parseContent = async (toolContent: string): Promise<Tool[]> => {
const parsedTool = await gpt().parseContent(toolContent);
return parsedTool.filter(
(block) => block.type === 'tool' && !block.name?.startsWith('metadata')
) as Tool[];
};

export const parse = async (file: string): Promise<Tool[]> => {
const parsedTool = await gpt().parse(file);
return parsedTool.filter(
(block) => block.type === 'tool' && !block.name?.startsWith('metadata')
) as Tool[];
};

export const load = async (file: string): Promise<Program> => {
return (await gpt().load(file)).program;
};

export const getTexts = async (toolContent: string): Promise<Text[]> => {
const parsedTool = await gpt().parseContent(toolContent);
return parsedTool.filter((block) => block.type === 'text') as Text[];
Expand Down
290 changes: 171 additions & 119 deletions components/edit/configure/imports.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useContext } from 'react';
import { useContext, useEffect, useState } from 'react';
import { Button } from '@nextui-org/react';
import {
GoBook,
Expand Down Expand Up @@ -33,7 +33,14 @@ import {
} from 'react-icons/pi';
import { EditContext } from '@/contexts/edit';
import CustomTool from '@/components/edit/configure/customTool';
import { SiAmazoneks, SiGooglecloud, SiJson, SiSupabase } from 'react-icons/si';
import {
SiAmazoneks,
SiGooglecloud,
SiJson,
SiMongodb,
SiNotion,
SiSupabase,
} from 'react-icons/si';
import { VscAzure } from 'react-icons/vsc';
import {
BsClock,
Expand All @@ -47,6 +54,8 @@ import {
import { MdDeleteForever } from 'react-icons/md';
import PropTypes from 'prop-types';

import { load } from '@/actions/gptscript';

interface ImportsProps {
tools: string[] | undefined;
setTools: (tools: string[]) => void;
Expand All @@ -62,143 +71,70 @@ const Imports: React.FC<ImportsProps> = ({
collapsed,
enableLocal = 'true',
}) => {
const [remoteTools, setRemoteTools] = useState<string[]>([]);
// remoteTools contains a mapping of tool references to display names for
const [remoteTools, setRemoteTools] = useState<Map<string, string>>(
new Map()
);
const [localTools, setLocalTools] = useState<string[]>([]);
const { createNewTool, deleteLocalTool } = useContext(EditContext);

const updateRemoteTools = async (remoteRefs: string[]) => {
const updatedRemoteTools = new Map(remoteTools);
for (const ref of remoteRefs) {
if (updatedRemoteTools.has(ref)) {
// We've already the display name of this tool
continue;
}
updatedRemoteTools.set(ref, await getDisplayName(ref));
}

setRemoteTools(() => {
const newRemoteTools = new Map();
updatedRemoteTools.forEach((value, key) => {
newRemoteTools.set(key, value);
});
return newRemoteTools;
});
};

useEffect(() => {
if (tools) {
setLocalTools(
tools.filter(
(t) =>
!(
t.startsWith('https://') ||
t.startsWith('http://') ||
t.startsWith('sys.') || // not local, but considered remote for the purposes of this component
t.startsWith('github.com')
)
)
);
setRemoteTools(
tools.filter(
(t) =>
t.startsWith('https://') ||
t.startsWith('http://') ||
t.startsWith('sys.') || // not local, but considered remote for the purposes of this component
t.startsWith('github.com')
)
);
setLocalTools(tools.filter((t) => !isRemote(t)));
updateRemoteTools(tools.filter(isRemote)).catch((e) => {
console.error('failed to update remote tools', e);
});
}
}, [tools]);

// note - I know this is a bit of a mess, but it's a quick way to get icons for tools
const iconForTool = (tool: string) => {
switch (tool.split('/').pop()?.replace(/-/g, ' ')) {
case 'gpt4 v vision':
return <BsEye className="text-md" />;
case 'dalle image generation':
return <FaPaintBrush className="text-md" />;
case 'answers from the internet':
return <GoGlobe className="text-md" />;
case 'search website':
return <GoSearch className="text-md" />;
case 'browser':
return <GoBrowser className="text-md" />;
case 'write':
return <AiOutlineSlack className="text-md" />;
case 'trello':
return <FaTrello className="text-md" />;
case 'manage':
return <PiMicrosoftOutlookLogoDuotone className="text-md" />;
case 'knowledge':
return <GoBook className="text-md" />;
case 'structured data querier':
return <PiMicrosoftExcelLogo className="text-md" />;
case 'json query':
return <SiJson className="text-md" />;
case 'filesystem':
return <BsFiles className="text-md" />;
case 'workspace':
return <GoFileDirectory className="text-md" />;
case 'github':
return <FaGithub className="text-md" />;
case 'aws':
return <FaAws className="text-md" />;
case 'azure':
return <VscAzure className="text-md" />;
case 'digitalocean':
return <FaDigitalOcean className="text-md" />;
case 'eksctl':
return <SiAmazoneks className="text-md" />;
case 'gcp':
return <SiGooglecloud className="text-md" />;
case 'k8s':
return <AiOutlineKubernetes className="text-md" />;
case 'read-write':
return <SiSupabase className="text-md" />;
case 'supabase':
return <SiSupabase className="text-md" />;
case 'sys.append':
return <AiFillFileAdd className="text-md" />;
case 'sys.download':
return <BsDownload className="text-md" />;
case 'sys.exec':
return <GoTerminal className="text-md" />;
case 'sys.find':
return <BsFiles className="text-md" />;
case 'sys.getenv':
return <BsCode className="text-md" />;
case 'sys.http.html2text':
return <FaCode className="text-md" />;
case 'sys.http.get':
return <GoGlobe className="text-md" />;
case 'sys.http.post':
return <GoGlobe className="text-md" />;
case 'sys.ls':
return <BsFolder className="text-md" />;
case 'sys.prompt':
return <GoQuestion className="text-md" />;
case 'sys.read':
return <FaGlasses className="text-md" />;
case 'sys.remove':
return <MdDeleteForever className="text-md" />;
case 'sys.stat':
return <BsSearch className="text-md" />;
case 'sys.time.now':
return <BsClock className="text-md" />;
case 'sys.write':
return <GoPencil className="text-md" />;
}
};
const deleteRemoteTool = (tool: string) => {
// Remove the remote tool's display name mapping
setRemoteTools((prevRemoteTools) => {
const newRemoteTools = new Map(prevRemoteTools);
newRemoteTools.delete(tool);
return newRemoteTools;
});

const handleDeleteTool = (tool: string) => {
// Remove the tool from the assistant
setTools(tools!.filter((t) => t !== tool));
};

return (
<div className={`${className}`}>
{remoteTools && remoteTools.length > 0 && (
{remoteTools && remoteTools.size > 0 && (
<div className="grid grid-cols-1 gap-2 w-full mb-2">
{remoteTools.map((tool, i) => (
{Array.from(remoteTools.keys()).map((ref, i) => (
<div key={i} className="flex space-x-2">
<div className="truncate w-full border-2 dark:border-zinc-700 text-sm pl-2 rounded-lg flex justify-between items-center">
<div className="flex items-center space-x-2">
{iconForTool(tool)}
<p className="capitalize">
{tool
.split('/')
.pop()
?.replace(/-/g, ' ')
.replace('sys.', '')
.replace('.', ' ')}
</p>
{iconForTool(ref)}
<p className="capitalize">{remoteTools.get(ref)!}</p>
</div>
<Button
variant="light"
isIconOnly
size="sm"
startContent={<GoTrash />}
onPress={() => handleDeleteTool(tool)}
onPress={() => deleteRemoteTool(ref)}
/>
</div>
</div>
Expand Down Expand Up @@ -234,10 +170,16 @@ const Imports: React.FC<ImportsProps> = ({
>
<ToolCatalogModal
tools={tools}
addTool={(tool) => setTools([...(tools || []), tool])}
removeTool={(tool) =>
setTools(tools?.filter((t) => t !== tool) || [])
}
addTool={(tool) => {
setTools([...(tools || []), tool]);
}}
removeTool={(tool) => {
if (isRemote(tool)) {
deleteRemoteTool(tool);
} else {
setTools(tools?.filter((t) => t !== tool) || []);
}
}}
/>
{enableLocal && (
<Button
Expand Down Expand Up @@ -266,3 +208,113 @@ Imports.propTypes = {
};

export default Imports;

function isRemote(ref: string): boolean {
return (
ref.startsWith('https://') ||
ref.startsWith('http://') ||
ref.startsWith('sys.') || // not local, but considered remote for the purposes of this component
ref.startsWith('github.com')
);
}

async function getDisplayName(ref: string): Promise<string> {
let displayName: string =
ref.split('/').pop()?.replace('sys.', '').replace('.', ' ') ?? ref;

if (!ref.startsWith('sys.')) {
const loadedTool = await load(ref);
// TODO: Use entryToolId field once node-gptscript is bumped to a release containing 317e6457f056718bd9fdade18d6fbf9e0311cd46
const entryToolId = (loadedTool as any)['entryToolId'];
const loadedName = loadedTool.toolSet[entryToolId].name;
if (loadedName) {
displayName = loadedName;
}
}

return displayName.replace(/-/g, ' ');
}

// note - I know this is a bit of a mess, but it's a quick way to get icons for tools
const iconForTool = (toolName: string | undefined) => {
switch (toolName) {
case 'github.com/gptscript-ai/gpt4-v-vision@gateway':
return <BsEye className="text-md" />;
case 'github.com/gptscript-ai/dalle-image-generation@gateway':
return <FaPaintBrush className="text-md" />;
case 'github.com/gptscript-ai/answers-from-the-internet':
return <GoGlobe className="text-md" />;
case 'github.com/gptscript-ai/search-website':
return <GoSearch className="text-md" />;
case 'github.com/gptscript-ai/browser':
return <GoBrowser className="text-md" />;
case 'github.com/gptscript-ai/tools/apis/slack/write':
return <AiOutlineSlack className="text-md" />;
case 'github.com/gptscript-ai/tools/apis/notion/write':
return <SiNotion className="text-md" />;
case 'github.com/gptscript-ai/tools/apis/trello':
return <FaTrello className="text-md" />;
case 'github.com/gptscript-ai/tools/apis/outlook/mail/manage':
return <PiMicrosoftOutlookLogoDuotone className="text-md" />;
case 'github.com/gptscript-ai/tools/apis/outlook/calendar/manage':
return <PiMicrosoftOutlookLogoDuotone className="text-md" />;
case 'github.com/gptscript-ai/[email protected]':
return <GoBook className="text-md" />;
case 'github.com/gptscript-ai/structured-data-querier':
return <PiMicrosoftExcelLogo className="text-md" />;
case 'github.com/gptscript-ai/json-query':
return <SiJson className="text-md" />;
case 'github.com/gptscript-ai/context/filesystem':
return <BsFiles className="text-md" />;
case 'github.com/gptscript-ai/context/workspace':
return <GoFileDirectory className="text-md" />;
case 'github.com/gptscript-ai/tools/clis/github':
return <FaGithub className="text-md" />;
case 'github.com/gptscript-ai/tools/clis/aws':
return <FaAws className="text-md" />;
case 'github.com/gptscript-ai/tools/clis/azure':
return <VscAzure className="text-md" />;
case 'github.com/gptscript-ai/tools/clis/digitalocean':
return <FaDigitalOcean className="text-md" />;
case 'github.com/gptscript-ai/tools/clis/eksctl':
return <SiAmazoneks className="text-md" />;
case 'github.com/gptscript-ai/tools/clis/atlas':
return <SiMongodb className="text-md" />;
case 'github.com/gptscript-ai/tools/clis/gcp':
return <SiGooglecloud className="text-md" />;
case 'github.com/gptscript-ai/tools/clis/k8s':
return <AiOutlineKubernetes className="text-md" />;
case 'github.com/gptscript-ai/tools/clis/supabase':
return <SiSupabase className="text-md" />;
case 'sys.append':
return <AiFillFileAdd className="text-md" />;
case 'sys.download':
return <BsDownload className="text-md" />;
case 'sys.exec':
return <GoTerminal className="text-md" />;
case 'sys.find':
return <BsFiles className="text-md" />;
case 'sys.getenv':
return <BsCode className="text-md" />;
case 'sys.http.html2text':
return <FaCode className="text-md" />;
case 'sys.http.get':
return <GoGlobe className="text-md" />;
case 'sys.http.post':
return <GoGlobe className="text-md" />;
case 'sys.ls':
return <BsFolder className="text-md" />;
case 'sys.prompt':
return <GoQuestion className="text-md" />;
case 'sys.read':
return <FaGlasses className="text-md" />;
case 'sys.remove':
return <MdDeleteForever className="text-md" />;
case 'sys.stat':
return <BsSearch className="text-md" />;
case 'sys.time.now':
return <BsClock className="text-md" />;
case 'sys.write':
return <GoPencil className="text-md" />;
}
};
Loading

0 comments on commit 3d4ba2a

Please sign in to comment.