Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: imported tool names and icons on edit page #241

Merged
merged 1 commit into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading