diff --git a/components/chat.tsx b/components/chat.tsx index b7be99f..3e1fbb9 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -1,27 +1,26 @@ 'use client'; -import React, { - useContext, - useEffect, - useState, - useRef, - useCallback, -} from 'react'; -import Messages, { MessageType } from '@/components/chat/messages'; +import { getGatewayUrl } from '@/actions/gateway'; +import { rootTool } from '@/actions/gptscript'; +import { generateThreadName, renameThread } from '@/actions/threads'; +import { getWorkspaceDir } from '@/actions/workspace'; import ChatBar from '@/components/chat/chatBar'; import ToolForm from '@/components/chat/form'; +import Messages, { MessageType } from '@/components/chat/messages'; import Loading from '@/components/loading'; -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'; import KnowledgeDropdown from '@/components/scripts/knowledge-dropdown'; import SaveScriptDropdown from '@/components/scripts/script-save'; +import ScriptToolsDropdown from '@/components/scripts/tool-dropdown'; +import { ChatContext } from '@/contexts/chat'; import { Tool } from '@gptscript-ai/gptscript'; -import { rootTool } from '@/actions/gptscript'; +import { Button } from '@nextui-org/react'; +import React, { + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react'; interface ScriptProps { className?: string; @@ -155,11 +154,13 @@ const Chat: React.FC = ({

{scriptDisplayName ?? ''}

-
- - - -
+ {!notFound && ( +
+ + + +
+ )} )} = ({ {tool.chat ? 'Start chat' : 'Run script'} ) : ( - + <> + {!notFound && ( + + )} + )} - ) : notFound ? ( - ) : ( Loading your assistant... )} diff --git a/components/threads.tsx b/components/threads.tsx index 20ff08c..6753a4f 100644 --- a/components/threads.tsx +++ b/components/threads.tsx @@ -1,10 +1,9 @@ -import { useState, useContext } from 'react'; -import New from './threads/new'; -import Menu from './threads/menu'; -import { Button, Divider, Tooltip } from '@nextui-org/react'; -import { GoSidebarExpand, GoSidebarCollapse } from 'react-icons/go'; import { ChatContext } from '@/contexts/chat'; -import { getScript } from '@/actions/me/scripts'; +import { Button, Divider, Tooltip } from '@nextui-org/react'; +import { useContext, useState } from 'react'; +import { GoSidebarCollapse, GoSidebarExpand } from 'react-icons/go'; +import Menu from './threads/menu'; +import New from './threads/new'; interface ThreadsProps { className?: string; @@ -12,28 +11,10 @@ interface ThreadsProps { } const Threads: React.FC = ({ onOpenExplore }: ThreadsProps) => { - const { - setScript, - setScriptContent, - thread, - setThread, - threads, - setScriptId, - setShouldRestart, - } = useContext(ChatContext); + const { thread, threads, switchToThread } = useContext(ChatContext); const [isCollapsed, setIsCollapsed] = useState(false); - const handleRun = async (script: string, id: string, scriptId: string) => { - if (id !== thread) { - setScriptContent((await getScript(scriptId))?.script || []); - setScript(script); - setThread(id); - setScriptId(scriptId); - setShouldRestart(true); - } - }; - const isSelected = (id: string) => id === thread; return ( @@ -90,7 +71,7 @@ const Threads: React.FC = ({ onOpenExplore }: ThreadsProps) => { key={key} className={`border-1 border-gray-300 px-4 rounded-xl transition duration-150 ease-in-out ${isSelected(thread.meta.id) ? 'bg-primary border-primary dark:border-primary-50 dark:bg-primary-50 text-white' : 'hover:bg-gray-100 dark:hover:bg-zinc-700 cursor-pointer dark:bg-zinc-800 dark:border-zinc-800'} `} onClick={() => - handleRun( + switchToThread( thread.meta.script, thread.meta.id, thread.meta.scriptId || '' diff --git a/contexts/chat.tsx b/contexts/chat.tsx index 0884231..d68ce05 100644 --- a/contexts/chat.tsx +++ b/contexts/chat.tsx @@ -70,9 +70,15 @@ interface ChatContextState { interrupt: () => void; fetchThreads: () => void; restartScript: () => void; + switchToThread: ( + script: string, + id: string, + scriptId: string + ) => Promise; } const defaultScriptName = `Tildy`; +const notFoundScriptName = `[Assistant Not Found]`; const ChatContext = createContext({} as ChatContextState); const ChatContextProvider: React.FC = ({ @@ -121,6 +127,21 @@ const ChatContextProvider: React.FC = ({ const threadInitialized = useRef(false); const [shouldRestart, setShouldRestart] = useState(false); + const switchToThread = async ( + script: string, + id: string, + scriptId: string + ) => { + if (id !== thread) { + setScript(script); + setThread(id); + setScriptContent((await getScript(scriptId))?.script || []); + setScriptId(scriptId); + // use `setForceRun` instead of `setShouldRestart` because it triggers the `run` WS event which will reset the messages as well + setForceRun(true); + } + }; + useEffect(() => { if (!thread || scriptContent.length === 0) return; @@ -146,28 +167,32 @@ const ChatContextProvider: React.FC = ({ getScript(scriptId).then(async (script) => { if (script === undefined) { setNotFound(true); - return; + setScriptContent([]); + setScriptDisplayName(notFoundScriptName); + } else { + setNotFound(false); + setScriptContent(script.script as Block[]); + setScriptDisplayName(script.displayName || ''); } - setNotFound(false); - setScriptContent(script.script as Block[]); - setScriptDisplayName(script.displayName || ''); setInitialFetch(true); }); } else if (script) { getScriptContent(script).then((content) => { if (content === undefined) { setNotFound(true); - return; + setScriptContent([]); + setScriptDisplayName(notFoundScriptName); + } else { + parseContent(content).then((parsedContent) => { + setScriptContent(parsedContent); + }); + setNotFound(false); + setScriptDisplayName(defaultScriptName); } - setScriptDisplayName(defaultScriptName); - parseContent(content).then((parsedContent) => { - setScriptContent(parsedContent); - }); - setNotFound(false); setInitialFetch(true); }); } - }, [script, scriptId, thread]); + }, [script, scriptId, setScriptContent, thread]); useEffect(() => { if (!enableThread || thread || threadInitialized.current) { @@ -215,12 +240,13 @@ const ChatContextProvider: React.FC = ({ useEffect(() => { if (thread && shouldRestart) { - getThread(thread).then((thread) => { + getThread(thread).then(async (thread) => { if (thread) { setInitialFetch(false); setWorkspace(thread.meta.workspace); } - restartScript(); + // need to wait for the WS Server to restart before triggering another event + await restartScript(); setShouldRestart(false); }); } @@ -301,25 +327,31 @@ const ChatContextProvider: React.FC = ({ setInitialFetch(false); if (scriptId) { - getScript(scriptId).then(async (script) => { - if (script === undefined) { - setNotFound(true); - return; - } - setNotFound(false); - setScriptContent(script.script as Block[]); - setInitialFetch(true); - }); + const foundScript = await getScript(scriptId); + + if (!foundScript) { + setNotFound(true); + setScriptContent([]); + setScriptDisplayName(notFoundScriptName); + return; + } + + setNotFound(false); + setScriptContent(foundScript.script as Block[]); + setInitialFetch(true); } else { - getScriptContent(script).then(async (content) => { - if (content === undefined) { - setNotFound(true); - return; - } - setScriptContent(await parseContent(content)); - setNotFound(false); - setInitialFetch(true); - }); + const content = await getScriptContent(script); + + if (!content) { + setNotFound(true); + setScriptContent([]); + setScriptDisplayName(notFoundScriptName); + return; + } + + setScriptContent(await parseContent(content)); + setNotFound(false); + setInitialFetch(true); } restart(); }, 200), @@ -385,6 +417,7 @@ const ChatContextProvider: React.FC = ({ tools, setTools, handleCreateThread, + switchToThread, }} > {children}