Skip to content

Commit b2361ae

Browse files
committed
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]>
1 parent d7b8724 commit b2361ae

File tree

4 files changed

+192
-125
lines changed

4 files changed

+192
-125
lines changed

actions/gptscript.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use server';
22

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

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

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

22+
export const parse = async (file: string): Promise<Tool[]> => {
23+
const parsedTool = await gpt().parse(file);
24+
return parsedTool.filter(
25+
(block) => block.type === 'tool' && !block.name?.startsWith('metadata')
26+
) as Tool[];
27+
};
28+
29+
export const load = async (file: string): Promise<Program> => {
30+
return (await gpt().load(file)).program;
31+
};
32+
2233
export const getTexts = async (toolContent: string): Promise<Text[]> => {
2334
const parsedTool = await gpt().parseContent(toolContent);
2435
return parsedTool.filter((block) => block.type === 'text') as Text[];
Lines changed: 175 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useContext } from 'react';
1+
import { useContext, useEffect, useState } from 'react';
22
import { Button } from '@nextui-org/react';
33
import {
44
GoBook,
@@ -33,7 +33,14 @@ import {
3333
} from 'react-icons/pi';
3434
import { EditContext } from '@/contexts/edit';
3535
import CustomTool from '@/components/edit/configure/customTool';
36-
import { SiAmazoneks, SiGooglecloud, SiJson, SiSupabase } from 'react-icons/si';
36+
import {
37+
SiAmazoneks,
38+
SiGooglecloud,
39+
SiJson,
40+
SiMongodb,
41+
SiNotion,
42+
SiSupabase,
43+
} from 'react-icons/si';
3744
import { VscAzure } from 'react-icons/vsc';
3845
import {
3946
BsClock,
@@ -47,6 +54,8 @@ import {
4754
import { MdDeleteForever } from 'react-icons/md';
4855
import PropTypes from 'prop-types';
4956

57+
import { load } from '@/actions/gptscript';
58+
5059
interface ImportsProps {
5160
tools: string[] | undefined;
5261
setTools: (tools: string[]) => void;
@@ -62,143 +71,70 @@ const Imports: React.FC<ImportsProps> = ({
6271
collapsed,
6372
enableLocal = 'true',
6473
}) => {
65-
const [remoteTools, setRemoteTools] = useState<string[]>([]);
74+
// remoteTools contains a mapping of tool references to display names for
75+
const [remoteTools, setRemoteTools] = useState<Map<string, string>>(
76+
new Map()
77+
);
6678
const [localTools, setLocalTools] = useState<string[]>([]);
6779
const { createNewTool, deleteLocalTool } = useContext(EditContext);
6880

81+
const updateRemoteTools = async (remoteRefs: string[]) => {
82+
const updatedRemoteTools = new Map(remoteTools);
83+
for (const ref of remoteRefs) {
84+
if (updatedRemoteTools.has(ref)) {
85+
// We've already the display name of this tool
86+
continue;
87+
}
88+
updatedRemoteTools.set(ref, await getDisplayName(ref));
89+
}
90+
91+
setRemoteTools(() => {
92+
const newRemoteTools = new Map();
93+
updatedRemoteTools.forEach((value, key) => {
94+
newRemoteTools.set(key, value);
95+
});
96+
return newRemoteTools;
97+
});
98+
};
99+
69100
useEffect(() => {
70101
if (tools) {
71-
setLocalTools(
72-
tools.filter(
73-
(t) =>
74-
!(
75-
t.startsWith('https://') ||
76-
t.startsWith('http://') ||
77-
t.startsWith('sys.') || // not local, but considered remote for the purposes of this component
78-
t.startsWith('github.com')
79-
)
80-
)
81-
);
82-
setRemoteTools(
83-
tools.filter(
84-
(t) =>
85-
t.startsWith('https://') ||
86-
t.startsWith('http://') ||
87-
t.startsWith('sys.') || // not local, but considered remote for the purposes of this component
88-
t.startsWith('github.com')
89-
)
90-
);
102+
setLocalTools(tools.filter((t) => !isRemote(t)));
103+
updateRemoteTools(tools.filter(isRemote)).catch((e) => {
104+
console.error('failed to update remote tools', e);
105+
});
91106
}
92107
}, [tools]);
93108

94-
// note - I know this is a bit of a mess, but it's a quick way to get icons for tools
95-
const iconForTool = (tool: string) => {
96-
switch (tool.split('/').pop()?.replace(/-/g, ' ')) {
97-
case 'gpt4 v vision':
98-
return <BsEye className="text-md" />;
99-
case 'dalle image generation':
100-
return <FaPaintBrush className="text-md" />;
101-
case 'answers from the internet':
102-
return <GoGlobe className="text-md" />;
103-
case 'search website':
104-
return <GoSearch className="text-md" />;
105-
case 'browser':
106-
return <GoBrowser className="text-md" />;
107-
case 'write':
108-
return <AiOutlineSlack className="text-md" />;
109-
case 'trello':
110-
return <FaTrello className="text-md" />;
111-
case 'manage':
112-
return <PiMicrosoftOutlookLogoDuotone className="text-md" />;
113-
case 'knowledge':
114-
return <GoBook className="text-md" />;
115-
case 'structured data querier':
116-
return <PiMicrosoftExcelLogo className="text-md" />;
117-
case 'json query':
118-
return <SiJson className="text-md" />;
119-
case 'filesystem':
120-
return <BsFiles className="text-md" />;
121-
case 'workspace':
122-
return <GoFileDirectory className="text-md" />;
123-
case 'github':
124-
return <FaGithub className="text-md" />;
125-
case 'aws':
126-
return <FaAws className="text-md" />;
127-
case 'azure':
128-
return <VscAzure className="text-md" />;
129-
case 'digitalocean':
130-
return <FaDigitalOcean className="text-md" />;
131-
case 'eksctl':
132-
return <SiAmazoneks className="text-md" />;
133-
case 'gcp':
134-
return <SiGooglecloud className="text-md" />;
135-
case 'k8s':
136-
return <AiOutlineKubernetes className="text-md" />;
137-
case 'read-write':
138-
return <SiSupabase className="text-md" />;
139-
case 'supabase':
140-
return <SiSupabase className="text-md" />;
141-
case 'sys.append':
142-
return <AiFillFileAdd className="text-md" />;
143-
case 'sys.download':
144-
return <BsDownload className="text-md" />;
145-
case 'sys.exec':
146-
return <GoTerminal className="text-md" />;
147-
case 'sys.find':
148-
return <BsFiles className="text-md" />;
149-
case 'sys.getenv':
150-
return <BsCode className="text-md" />;
151-
case 'sys.http.html2text':
152-
return <FaCode className="text-md" />;
153-
case 'sys.http.get':
154-
return <GoGlobe className="text-md" />;
155-
case 'sys.http.post':
156-
return <GoGlobe className="text-md" />;
157-
case 'sys.ls':
158-
return <BsFolder className="text-md" />;
159-
case 'sys.prompt':
160-
return <GoQuestion className="text-md" />;
161-
case 'sys.read':
162-
return <FaGlasses className="text-md" />;
163-
case 'sys.remove':
164-
return <MdDeleteForever className="text-md" />;
165-
case 'sys.stat':
166-
return <BsSearch className="text-md" />;
167-
case 'sys.time.now':
168-
return <BsClock className="text-md" />;
169-
case 'sys.write':
170-
return <GoPencil className="text-md" />;
171-
}
172-
};
109+
const deleteRemoteTool = (tool: string) => {
110+
// Remove the remote tool's display name mapping
111+
setRemoteTools((prevRemoteTools) => {
112+
const newRemoteTools = new Map(prevRemoteTools);
113+
newRemoteTools.delete(tool);
114+
return newRemoteTools;
115+
});
173116

174-
const handleDeleteTool = (tool: string) => {
117+
// Remove the tool from the assistant
175118
setTools(tools!.filter((t) => t !== tool));
176119
};
177120

178121
return (
179122
<div className={`${className}`}>
180-
{remoteTools && remoteTools.length > 0 && (
123+
{remoteTools && remoteTools.size > 0 && (
181124
<div className="grid grid-cols-1 gap-2 w-full mb-2">
182-
{remoteTools.map((tool, i) => (
125+
{Array.from(remoteTools.keys()).map((ref, i) => (
183126
<div key={i} className="flex space-x-2">
184127
<div className="truncate w-full border-2 dark:border-zinc-700 text-sm pl-2 rounded-lg flex justify-between items-center">
185128
<div className="flex items-center space-x-2">
186-
{iconForTool(tool)}
187-
<p className="capitalize">
188-
{tool
189-
.split('/')
190-
.pop()
191-
?.replace(/-/g, ' ')
192-
.replace('sys.', '')
193-
.replace('.', ' ')}
194-
</p>
129+
{iconForTool(ref)}
130+
<p className="capitalize">{remoteTools.get(ref)!}</p>
195131
</div>
196132
<Button
197133
variant="light"
198134
isIconOnly
199135
size="sm"
200136
startContent={<GoTrash />}
201-
onPress={() => handleDeleteTool(tool)}
137+
onPress={() => deleteRemoteTool(ref)}
202138
/>
203139
</div>
204140
</div>
@@ -234,10 +170,16 @@ const Imports: React.FC<ImportsProps> = ({
234170
>
235171
<ToolCatalogModal
236172
tools={tools}
237-
addTool={(tool) => setTools([...(tools || []), tool])}
238-
removeTool={(tool) =>
239-
setTools(tools?.filter((t) => t !== tool) || [])
240-
}
173+
addTool={(tool) => {
174+
setTools([...(tools || []), tool]);
175+
}}
176+
removeTool={(tool) => {
177+
if (isRemote(tool)) {
178+
deleteRemoteTool(tool);
179+
} else {
180+
setTools(tools?.filter((t) => t !== tool) || []);
181+
}
182+
}}
241183
/>
242184
{enableLocal && (
243185
<Button
@@ -266,3 +208,117 @@ Imports.propTypes = {
266208
};
267209

268210
export default Imports;
211+
212+
function isRemote(ref: string): boolean {
213+
return (
214+
ref.startsWith('https://') ||
215+
ref.startsWith('http://') ||
216+
ref.startsWith('sys.') || // not local, but considered remote for the purposes of this component
217+
ref.startsWith('github.com')
218+
);
219+
}
220+
221+
async function getDisplayName(ref: string): Promise<string> {
222+
let displayName: string =
223+
ref
224+
.split('/')
225+
.pop()
226+
?.replace('sys.', '')
227+
.replace('.', ' ') ?? ref;
228+
229+
if (!ref.startsWith('sys.')) {
230+
const loadedTool = await load(ref);
231+
// TODO: Use entryToolId field once node-gptscript is bumped to a release containing 317e6457f056718bd9fdade18d6fbf9e0311cd46
232+
const entryToolId = (loadedTool as any)['entryToolId'];
233+
const loadedName = loadedTool.toolSet[entryToolId].name;
234+
if (loadedName) {
235+
displayName = loadedName;
236+
}
237+
}
238+
239+
return displayName.replace(/-/g, ' ');
240+
}
241+
242+
// note - I know this is a bit of a mess, but it's a quick way to get icons for tools
243+
const iconForTool = (toolName: string | undefined) => {
244+
switch (toolName) {
245+
case 'github.com/gptscript-ai/gpt4-v-vision@gateway':
246+
return <BsEye className="text-md" />;
247+
case 'github.com/gptscript-ai/dalle-image-generation@gateway':
248+
return <FaPaintBrush className="text-md" />;
249+
case 'github.com/gptscript-ai/answers-from-the-internet':
250+
return <GoGlobe className="text-md" />;
251+
case 'github.com/gptscript-ai/search-website':
252+
return <GoSearch className="text-md" />;
253+
case 'github.com/gptscript-ai/browser':
254+
return <GoBrowser className="text-md" />;
255+
case 'github.com/gptscript-ai/tools/apis/slack/write':
256+
return <AiOutlineSlack className="text-md" />;
257+
case 'github.com/gptscript-ai/tools/apis/notion/write':
258+
return <SiNotion className="text-md" />;
259+
case 'github.com/gptscript-ai/tools/apis/trello':
260+
return <FaTrello className="text-md" />;
261+
case 'github.com/gptscript-ai/tools/apis/outlook/mail/manage':
262+
return <PiMicrosoftOutlookLogoDuotone className="text-md" />;
263+
case 'github.com/gptscript-ai/tools/apis/outlook/calendar/manage':
264+
return <PiMicrosoftOutlookLogoDuotone className="text-md" />;
265+
case 'github.com/gptscript-ai/[email protected]':
266+
return <GoBook className="text-md" />;
267+
case 'github.com/gptscript-ai/structured-data-querier':
268+
return <PiMicrosoftExcelLogo className="text-md" />;
269+
case 'github.com/gptscript-ai/json-query':
270+
return <SiJson className="text-md" />;
271+
case 'github.com/gptscript-ai/context/filesystem':
272+
return <BsFiles className="text-md" />;
273+
case 'github.com/gptscript-ai/context/workspace':
274+
return <GoFileDirectory className="text-md" />;
275+
case 'github.com/gptscript-ai/tools/clis/github':
276+
return <FaGithub className="text-md" />;
277+
case 'github.com/gptscript-ai/tools/clis/aws':
278+
return <FaAws className="text-md" />;
279+
case 'github.com/gptscript-ai/tools/clis/azure':
280+
return <VscAzure className="text-md" />;
281+
case 'github.com/gptscript-ai/tools/clis/digitalocean':
282+
return <FaDigitalOcean className="text-md" />;
283+
case 'github.com/gptscript-ai/tools/clis/eksctl':
284+
return <SiAmazoneks className="text-md" />;
285+
case 'github.com/gptscript-ai/tools/clis/atlas':
286+
return <SiMongodb className="text-md" />;
287+
case 'github.com/gptscript-ai/tools/clis/gcp':
288+
return <SiGooglecloud className="text-md" />;
289+
case 'github.com/gptscript-ai/tools/clis/k8s':
290+
return <AiOutlineKubernetes className="text-md" />;
291+
case 'github.com/gptscript-ai/tools/clis/supabase':
292+
return <SiSupabase className="text-md" />;
293+
case 'sys.append':
294+
return <AiFillFileAdd className="text-md" />;
295+
case 'sys.download':
296+
return <BsDownload className="text-md" />;
297+
case 'sys.exec':
298+
return <GoTerminal className="text-md" />;
299+
case 'sys.find':
300+
return <BsFiles className="text-md" />;
301+
case 'sys.getenv':
302+
return <BsCode className="text-md" />;
303+
case 'sys.http.html2text':
304+
return <FaCode className="text-md" />;
305+
case 'sys.http.get':
306+
return <GoGlobe className="text-md" />;
307+
case 'sys.http.post':
308+
return <GoGlobe className="text-md" />;
309+
case 'sys.ls':
310+
return <BsFolder className="text-md" />;
311+
case 'sys.prompt':
312+
return <GoQuestion className="text-md" />;
313+
case 'sys.read':
314+
return <FaGlasses className="text-md" />;
315+
case 'sys.remove':
316+
return <MdDeleteForever className="text-md" />;
317+
case 'sys.stat':
318+
return <BsSearch className="text-md" />;
319+
case 'sys.time.now':
320+
return <BsClock className="text-md" />;
321+
case 'sys.write':
322+
return <GoPencil className="text-md" />;
323+
}
324+
};

0 commit comments

Comments
 (0)