Skip to content

Commit 6e2f8ed

Browse files
committed
feat: add ability to dynamically add tools and add dep management
Signed-off-by: tylerslaton <[email protected]>
1 parent 515cae5 commit 6e2f8ed

File tree

10 files changed

+467
-133
lines changed

10 files changed

+467
-133
lines changed

components/edit/configure.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const Configure: React.FC<ConfigureProps> = ({className, collapsed}) => {
3333
setVisibility,
3434
dynamicInstructions,
3535
setDynamicInstructions,
36+
dependencies,
37+
setDependencies,
3638
} = useContext(EditContext);
3739

3840
const abbreviate = (name: string) => {
@@ -108,7 +110,12 @@ const Configure: React.FC<ConfigureProps> = ({className, collapsed}) => {
108110
Augment your instructions with code that can pull information from local or remote systems.
109111
</p>
110112
</div>
111-
<Code label="Code" code={dynamicInstructions} onChange={setDynamicInstructions} />
113+
<Code
114+
code={dynamicInstructions}
115+
onChange={setDynamicInstructions}
116+
dependencies={dependencies.find((d) => d.forTool === 'dynamic-instructions')?.content || ''}
117+
onDependenciesChange={(code, type) => setDependencies([...dependencies.filter((d) => d.forTool !== 'dynamic-instructions'), {forTool: 'dynamic-instructions', content: code, type: type}])}
118+
/>
112119
{/* <div className="my-4"/>
113120
<Code label="Dependencies" code={'// package.json'} onChange={(code) => {}} /> */}
114121
</AccordionItem>

components/edit/configure/code.tsx

Lines changed: 200 additions & 84 deletions
Large diffs are not rendered by default.

components/edit/configure/customTool.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ const CustomTool: React.FC<ConfigureProps> = ({className, tool}) => {
4545
setTools,
4646
tools,
4747
scriptPath,
48-
models
48+
models,
49+
dependencies,
50+
setDependencies,
4951
} = useContext(EditContext)
5052

5153
useEffect(() => {
@@ -89,6 +91,15 @@ const CustomTool: React.FC<ConfigureProps> = ({className, tool}) => {
8991
return prevRoot;
9092
});
9193

94+
setDependencies((prevDeps) => {
95+
return prevDeps.map((dep) => {
96+
if (dep.forTool === customTool.name) {
97+
dep.forTool = name;
98+
}
99+
return dep;
100+
});
101+
});
102+
92103
setTools((prevTools) => {
93104
let updatedTools = prevTools.map((t: Tool) => {
94105
// replace all instances of the old name with the new name
@@ -98,6 +109,7 @@ const CustomTool: React.FC<ConfigureProps> = ({className, tool}) => {
98109
});
99110
return updatedTools;
100111
});
112+
101113
}, [name])
102114

103115
const setCustomToolTools = useCallback((newTools: string[]) => {
@@ -271,8 +283,12 @@ const CustomTool: React.FC<ConfigureProps> = ({className, tool}) => {
271283
}
272284
{instructionsType === "code" &&
273285
<>
274-
<Code code={customTool.instructions || ''} onChange={(e) => setCustomTool({...customTool, instructions: e})}
275-
/>
286+
<Code
287+
code={customTool.instructions || ''}
288+
onChange={(e) => setCustomTool({...customTool, instructions: e})}
289+
dependencies={dependencies.find((d) => d.forTool === customTool.name)?.content || ''}
290+
onDependenciesChange={(code, type) => setDependencies([...dependencies.filter((d) => d.forTool !== customTool.name), {forTool: customTool.name ||'', content: code, type: type}])}
291+
/>
276292
</>
277293
}
278294
</div>

components/script/chatBar/commands.tsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Card, Listbox, ListboxItem, Menu } from "@nextui-org/react";
22
import React, { useEffect, useContext } from "react";
3-
import { GoInbox, GoIssueReopened } from "react-icons/go";
3+
import { GoInbox, GoIssueReopened, GoTools } from "react-icons/go";
44
import { PiToolbox } from "react-icons/pi";
55
import { ScriptContext } from "@/contexts/script";
66
import Upload from "@/components/script/chatBar/upload";
77
import ToolCatalog from "@/components/script/chatBar/toolCatalog";
8+
import { MessageType } from "@/components/script/messages";
89

910
/*
1011
note(tylerslaton):
@@ -56,7 +57,7 @@ export default function Commands({text, setText, isOpen, setIsOpen, children }:
5657
const [filteredOptions, setFilteredOptions] = React.useState<typeof options>(options);
5758
const [uploadOpen, setUploadOpen] = React.useState(false);
5859
const [toolCatalogOpen, setToolCatalogOpen] = React.useState(false);
59-
const {restartScript} = useContext(ScriptContext);
60+
const {restartScript, socket, setMessages, tools} = useContext(ScriptContext);
6061

6162
useEffect(() => {
6263
if (!text.startsWith("/")) {
@@ -90,7 +91,37 @@ export default function Commands({text, setText, isOpen, setIsOpen, children }:
9091
return (
9192
<div className="relative w-full command-options">
9293
<Upload isOpen={uploadOpen} setIsOpen={setUploadOpen}/>
93-
<ToolCatalog tools={[]} addTool={() => {}} removeTool={() => {}} isOpen={toolCatalogOpen} setIsOpen={setToolCatalogOpen}/>
94+
<ToolCatalog
95+
tools={tools}
96+
addTool={(tool) => {
97+
socket?.emit("addTool", tool);
98+
setMessages((prev) => [
99+
...prev,
100+
{
101+
type: MessageType.Alert,
102+
icon: <GoTools className="mt-1"/>,
103+
message: `Added ${
104+
tool.split("/").pop()?.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
105+
}`
106+
}
107+
]);
108+
}}
109+
removeTool={(tool) => {
110+
socket?.emit("removeTool", tool);
111+
setMessages((prev) => [
112+
...prev,
113+
{
114+
type: MessageType.Alert,
115+
icon: <GoTools className="mt-1"/>,
116+
message: `Removed ${
117+
tool.split("/").pop()?.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
118+
}`
119+
}
120+
]);
121+
}}
122+
isOpen={toolCatalogOpen}
123+
setIsOpen={setToolCatalogOpen}
124+
/>
94125
{isOpen && !!filteredOptions.length && (
95126
<Card className="absolute bottom-14 w-full p-4">
96127
<Listbox aria-label="commands">

components/script/messages.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export enum MessageType {
1616

1717
export type Message = {
1818
type: MessageType;
19+
icon?: ReactNode;
1920
message?: string;
2021
error?: string;
2122
name?: string;
@@ -123,10 +124,16 @@ const Message = ({message, noAvatar, restart}: { message: Message, noAvatar?: bo
123124
<div className="flex flex-col items-start mb-10">
124125
<div className="flex gap-2 w-full">
125126
<div
126-
className="w-full flex justify-center space-x-2 rounded-2xl text-black text-sm bg-gray-50 shadow text-center py-2 px-4 dark:text-white dark:border-zinc-800 dark:border dark:bg-black"
127+
className="w-full flex justify-center space-x-2 rounded-2xl text-black text-sm bg-zinc-50 shadow text-center py-2 px-4 dark:text-white dark:border-zinc-800 dark:border dark:bg-black"
127128
>
128-
<div className="w-2 h-2 my-auto bg-green-500 rounded-full"></div>
129-
<p>{message.message}</p>
129+
{message.icon ? message.icon : <div className="w-2 h-2 my-auto bg-green-500 rounded-full"/>}
130+
<Markdown
131+
className={`!text-wrap prose overflow-x-auto dark:prose-invert prose-thead:text-left prose-p:text-sm prose-img:rounded-xl prose-img:shadow-lg`}
132+
remarkPlugins={[remarkGfm]}
133+
rehypePlugins={[[rehypeExternalLinks, {target: "_blank"}]]}
134+
>
135+
{message.message}
136+
</Markdown>
130137
</div>
131138
</div>
132139
</div>

components/script/useChatSocket.tsx

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const useChatSocket = (isEmpty?: boolean) => {
1313
const [generating, setGenerating] = useState(false);
1414
const [running, setRunning] = useState(false);
1515
const [error, setError] = useState<string | null>(null);
16+
const [tools, setTools] = useState<string[]>([]);
1617

1718
// Refs
1819
const socketRef = useRef<Socket | null>(null);
@@ -173,6 +174,18 @@ const useChatSocket = (isEmpty?: boolean) => {
173174
}
174175
}, []);
175176

177+
const handleAddingTool = () => setRunning(false);
178+
const handleToolAdded = (tools: string[]) => {
179+
setTools(tools);
180+
setRunning(true);
181+
}
182+
183+
const handleRemovingTool = () => setRunning(false);
184+
const handleToolRemoved = (tools: string[]) => {
185+
setTools(tools);
186+
setRunning(true);
187+
}
188+
176189
const parseToolCall = (toolCall: string): { tool: string, params: string } => {
177190
const [tool, params] = toolCall.replace('<tool call> ', '').split(' -> ');
178191
return {tool, params};
@@ -243,16 +256,17 @@ const useChatSocket = (isEmpty?: boolean) => {
243256
socket.on("running", () => setRunning(true));
244257
socket.on("progress", (data: { frame: CallFrame, state: any }) => handleProgress(data));
245258
socket.on("error", (data: string) => handleError(data));
246-
socket.on("promptRequest", (data: { frame: PromptFrame, state: any }) => {
247-
handlePromptRequest(data)
259+
socket.on("promptRequest", (data: { frame: PromptFrame, state: any }) => handlePromptRequest(data));
260+
socket.on("confirmRequest", (data: { frame: CallFrame, state: any }) => handleConfirmRequest(data));
261+
socket.on("toolAdded", handleToolAdded);
262+
socket.on("addingTool", handleAddingTool);
263+
socket.on("toolRemoved", handleToolRemoved);
264+
socket.on("removingTool", handleRemovingTool);
265+
socket.on("loaded", (data: {messages: Message[], tools: string[]},) => {
266+
setMessages(data.messages);
267+
setTools(data.tools);
248268
});
249-
socket.on("confirmRequest", (data: { frame: CallFrame, state: any }) => {
250-
handleConfirmRequest(data)
251-
});
252-
socket.on("loaded", (data: Message[]) => {
253-
setMessages(data)
254-
});
255-
269+
256270
setSocket(socket);
257271
}
258272

@@ -294,14 +308,19 @@ const useChatSocket = (isEmpty?: boolean) => {
294308

295309
return {
296310
error,
297-
socket, setSocket,
298-
connected, setConnected,
299-
messages, setMessages,
311+
socket,
312+
setSocket,
313+
connected,
314+
setConnected,
315+
messages,
316+
setMessages,
300317
restart,
301318
interrupt,
302319
generating,
303320
running,
304321
setRunning,
322+
tools,
323+
setTools,
305324
};
306325
};
307326

components/threads.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const Threads: React.FC<ThreadsProps> = ({className}) => {
6262
>
6363
<div className="flex justify-between items-center">
6464
<h2 className="text-sm truncate">{thread.meta.name}</h2>
65-
<Menu thread={thread.meta.id}/>
65+
<Menu thread={thread.meta.id} className={isSelected(thread.meta.id) ? 'text-white' : ''}/>
6666
</div>
6767
</div>
6868
</Tooltip>

contexts/edit.tsx

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ const DEBOUNCE_TIME = 1000; // milliseconds
88
const DYNAMIC_INSTRUCTIONS = "dynamic-instructions";
99

1010
export type ToolType = "tool" | "context" | "agent";
11+
export type DependencyBlock = {
12+
content: string;
13+
forTool: string;
14+
type: string;
15+
}
1116

1217
interface EditContextProps {
1318
scriptPath: string,
@@ -18,6 +23,7 @@ interface EditContextState {
1823
loading: boolean;
1924
setLoading: (loading: boolean) => void;
2025
root: Tool;
26+
dependencies: DependencyBlock[]; setDependencies: React.Dispatch<React.SetStateAction<DependencyBlock[]>>;
2127
models: string[], setModels: React.Dispatch<React.SetStateAction<string[]>>;
2228
setRoot: React.Dispatch<React.SetStateAction<Tool>>;
2329
tools: Tool[];
@@ -52,12 +58,16 @@ const EditContextProvider: React.FC<EditContextProps> = ({scriptPath, children})
5258
const [visibility, setVisibility] = useState<'public' | 'private' | 'protected'>('private');
5359
const [models, setModels] = useState<string[]>([]);
5460
const debounceTimer = useRef<NodeJS.Timeout | null>(null);
55-
61+
5662
// Dynamic instructions are simply a text tool with the name "dynamic-instructions" that is
5763
// imported as a context in the root tool. This field is used to store the instructions for
5864
// that tool.
5965
const [dynamicInstructions, setDynamicInstructions] = useState<string>('');
6066

67+
// Dependencies are special text tools that reference a tool, type, and content. They are used
68+
// to store requirements.txt and package.json files for the script.
69+
const [dependencies, setDependencies] = useState<DependencyBlock[]>([]);
70+
6171
useEffect(() => {
6272
getModels().then((m) => {
6373
setModels(m)
@@ -85,7 +95,25 @@ const EditContextProvider: React.FC<EditContextProps> = ({scriptPath, children})
8595
useEffect(() => {
8696
if (loading) return;
8797
update();
88-
}, [root, tools, visibility])
98+
}, [root, tools, dependencies, visibility])
99+
100+
useEffect(() => {
101+
setTools((prevTools) => {
102+
return [
103+
...prevTools.filter((t) => t.name !== DYNAMIC_INSTRUCTIONS),
104+
{name: DYNAMIC_INSTRUCTIONS, type: 'tool', instructions: dynamicInstructions}
105+
] as Tool[];
106+
});
107+
108+
setRoot((prevRoot) => {
109+
if (prevRoot.context?.includes(DYNAMIC_INSTRUCTIONS)) return prevRoot;
110+
return {...prevRoot, context: [...(prevRoot.context || []), DYNAMIC_INSTRUCTIONS]};
111+
});
112+
}, [dynamicInstructions]);
113+
114+
useEffect(() => {
115+
116+
}, [dependencies])
89117

90118
// The first tool in the script is not always the root tool, so we find it
91119
// by finding the first non-text tool in the script.
@@ -119,11 +147,11 @@ const EditContextProvider: React.FC<EditContextProps> = ({scriptPath, children})
119147
debounceTimer.current = setTimeout(async () => {
120148
await updateScript({
121149
visibility: visibility,
122-
content: await stringify([root, ...tools]),
150+
content: await stringify([root, ...tools, ...dependenciesToText(dependencies)]),
123151
id: scriptId,
124152
}).catch((error) => console.error(error));
125153
}, DEBOUNCE_TIME);
126-
}, [scriptId, root, tools, visibility]);
154+
}, [scriptId, root, tools, dependencies, visibility]);
127155

128156
const newestToolName = useCallback(() => {
129157
let num = 1
@@ -171,25 +199,26 @@ const EditContextProvider: React.FC<EditContextProps> = ({scriptPath, children})
171199
});
172200
}
173201

174-
useEffect(() => {
175-
setTools((prevTools) => {
176-
return [
177-
...prevTools.filter((t) => t.name !== DYNAMIC_INSTRUCTIONS),
178-
{name: DYNAMIC_INSTRUCTIONS, type: 'tool', instructions: dynamicInstructions}
179-
] as Tool[];
180-
});
181-
182-
setRoot((prevRoot) => {
183-
if (prevRoot.context?.includes(DYNAMIC_INSTRUCTIONS)) return prevRoot;
184-
return {...prevRoot, context: [...(prevRoot.context || []), DYNAMIC_INSTRUCTIONS]};
185-
});
186-
}, [dynamicInstructions])
202+
const dependenciesToText = (dependencies: DependencyBlock[]): Text[] => {
203+
return dependencies
204+
.filter((dep) => dep.content.trim() !== '') // Filter out empty dependencies
205+
.map((dep) => {
206+
return {
207+
id: `${dep.forTool}-${dep.type}`,
208+
format: `metadata:${dep.forTool}:${dep.type}`,
209+
type: 'text',
210+
content: dep.content,
211+
name: dep.forTool,
212+
}
213+
});
214+
}
187215

188216
// Provide the context value to the children components
189217
return (
190218
<EditContext.Provider
191219
value={{
192220
scriptPath,
221+
dependencies, setDependencies,
193222
dynamicInstructions, setDynamicInstructions,
194223
models, setModels,
195224
loading, setLoading,

contexts/script.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ interface ScriptContextState {
2222
scriptId?: string;
2323
scriptContent: ToolDef[] | null;
2424
workspace: string;
25+
tools: string[];
26+
setTools: React.Dispatch<React.SetStateAction<string[]>>;
2527
setWorkspace: React.Dispatch<React.SetStateAction<string>>;
2628
subTool: string;
2729
setSubTool: React.Dispatch<React.SetStateAction<string>>;
@@ -79,7 +81,7 @@ const ScriptContextProvider: React.FC<ScriptContextProps> = ({children, initialS
7981
const [initialFetch, setInitialFetch] = useState(false);
8082
const [subTool, setSubTool] = useState(initialSubTool || '');
8183
const {
82-
socket, connected, running, messages, setMessages, restart, interrupt, generating, error, setRunning
84+
socket, connected, running, messages, setMessages, restart, interrupt, generating, error, setRunning, tools, setTools
8385
} = useChatSocket(isEmpty);
8486

8587
// need to initialize the workspace from the env variable with serves
@@ -187,6 +189,8 @@ const ScriptContextProvider: React.FC<ScriptContextProps> = ({children, initialS
187189
interrupt,
188190
fetchThreads,
189191
restartScript,
192+
tools,
193+
setTools,
190194
}}
191195
>
192196
{children}

0 commit comments

Comments
 (0)