Skip to content
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: 14 additions & 1 deletion actions/gptscript.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use server';

import { Tool, Block, Text, Program } from '@gptscript-ai/gptscript';
import { ToolDef, Tool, Block, Text, Program } from '@gptscript-ai/gptscript';
import { gpt } from '@/config/env';

export const rootTool = async (toolContent: string): Promise<Tool> => {
Expand Down Expand Up @@ -30,6 +30,19 @@ export const load = async (file: string): Promise<Program> => {
return (await gpt().load(file)).program;
};

export const loadTools = async (
tools: ToolDef | ToolDef[]
): Promise<Program> => {
try {
if (Array.isArray(tools)) return (await gpt().loadTools(tools)).program;
return (await gpt().loadTools([tools])).program;
} catch (e) {
console.log(`Error loading tools: ${e}`);
}

return {} as 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
22 changes: 19 additions & 3 deletions components/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use client';

import { useContext, useEffect, useState, useRef, useCallback } from 'react';
import React, {
useContext,
useEffect,
useState,
useRef,
useCallback,
} from 'react';
import Messages, { MessageType } from '@/components/chat/messages';
import ChatBar from '@/components/chat/chatBar';
import ToolForm from '@/components/chat/form';
Expand All @@ -9,6 +15,7 @@ import { Button } from '@nextui-org/react';
import { getWorkspaceDir } from '@/actions/workspace';
import { getGatewayUrl } from '@/actions/gateway';
import { ChatContext } from '@/contexts/chat';
import ScriptToolsDropdown from '@/components/scripts/tool-dropdown';
import AssistantNotFound from '@/components/assistant-not-found';
import { generateThreadName, renameThread } from '@/actions/threads';

Expand All @@ -31,6 +38,8 @@ const Chat: React.FC<ScriptProps> = ({
}) => {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, _setInputValue] = useState<string>('');
const [toolCatalogOpen, setToolCatalogOpen] = React.useState(false);

const {
script,
scriptDisplayName,
Expand Down Expand Up @@ -121,11 +130,16 @@ const Chat: React.FC<ScriptProps> = ({
/>
) : (
<div>
{showAssistantName && (
{showAssistantName && scriptDisplayName && (
<div className="sticky top-0 p-4 z-50 bg-background">
<h1 className="text-2xl font-medium truncate">
<h1 className="text-3xl font-medium truncate">
{scriptDisplayName ?? ''}
</h1>
<div className="flex gap-2">
<ScriptToolsDropdown
setToolCatalogOpen={setToolCatalogOpen}
/>
</div>
</div>
)}
<Messages restart={restartScript} messages={messages} />
Expand All @@ -150,6 +164,8 @@ const Chat: React.FC<ScriptProps> = ({
disableCommands={disableCommands}
inputPlaceholder={inputPlaceholder}
onMessageSent={handleMessageSent}
toolCatalogOpen={toolCatalogOpen}
setToolCatalogOpen={setToolCatalogOpen}
/>
)}
</div>
Expand Down
6 changes: 6 additions & 0 deletions components/chat/chatBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ interface ChatBarProps {
disableCommands?: boolean;
inputPlaceholder?: string;
onMessageSent: (message: string) => void;
toolCatalogOpen: boolean;
setToolCatalogOpen: (open: boolean) => void;
}

const ChatBar = ({
disableInput = false,
disableCommands = false,
inputPlaceholder,
onMessageSent,
toolCatalogOpen,
setToolCatalogOpen,
}: ChatBarProps) => {
const [inputValue, setInputValue] = useState('');
const [commandsOpen, setCommandsOpen] = useState(false);
Expand Down Expand Up @@ -124,6 +128,8 @@ const ChatBar = ({
setText={setInputValue}
isOpen={commandsOpen}
setIsOpen={setCommandsOpen}
toolCatalogOpen={toolCatalogOpen}
setToolCatalogOpen={setToolCatalogOpen}
>
<Textarea
color="primary"
Expand Down
7 changes: 5 additions & 2 deletions components/chat/chatBar/commands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ interface CommandsProps {
setText: (text: string) => void;
isOpen: boolean;
setIsOpen: (open: boolean) => void;
toolCatalogOpen: boolean;
setToolCatalogOpen: (open: boolean) => void;
children: React.ReactNode;
}

Expand All @@ -90,12 +92,12 @@ export default function Commands({
setText,
isOpen,
setIsOpen,
toolCatalogOpen,
setToolCatalogOpen,
children,
}: CommandsProps) {
const [filteredOptions, setFilteredOptions] =
React.useState<typeof options>(options);
const [uploadOpen, setUploadOpen] = React.useState(false);
const [toolCatalogOpen, setToolCatalogOpen] = React.useState(false);
const {
restartScript,
socket,
Expand All @@ -106,6 +108,7 @@ export default function Commands({
workspace,
selectedThreadId,
} = useContext(ChatContext);
const [uploadOpen, setUploadOpen] = React.useState(false);
const { openFilePicker, filesContent, loading, plainFiles } = useFilePicker(
{}
);
Expand Down
4 changes: 1 addition & 3 deletions components/edit/configure/imports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,7 @@ async function getDisplayName(ref: string): Promise<string> {

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;
const loadedName = loadedTool.toolSet[loadedTool.entryToolId].name;
if (loadedName) {
displayName = loadedName;
}
Expand Down
157 changes: 157 additions & 0 deletions components/scripts/tool-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {
Button,
DropdownMenu,
Dropdown,
DropdownTrigger,
DropdownSection,
DropdownItem,
} from '@nextui-org/react';
import React, { useContext, useEffect, useState } from 'react';
import { ChatContext } from '@/contexts/chat';
import { PiToolboxThin, PiXThin } from 'react-icons/pi';
import { MessageType } from '@/components/chat/messages';
import { GoTools } from 'react-icons/go';
import { load } from '@/actions/gptscript';

const ScriptToolsDropdown = () => {
const { program, tools, socket, setMessages } = useContext(ChatContext);
const [displayNames, setDisplayNames] = useState<Record<string, string>>({});
const [threadTools, setThreadTools] = useState<string[]>([]);

useEffect(() => {
const threadTools = [];
for (const tool of tools) {
if (tool !== 'github.com/gptscript-ai/knowledge@v0.4.10-gateway.7') {
threadTools.push(tool);
if (!displayNames[tool]) {
load(tool).then((loadedTool) => {
const loadedName = loadedTool.toolSet[loadedTool.entryToolId].name;
if (loadedName) {
setDisplayNames({
...displayNames,
[tool]: loadedName,
});
}
});
}
}
}

setThreadTools(threadTools);
}, [tools]);

function getDisplayName(ref: string): string {
return (
ref
.split('/')
.pop()
?.replace('sys.', '')
.replace('.', ' ')
.replace(/-/g, ' ') ?? ref.replace(/-/g, ' ')
)
.toLowerCase()
.split(' ')
.map((word: any) => {
return word.charAt(0).toUpperCase() + word.slice(1);
})
.join(' ');
}

function removeTool(tool: string) {
socket?.emit('removeTool', tool);
setMessages((prev) => [
...prev,
{
type: MessageType.Alert,
icon: <GoTools className="mt-1" />,
name: prev ? prev[prev.length - 1].name : undefined,
message: `Removed ${displayNames[tool]}`,
},
]);
}

return (
<Dropdown placement="bottom" closeOnSelect={false}>
<DropdownTrigger>
<Button variant="light" isIconOnly>
<PiToolboxThin className="size-5" />
</Button>
</DropdownTrigger>
{program && (
<DropdownMenu
onAction={(key) => {
removeTool(key as string);
}}
>
{program.toolSet && (
<DropdownSection title="Assistant's Tools">
{program.toolSet[program.entryToolId].toolMapping ? (
Object.entries(
program.toolSet[program.entryToolId].toolMapping || {}
).map(([t, v]) => (
<DropdownItem
aria-label={t}
color="primary"
key={t}
className="py-2 hover:cursor-default"
content={t}
isReadOnly
>
{program.toolSet[
(v.find((v) => v.reference === t) || {}).toolID || ''
].name || getDisplayName(t)}
</DropdownItem>
))
) : (
<DropdownItem
aria-label="No tools"
color="primary"
key="No tools"
className="py-2 hover:cursor-default"
content="No tools"
isReadOnly
>
No tools
</DropdownItem>
)}
</DropdownSection>
)}
<DropdownSection
title={
"Thread's Tools" +
(threadTools.length > 0 ? ' (Click to remove)' : '')
}
>
{threadTools && threadTools.length ? (
threadTools.map((t) => (
<DropdownItem
aria-label={t}
color="danger"
key={t}
className="py-2"
content={t}
endContent={<PiXThin />}
>
{displayNames[t] || t}
</DropdownItem>
))
) : (
<DropdownItem
aria-label="No tools"
color="danger"
key="No tools"
className="py-2"
content="No tools"
isReadOnly
>
No tools
</DropdownItem>
)}
</DropdownSection>
</DropdownMenu>
)}
</Dropdown>
);
};

export default ScriptToolsDropdown;
19 changes: 17 additions & 2 deletions contexts/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createContext, useState, useEffect, useCallback, useRef } from 'react';
import useChatSocket from '@/components/chat/useChatSocket';
import { Message } from '@/components/chat/messages';
import { Block, Tool, ToolDef } from '@gptscript-ai/gptscript';
import { Block, Tool, ToolDef, Program } from '@gptscript-ai/gptscript';
import { Socket } from 'socket.io-client';
import { getThreads, getThread, Thread, createThread } from '@/actions/threads';
import { getScript, getScriptContent } from '@/actions/me/scripts';
import { rootTool } from '@/actions/gptscript';
import { loadTools, parseContent, rootTool } from '@/actions/gptscript';
import debounce from 'lodash/debounce';
import { getWorkspaceDir } from '@/actions/workspace';

Expand Down Expand Up @@ -33,6 +33,7 @@ interface ChatContextState {
setScriptContent: React.Dispatch<React.SetStateAction<Block[] | null>>;
tool: Tool;
setTool: React.Dispatch<React.SetStateAction<Tool>>;
program: Program | null;
showForm: boolean;
setShowForm: React.Dispatch<React.SetStateAction<boolean>>;
formValues: Record<string, string>;
Expand Down Expand Up @@ -78,6 +79,7 @@ const ChatContextProvider: React.FC<ChatContextProps> = ({
const [script, setScript] = useState<string>(initialScript);
const [workspace, setWorkspace] = useState('');
const [tool, setTool] = useState<Tool>({} as Tool);
const [program, setProgram] = useState<Program>({} as Program);
const [showForm, setShowForm] = useState(true);
const [formValues, setFormValues] = useState<Record<string, string>>({});
const [scriptId, setScriptId] = useState<string | undefined>(initialScriptId);
Expand Down Expand Up @@ -115,6 +117,17 @@ const ChatContextProvider: React.FC<ChatContextProps> = ({
hasRunRef.current = hasRun;
}, [hasRun]);

useEffect(() => {
if (!scriptContent) return;
loadTools(scriptContent?.filter((block) => block.type === 'tool') || [])
.then((program) => {
setProgram(program);
})
.catch((e) => {
console.error(e);
});
}, [scriptContent]);

// need to initialize the workspace from the env variable with serves
// as the default.
useEffect(() => {
Expand Down Expand Up @@ -143,6 +156,7 @@ const ChatContextProvider: React.FC<ChatContextProps> = ({
return;
}
setScriptDisplayName(defaultScriptName);
setScriptContent(await parseContent(content));
setNotFound(false);
setTool(await rootTool(content));
setInitialFetch(true);
Expand Down Expand Up @@ -322,6 +336,7 @@ const ChatContextProvider: React.FC<ChatContextProps> = ({
setWorkspace,
tool,
setTool,
program,
subTool,
setSubTool,
showForm,
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading