-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: imported tool names and icons on edit page
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
Showing
4 changed files
with
188 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
|
@@ -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, | ||
|
@@ -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; | ||
|
@@ -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> | ||
|
@@ -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 | ||
|
@@ -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" />; | ||
} | ||
}; |
Oops, something went wrong.