Skip to content

Commit 439fe40

Browse files
authored
Merge pull request #288 from thedadams/save-assistant
feat: add ability to save an assistant
2 parents a92430c + f32ca03 commit 439fe40

File tree

7 files changed

+106
-28
lines changed

7 files changed

+106
-28
lines changed

components/chat.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import ScriptToolsDropdown from '@/components/scripts/tool-dropdown';
1919
import AssistantNotFound from '@/components/assistant-not-found';
2020
import { generateThreadName, renameThread } from '@/actions/threads';
2121
import KnowledgeDropdown from '@/components/scripts/knowledge-dropdown';
22+
import SaveScriptDropdown from '@/components/scripts/script-save';
2223

2324
interface ScriptProps {
2425
className?: string;
@@ -44,7 +45,7 @@ const Chat: React.FC<ScriptProps> = ({
4445
const {
4546
script,
4647
scriptDisplayName,
47-
tool,
48+
rootTool,
4849
showForm,
4950
setShowForm,
5051
formValues,
@@ -80,7 +81,7 @@ const Chat: React.FC<ScriptProps> = ({
8081
socket?.emit(
8182
'run',
8283
`${await getGatewayUrl()}/${script}`,
83-
tool.name,
84+
rootTool.name,
8485
formValues,
8586
workspace,
8687
thread
@@ -125,7 +126,7 @@ const Chat: React.FC<ScriptProps> = ({
125126
>
126127
{showForm && hasParams ? (
127128
<ToolForm
128-
tool={tool}
129+
tool={rootTool}
129130
formValues={formValues}
130131
handleInputChange={handleInputChange}
131132
/>
@@ -139,6 +140,7 @@ const Chat: React.FC<ScriptProps> = ({
139140
<div className="flex gap-2">
140141
<ScriptToolsDropdown />
141142
<KnowledgeDropdown />
143+
<SaveScriptDropdown />
142144
</div>
143145
</div>
144146
)}
@@ -152,11 +154,11 @@ const Chat: React.FC<ScriptProps> = ({
152154
<Button
153155
className="mt-4 w-full"
154156
type="submit"
155-
color={tool.chat ? 'primary' : 'secondary'}
157+
color={rootTool.chat ? 'primary' : 'secondary'}
156158
onPress={handleFormSubmit}
157159
size="lg"
158160
>
159-
{tool.chat ? 'Start chat' : 'Run script'}
161+
{rootTool.chat ? 'Start chat' : 'Run script'}
160162
</Button>
161163
) : (
162164
<ChatBar

components/chat/chatBar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const ChatBar = ({
3434
generating,
3535
interrupt,
3636
hasParams,
37-
tool,
37+
rootTool,
3838
setShowForm,
3939
messages,
4040
setMessages,
@@ -89,7 +89,7 @@ const ChatBar = ({
8989
if (generating) setLocked(false);
9090
}, [generating]);
9191

92-
if (!tool.chat) {
92+
if (!rootTool.chat) {
9393
if (hasParams)
9494
return (
9595
<Button

components/scripts/knowledge-dropdown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const ScriptKnowledgeDropdown = () => {
4545
const newKnowledgeFiles = knowledgeFiles.filter((f) => f !== file);
4646
setKnowledgeFiles(newKnowledgeFiles);
4747
if (newKnowledgeFiles.length === 0) {
48-
socket?.emit('removeTool', gatewayTool());
48+
socket?.emit('removeTool', gatewayTool(), true);
4949
}
5050
}
5151
);

components/scripts/script-save.tsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {
2+
Button,
3+
DropdownMenu,
4+
Dropdown,
5+
DropdownTrigger,
6+
DropdownItem,
7+
} from '@nextui-org/react';
8+
import React, { useContext } from 'react';
9+
import { ChatContext } from '@/contexts/chat';
10+
import { PiFloppyDiskThin } from 'react-icons/pi';
11+
import { getScript, Script, updateScript } from '@/actions/me/scripts';
12+
import { stringify } from '@/actions/gptscript';
13+
import { gatewayTool } from '@/actions/knowledge/util';
14+
15+
const SaveScriptDropdown = () => {
16+
const { scriptId, rootTool, setRootTool, tools, socket, setScriptContent } =
17+
useContext(ChatContext);
18+
19+
const knowledgeTool = gatewayTool();
20+
21+
async function saveScript() {
22+
if (!rootTool.tools || rootTool.tools.length === 0) {
23+
rootTool.tools = [];
24+
}
25+
26+
// The knowledge tool is dynamic and not controlled by the user. Don't add it to the saved tool.
27+
const addedTools = tools.filter((t) => t !== knowledgeTool);
28+
29+
rootTool.tools = rootTool.tools
30+
.filter((t) => !addedTools.includes(t))
31+
.concat(...addedTools);
32+
try {
33+
await updateScript({
34+
content: await stringify([rootTool]),
35+
id: parseInt(scriptId!),
36+
} as Script);
37+
38+
socket?.emit('removeTool', addedTools, false);
39+
setRootTool(rootTool);
40+
41+
setScriptContent((await getScript(scriptId!))?.script || null);
42+
} catch (e) {
43+
console.error(e);
44+
}
45+
}
46+
47+
return (
48+
<Dropdown placement="bottom">
49+
<DropdownTrigger>
50+
<Button variant="light" isIconOnly>
51+
<PiFloppyDiskThin className="size-5" />
52+
</Button>
53+
</DropdownTrigger>
54+
<DropdownMenu>
55+
{scriptId ? (
56+
<DropdownItem key="save" onPress={saveScript}>
57+
Save Assistant
58+
</DropdownItem>
59+
) : (
60+
(null as any)
61+
)}
62+
</DropdownMenu>
63+
</Dropdown>
64+
);
65+
};
66+
67+
export default SaveScriptDropdown;

components/scripts/tool-dropdown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const ScriptToolsDropdown = () => {
6161
}
6262

6363
function removeTool(tool: string) {
64-
socket?.emit('removeTool', tool);
64+
socket?.emit('removeTool', tool, true);
6565
setMessages((prev) => [
6666
...prev,
6767
{

contexts/chat.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ interface ChatContextState {
3131
setSubTool: React.Dispatch<React.SetStateAction<string>>;
3232
setScript: React.Dispatch<React.SetStateAction<string>>;
3333
setScriptContent: React.Dispatch<React.SetStateAction<Block[] | null>>;
34-
tool: Tool;
35-
setTool: React.Dispatch<React.SetStateAction<Tool>>;
34+
rootTool: Tool;
35+
setRootTool: React.Dispatch<React.SetStateAction<Tool>>;
3636
program: Program | null;
3737
showForm: boolean;
3838
setShowForm: React.Dispatch<React.SetStateAction<boolean>>;
@@ -346,8 +346,8 @@ const ChatContextProvider: React.FC<ChatContextProps> = ({
346346
setScriptContent,
347347
workspace,
348348
setWorkspace,
349-
tool,
350-
setTool,
349+
rootTool: tool,
350+
setRootTool: setTool,
351351
program,
352352
subTool,
353353
setSubTool,

server/app.mjs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -313,31 +313,40 @@ const mount = async (
313313
socket.emit('toolAdded', state.tools);
314314
});
315315

316-
socket.on('removeTool', async (tool) => {
316+
socket.on('removeTool', async (tool, alterChatState) => {
317317
if (runningScript) {
318318
await runningScript.close();
319319
runningScript = null;
320320
}
321321

322-
const stateTools = (state.tools || []).filter((t) => t !== tool);
323-
// find the root tool and then remove the tool
324-
for (let block of script) {
325-
if (block.type === 'tool') {
326-
if (!block.tools) break;
327-
block.tools = [...new Set(block.tools.filter((t) => t !== tool))];
328-
break;
322+
const stateTools = (state.tools || []).filter((t) => {
323+
if (Array.isArray(tool)) {
324+
return !tool.includes(t);
329325
}
330-
}
326+
return t !== tool;
327+
});
331328

332329
socket.emit('removingTool');
333330

334-
const loaded = await gptscript.loadTools(script, true);
335-
const newTools = toChatStateTools(loaded?.program?.toolSet);
336-
const currentState = JSON.parse(state.chatState);
337-
currentState.continuation.state.completion.tools = newTools;
331+
if (alterChatState) {
332+
// find the root tool and then remove the tool
333+
for (let block of script) {
334+
if (block.type === 'tool') {
335+
if (!block.tools) break;
336+
block.tools = [...new Set(block.tools.filter((t) => t !== tool))];
337+
break;
338+
}
339+
}
340+
341+
const loaded = await gptscript.loadTools(script, true);
342+
const newTools = toChatStateTools(loaded?.program?.toolSet);
343+
const currentState = JSON.parse(state.chatState);
344+
currentState.continuation.state.completion.tools = newTools;
345+
346+
opts.chatState = JSON.stringify(currentState);
347+
state.chatState = JSON.stringify(currentState);
348+
}
338349

339-
opts.chatState = JSON.stringify(currentState);
340-
state.chatState = JSON.stringify(currentState);
341350
state.tools = stateTools;
342351

343352
if (threadID) {

0 commit comments

Comments
 (0)