From 417dc0f4bd7b9ce71b123fbccdbe216698ae64b9 Mon Sep 17 00:00:00 2001 From: Cesar Date: Fri, 22 Dec 2023 14:25:23 +0100 Subject: [PATCH 01/14] chore: add title to app in browser tab --- apps/browser/app/layout.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/browser/app/layout.tsx b/apps/browser/app/layout.tsx index 5832b675..455aaaee 100644 --- a/apps/browser/app/layout.tsx +++ b/apps/browser/app/layout.tsx @@ -9,8 +9,13 @@ import "react-toastify/dist/ReactToastify.css"; import SidebarLayout from "@/components/SidebarLayout"; import { Providers } from "@/components/providers/Providers"; import { cookies } from "next/headers"; +import { Metadata } from "next"; config.autoAddCss = false; +export const metadata: Metadata = { + title: "evo.ninja" +} + export default function EvoApp({ children }: { children: React.ReactNode }) { const currentDevice = cookies().get("X-User-Device"); return ( From ae192d482ce3fc4471ba3c5439ef6f35099d36fa Mon Sep 17 00:00:00 2001 From: Colin Spence Date: Fri, 22 Dec 2023 13:34:12 -0700 Subject: [PATCH 02/14] adjusts prose and modal styles --- .../browser/components/modals/ChatDetails.tsx | 24 +++++++++++++++---- apps/browser/components/modals/FileModal.tsx | 2 +- apps/browser/components/modals/ModalBase.tsx | 6 ++--- apps/browser/styles/globals.css | 11 +++++---- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/apps/browser/components/modals/ChatDetails.tsx b/apps/browser/components/modals/ChatDetails.tsx index 6b92d649..f008f791 100644 --- a/apps/browser/components/modals/ChatDetails.tsx +++ b/apps/browser/components/modals/ChatDetails.tsx @@ -1,7 +1,7 @@ import { useRef, useState } from "react"; import Modal from "./ModalBase"; import ReactMarkdown from "react-markdown"; -import { CaretUp } from "@phosphor-icons/react"; +import { CaretUp, CheckCircle } from "@phosphor-icons/react"; import { MessageSet } from "@/lib/utils/sanitizeLogsDetails"; import clsx from "clsx"; @@ -57,15 +57,29 @@ export default function ChatDetails({ "prose-condensed prose prose-zinc prose-invert rounded-md bg-zinc-800 shadow-md transition-colors duration-0 ease-in-out hover:shadow-lg", { "cursor-pointer duration-150 hover:bg-zinc-700": - expandedStep !== stepTitle, + expandedStep !== stepTitle && stepDetails.length > 0, } )} > +
{ + contentRefs.current[index] = el; + }} className={clsx( - "transform text-white transition-transform duration-500 ease-in-out group-hover:text-cyan-500", - expandedStep !== stepTitle && "rotate-180" + "step h-0 overflow-hidden transition-[height] duration-500 ease-in-out" )} - /> - )} - -
{ - contentRefs.current[index] = el; - }} - className={clsx( - "step h-0 overflow-hidden transition-[height] duration-500 ease-in-out" - )} - > - {stepDetails.map((detail, detailIndex) => ( -
- {detail} + > + {stepDetails.map((detail, detailIndex) => ( +
+ {detail} +
+ ))}
- ))} +
+ {status && + Object.keys(logs.details).length - 1 === index && + !isGoal && ( +
+ +
{status}
+
+ )}
- - ) + ); + } )} diff --git a/apps/browser/components/modals/ModalBase.tsx b/apps/browser/components/modals/ModalBase.tsx index 773cca8c..06a130b8 100644 --- a/apps/browser/components/modals/ModalBase.tsx +++ b/apps/browser/components/modals/ModalBase.tsx @@ -1,4 +1,10 @@ -import { PropsWithChildren } from "react"; +import { + PropsWithChildren, + useCallback, + useEffect, + useRef, + useState, +} from "react"; import { Dialog, Transition } from "@headlessui/react"; import { Fragment } from "react"; import { Ubuntu_FONT } from "@/lib/fonts"; @@ -10,6 +16,7 @@ interface ModalProps { title: string; isOpen: boolean; onClose: () => void; + autoScroll?: boolean; panelStyles?: { maxWidth?: string; other?: string; @@ -24,8 +31,18 @@ interface ModalProps { } export default function Modal(props: PropsWithChildren) { - const { title, isOpen, onClose, panelStyles, contentStyles, children } = - props; + const { + title, + isOpen, + onClose, + autoScroll = false, + panelStyles, + contentStyles, + children, + } = props; + const modalContainerRef = useRef(null); + const [isAtBottom, setIsAtBottom] = useState(true); + const maxWidth = panelStyles?.maxWidth ?? "max-w-[540px]"; const defaultContentStyles = clsx( @@ -39,6 +56,38 @@ export default function Modal(props: PropsWithChildren) { contentStyles?.other ); + const scrollToBottom = () => { + modalContainerRef.current?.scrollTo({ + top: modalContainerRef.current.scrollHeight, + behavior: "smooth", + }); + }; + + const handleScroll = useCallback(() => { + // Detect if the user is at the bottom of the modal + const container = modalContainerRef.current; + if (container) { + const isScrolledToBottom = + container.scrollHeight - container.scrollTop <= container.clientHeight; + setIsAtBottom(isScrolledToBottom); + } + }, []); + + useEffect(() => { + // If the user is at the bottom, scroll to the bottom + if (isAtBottom && autoScroll) { + scrollToBottom(); + } + }, [children, isAtBottom, autoScroll]); + + useEffect(() => { + const container = modalContainerRef.current; + if (container) { + // Add scroll event listener + container.addEventListener("scroll", handleScroll); + } + }); + return ( <> @@ -84,7 +133,13 @@ export default function Modal(props: PropsWithChildren) { /> -
{children}
+
autoScroll && handleScroll()} + ref={modalContainerRef} + > + {children} +
From 99a8279e9fc151f432ef90a58c70bb4ca1bad0e1 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Thu, 28 Dec 2023 14:49:41 +0100 Subject: [PATCH 06/14] supabase client is now a singleton --- apps/browser/app/page.tsx | 5 +- .../components/providers/Providers.tsx | 17 ++-- apps/browser/lib/hooks/useEvoService.ts | 16 ++-- apps/browser/lib/mutations/useAddChatLog.ts | 12 +-- apps/browser/lib/mutations/useAddMessages.ts | 12 +-- apps/browser/lib/mutations/useAddVariable.ts | 12 +-- apps/browser/lib/mutations/useCreateChat.ts | 12 +-- apps/browser/lib/mutations/useDeleteChat.ts | 12 +-- .../lib/mutations/useUpdateChatTitle.ts | 12 +-- apps/browser/lib/queries/useChats.ts | 83 ++++++++++++------- apps/browser/lib/services/evo/EvoService.ts | 6 +- apps/browser/lib/services/evo/EvoThread.ts | 55 ++++++------ .../lib/supabase/SupabaseClientProvider.tsx | 35 ++++++++ .../browser/lib/supabase/useSupabaseClient.ts | 9 -- .../lib/supabase/useSupabaseClient.tsx | 4 + 15 files changed, 187 insertions(+), 115 deletions(-) create mode 100644 apps/browser/lib/supabase/SupabaseClientProvider.tsx delete mode 100644 apps/browser/lib/supabase/useSupabaseClient.ts create mode 100644 apps/browser/lib/supabase/useSupabaseClient.tsx diff --git a/apps/browser/app/page.tsx b/apps/browser/app/page.tsx index a49659d1..17a70fa2 100644 --- a/apps/browser/app/page.tsx +++ b/apps/browser/app/page.tsx @@ -26,8 +26,10 @@ import { useRouter } from "next/navigation"; import { useAtom } from "jotai"; import { v4 as uuid } from "uuid"; import { InMemoryFile } from "@nerfzael/memory-fs"; +import { useSupabaseClient } from "@/lib/supabase/useSupabaseClient"; function Dojo({ params }: { params: { id?: string } }) { + const supabase = useSupabaseClient(); const [evoService, setEvoService] = useAtom(evoServiceAtom); const [newGoalSubmitted, setNewGoalSubmitted] = useAtom(newGoalSubmittedAtom); const [isChatLoading, setIsChatLoading] = useAtom(isChatLoadingAtom); @@ -43,13 +45,14 @@ function Dojo({ params }: { params: { id?: string } }) { const { data: chats, isLoading: isChatsLoading } = useChats(); const router = useRouter(); const { status: sessionStatus, data: sessionData } = useSession(); - const isAuthenticated = sessionStatus === "authenticated"; + const isAuthenticated = sessionStatus === "authenticated" && !!supabase; const { mutateAsync: createChat } = useCreateChat(); const { mutateAsync: updateChatTitle } = useUpdateChatTitle(); const { logs, isConnected, isStarting, isRunning, handleStart, status } = useEvoService( chatId, isAuthenticated, + supabase ); const workspaceUploadUpdate = useWorkspaceUploadUpdate(); diff --git a/apps/browser/components/providers/Providers.tsx b/apps/browser/components/providers/Providers.tsx index d960e474..6518870f 100644 --- a/apps/browser/components/providers/Providers.tsx +++ b/apps/browser/components/providers/Providers.tsx @@ -4,17 +4,20 @@ import { Provider as JotaiProvider } from "jotai"; import ReactQueryProvider from "./ReactQueryProvider"; import { SessionProvider } from "next-auth/react"; import ToastProvider from "./ToastProvider"; +import SupabaseClientProvider from "@/lib/supabase/SupabaseClientProvider"; export function Providers({ children }: { children: React.ReactNode }) { return ( - - - - {children} - - - + + + + + {children} + + + + ); } diff --git a/apps/browser/lib/hooks/useEvoService.ts b/apps/browser/lib/hooks/useEvoService.ts index caf0f593..1fcce57b 100644 --- a/apps/browser/lib/hooks/useEvoService.ts +++ b/apps/browser/lib/hooks/useEvoService.ts @@ -11,9 +11,8 @@ import { EvoThreadCallbacks, EvoThreadConfig } from "@/lib/services/evo/EvoThrea import { useAddChatLog } from "@/lib/mutations/useAddChatLog"; import { useAddMessages } from "@/lib/mutations/useAddMessages"; import { useAddVariable } from "@/lib/mutations/useAddVariable"; -import { useChats } from "@/lib/queries/useChats"; +import { fetchChats, useChats } from "@/lib/queries/useChats"; import { SupabaseWorkspace } from "@/lib/supabase/SupabaseWorkspace"; -import { useSupabaseClient } from "@/lib/supabase/useSupabaseClient"; import { useWorkspaceFilesUpdate } from "@/lib/hooks/useWorkspaceFilesUpdate"; import { useWorkspaceUploadUpdate } from "@/lib/hooks/useWorkspaceUploadUpdate"; import { ChatLog } from "@/components/Chat"; @@ -21,10 +20,12 @@ import { Workspace, InMemoryWorkspace } from "@evo-ninja/agent-utils"; import { ChatLogType, ChatMessage } from "@evo-ninja/agents"; import { useState, useEffect } from "react"; import { useAtom } from "jotai"; +import { EvoSupabaseClient } from "../supabase/EvoSupabaseClient"; export const useEvoService = ( chatId: string | "" | undefined, isAuthenticated: boolean, + supabase: EvoSupabaseClient | undefined ): { logs: ChatLog[]; isConnected: boolean; @@ -33,8 +34,6 @@ export const useEvoService = ( status: string | undefined; handleStart: (goal: string) => Promise; } => { - const supabase = useSupabaseClient(); - // Globals const [evoService] = useAtom(evoServiceAtom); const [allowTelemetry] = useAtom(allowTelemetryAtom); @@ -56,9 +55,6 @@ export const useEvoService = ( const { mutateAsync: addMessages } = useAddMessages(); const { mutateAsync: addVariable } = useAddVariable(); - // Queries - const { refetch: fetchChats } = useChats(); - // Helpers const workspaceFilesUpdate = useWorkspaceFilesUpdate(); const workspaceUploadUpdate = useWorkspaceUploadUpdate(); @@ -122,7 +118,7 @@ export const useEvoService = ( return []; } - const { data: chats, error } = await fetchChats(); + const { data: chats, error } = await fetchChats(supabase!); if (error) { console.error(error); @@ -140,8 +136,10 @@ export const useEvoService = ( }; async function loadWorkspace(chatId: string): Promise { + // isAuthenticated is only true if there's a supabase instance + // so we can safely assume that it's not undefined const workspace = isAuthenticated ? - new SupabaseWorkspace(chatId, supabase.storage) : + new SupabaseWorkspace(chatId, supabase!.storage) : new InMemoryWorkspace(); await workspaceUploadUpdate(workspace); diff --git a/apps/browser/lib/mutations/useAddChatLog.ts b/apps/browser/lib/mutations/useAddChatLog.ts index 66d2d381..715737de 100644 --- a/apps/browser/lib/mutations/useAddChatLog.ts +++ b/apps/browser/lib/mutations/useAddChatLog.ts @@ -1,8 +1,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" -import { useSession } from "next-auth/react" import { ChatLog } from "@/components/Chat" import { Row } from "../supabase/types" -import { createSupabaseClient } from "../supabase/createSupabaseClient" +import { useSupabaseClient } from "../supabase/useSupabaseClient" const mapChatLogToLogDTO = ( chatId: string, @@ -17,15 +16,18 @@ const mapChatLogToLogDTO = ( } export const useAddChatLog = () => { - const { data: session } = useSession() - const queryClient = useQueryClient() + const queryClient = useQueryClient(); + const supabase = useSupabaseClient(); return useMutation({ mutationFn: async (args: { chatId: string; log: ChatLog; }) => { - const supabase = createSupabaseClient(session?.supabaseAccessToken as string) + if (!supabase) { + throw new Error("Not authenticated"); + } + const { error } = await supabase .from("logs") .insert( diff --git a/apps/browser/lib/mutations/useAddMessages.ts b/apps/browser/lib/mutations/useAddMessages.ts index 35ecfa49..344f1c38 100644 --- a/apps/browser/lib/mutations/useAddMessages.ts +++ b/apps/browser/lib/mutations/useAddMessages.ts @@ -1,8 +1,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" -import { useSession } from "next-auth/react" import { ChatLogType, ChatMessage } from "@evo-ninja/agents" import { Row } from "../supabase/types" -import { createSupabaseClient } from "../supabase/createSupabaseClient" +import { useSupabaseClient } from "../supabase/useSupabaseClient" const mapChatMessageToMessageDTO = ( chatId: string, @@ -64,8 +63,8 @@ const mapChatMessageToMessageDTO = ( } export const useAddMessages = () => { - const { data: session } = useSession() - const queryClient = useQueryClient() + const queryClient = useQueryClient(); + const supabase = useSupabaseClient(); return useMutation({ mutationFn: async (args: { @@ -73,7 +72,10 @@ export const useAddMessages = () => { messages: ChatMessage[]; type: ChatLogType; }) => { - const supabase = createSupabaseClient(session?.supabaseAccessToken as string) + if (!supabase) { + throw new Error("Not authenticated"); + } + const { error } = await supabase .from("messages") .insert( diff --git a/apps/browser/lib/mutations/useAddVariable.ts b/apps/browser/lib/mutations/useAddVariable.ts index ab8cfc8e..cce4be64 100644 --- a/apps/browser/lib/mutations/useAddVariable.ts +++ b/apps/browser/lib/mutations/useAddVariable.ts @@ -1,7 +1,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" -import { useSession } from "next-auth/react" import { Row } from "../supabase/types" -import { createSupabaseClient } from "../supabase/createSupabaseClient" +import { useSupabaseClient } from "../supabase/useSupabaseClient" const mapVariableToVariableDTO = ( chatId: string, @@ -16,8 +15,8 @@ const mapVariableToVariableDTO = ( } export const useAddVariable = () => { - const { data: session } = useSession() - const queryClient = useQueryClient() + const queryClient = useQueryClient(); + const supabase = useSupabaseClient(); return useMutation({ mutationFn: async (args: { @@ -25,7 +24,10 @@ export const useAddVariable = () => { key: string; value: string; }) => { - const supabase = createSupabaseClient(session?.supabaseAccessToken as string) + if (!supabase) { + throw new Error("Not authenticated"); + } + const { error } = await supabase .from("variables") .insert( diff --git a/apps/browser/lib/mutations/useCreateChat.ts b/apps/browser/lib/mutations/useCreateChat.ts index baf06d70..72c6e4f0 100644 --- a/apps/browser/lib/mutations/useCreateChat.ts +++ b/apps/browser/lib/mutations/useCreateChat.ts @@ -1,15 +1,17 @@ -import { createSupabaseClient } from "../supabase/createSupabaseClient" import { Chat } from "@/lib/queries/useChats" import { useMutation, useQueryClient } from "@tanstack/react-query" -import { useSession } from "next-auth/react" +import { useSupabaseClient } from "../supabase/useSupabaseClient"; export const useCreateChat = () => { - const { data: session } = useSession() - const queryClient = useQueryClient() + const queryClient = useQueryClient(); + const supabase = useSupabaseClient(); return useMutation({ mutationFn: async (chatId: string) => { - const supabase = createSupabaseClient(session?.supabaseAccessToken as string) + if (!supabase) { + throw new Error("Not authenticated"); + } + const { data, error } = await supabase .from("chats") .insert({ diff --git a/apps/browser/lib/mutations/useDeleteChat.ts b/apps/browser/lib/mutations/useDeleteChat.ts index 25a6463b..2068231c 100644 --- a/apps/browser/lib/mutations/useDeleteChat.ts +++ b/apps/browser/lib/mutations/useDeleteChat.ts @@ -1,14 +1,16 @@ -import { createSupabaseClient } from "../supabase/createSupabaseClient" import { useMutation, useQueryClient } from "@tanstack/react-query" -import { useSession } from "next-auth/react" +import { useSupabaseClient } from "../supabase/useSupabaseClient"; export const useDeleteChat = () => { - const { data: session } = useSession() - const queryClient = useQueryClient() + const queryClient = useQueryClient(); + const supabase = useSupabaseClient(); return useMutation({ mutationFn: async (chatId: string) => { - const supabase = createSupabaseClient(session?.supabaseAccessToken as string) + if (!supabase) { + throw new Error("Not authenticated"); + } + const { error } = await supabase .from("chats") .delete() diff --git a/apps/browser/lib/mutations/useUpdateChatTitle.ts b/apps/browser/lib/mutations/useUpdateChatTitle.ts index 2e7b35b6..a0459985 100644 --- a/apps/browser/lib/mutations/useUpdateChatTitle.ts +++ b/apps/browser/lib/mutations/useUpdateChatTitle.ts @@ -1,16 +1,16 @@ -import { createSupabaseClient } from "../supabase/createSupabaseClient"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { useSession } from "next-auth/react"; +import { useSupabaseClient } from "../supabase/useSupabaseClient"; export const useUpdateChatTitle = () => { - const { data: session } = useSession(); const queryClient = useQueryClient(); + const supabase = useSupabaseClient(); return useMutation({ mutationFn: async (args: { chatId: string; title: string }) => { - const supabase = createSupabaseClient( - session?.supabaseAccessToken as string - ); + if (!supabase) { + throw new Error("Not authenticated"); + } + const { error } = await supabase .from("chats") .update({ title: args.title }) diff --git a/apps/browser/lib/queries/useChats.ts b/apps/browser/lib/queries/useChats.ts index 84922c65..9bf97c46 100644 --- a/apps/browser/lib/queries/useChats.ts +++ b/apps/browser/lib/queries/useChats.ts @@ -3,7 +3,7 @@ import { useSession } from "next-auth/react" import { ChatMessage } from "@evo-ninja/agents" import { ChatLog } from "@/components/Chat" import { Json } from "../supabase/dbTypes" -import { createSupabaseClient } from "../supabase/createSupabaseClient" +import { useSupabaseClient } from "../supabase/useSupabaseClient" export interface Chat { id: string; @@ -110,45 +110,68 @@ const mapChatDTOtoChat = (dto: ChatDTO): Chat => { } } +export const fetchChats = async (supabase: any): Promise<{ + data: Chat[] | undefined, + error: Error | undefined +}> => { + const { data, error } = await supabase + .from('chats') + .select(` + id, + created_at, + logs(id, created_at, title, content, user), + variables(id, key, value), + title, + messages( + id, + created_at, + content, + name, + function_call, + tool_calls, + temporary, + role, + tool_call_id + ) + `).order( + 'created_at', + { ascending: false } + ) + + if (error) { + return { + data: undefined, + error: error + } + } + + return { + data: data.map(mapChatDTOtoChat), + error: undefined + }; +} + export const useChats = () => { - const { data: session } = useSession() + const { data: session } = useSession(); + const supabase = useSupabaseClient(); return useQuery({ - queryKey: ['chats'], - enabled: !!session?.user?.email, + queryKey: ['chats', session?.user?.email, supabase], + enabled: !!session?.user?.email && !!supabase, refetchOnMount: false, queryFn: async () => { - const supabase = createSupabaseClient(session?.supabaseAccessToken as string) - const { data, error } = await supabase - .from('chats') - .select(` - id, - created_at, - logs(id, created_at, title, content, user), - variables(id, key, value), - title, - messages( - id, - created_at, - content, - name, - function_call, - tool_calls, - temporary, - role, - tool_call_id - ) - `).order( - 'created_at', - { ascending: false } - ) + if (!session?.user?.email || !supabase) { + throw new Error("Not authenticated") + } + + const { data, error } = await fetchChats(supabase); if (error) { console.error(error) - throw new Error(error.message) + throw new Error(error.message); } - return data.map(mapChatDTOtoChat) + return data; } }) } \ No newline at end of file diff --git a/apps/browser/lib/services/evo/EvoService.ts b/apps/browser/lib/services/evo/EvoService.ts index 94bcd4a9..65e09fe2 100644 --- a/apps/browser/lib/services/evo/EvoService.ts +++ b/apps/browser/lib/services/evo/EvoService.ts @@ -31,7 +31,7 @@ export class EvoService { } async connect(config: EvoThreadConfig, callbacks: EvoThreadCallbacks): Promise { - this._current = this.acquireThread(config); + this._current = await this.acquireThread(config); await this._current.connect(callbacks); } @@ -42,9 +42,9 @@ export class EvoService { return this._current.start(options); } - private acquireThread(config: EvoThreadConfig): EvoThread { + private async acquireThread(config: EvoThreadConfig): Promise { if (!this._threads[config.chatId]) { - this._threads[config.chatId] = new EvoThread(config); + this._threads[config.chatId] = await EvoThread.load(config); } return this._threads[config.chatId]; } diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 9aaf1ad3..14cd9fe8 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -58,17 +58,45 @@ export class EvoThread { private _state: EvoThreadState; private _callbacks?: EvoThreadCallbacks; - constructor( + protected constructor( private _config: EvoThreadConfig ) { this._state = Object.assign({}, INIT_STATE); - this.load(); } get chatId(): string { return this._config.chatId; } + public static async load( + config: EvoThreadConfig + ): Promise { + const thread = new EvoThread(config); + + const chatId = thread._config.chatId; + thread._state.isLoading = true; + + const results = await Promise.all<[ + Promise, + Promise + ]>([ + thread._config.loadChatLog(chatId).catch((reason) => { + thread._callbacks?.onError(reason.toString()); + return []; + }), + thread._config.loadWorkspace(chatId).catch((reason) => { + thread._callbacks?.onError(reason.toString()); + return new InMemoryWorkspace(); + }) + ]); + + thread._state.logs = results[0]; + thread._state.workspace = results[1]; + thread._state.isLoading = false; + + return thread; + } + destroy() { // Destroy all child objects & processes } @@ -167,29 +195,6 @@ export class EvoThread { this._state.goal = undefined; } - private async load() { - const chatId = this._config.chatId; - this._state.isLoading = true; - - const results = await Promise.all<[ - Promise, - Promise - ]>([ - this._config.loadChatLog(chatId).catch((reason) => { - this._callbacks?.onError(reason.toString()); - return []; - }), - this._config.loadWorkspace(chatId).catch((reason) => { - this._callbacks?.onError(reason.toString()); - return new InMemoryWorkspace(); - }) - ]); - - this._state.logs = results[0]; - this._state.workspace = results[1]; - this._state.isLoading = false; - } - private async waitForLoad() { while (this._state.isLoading) { await new Promise((resolve) => diff --git a/apps/browser/lib/supabase/SupabaseClientProvider.tsx b/apps/browser/lib/supabase/SupabaseClientProvider.tsx new file mode 100644 index 00000000..5a3536cb --- /dev/null +++ b/apps/browser/lib/supabase/SupabaseClientProvider.tsx @@ -0,0 +1,35 @@ +import { EvoSupabaseClient } from "./EvoSupabaseClient"; +import { createSupabaseClient } from "./createSupabaseClient"; + +import { useSession } from "next-auth/react"; +import { createContext, useEffect, useRef, useState } from "react"; +import React from "react"; + +export const SupabaseClientContext = createContext(undefined); + +const SupabaseClientProvider = ({ children }: { children: React.ReactNode}) => { + const { data: session } = useSession(); + const [supabaseClient, setSupabaseClient] = useState(undefined); + const [lastAccessToken, setLastAccessToken] = useState(undefined); + + useEffect(() => { + if (!session?.supabaseAccessToken) { + setSupabaseClient(undefined); + setLastAccessToken(undefined) + return; + } + + if (lastAccessToken === session.supabaseAccessToken) { + return; + } + setSupabaseClient(createSupabaseClient(session.supabaseAccessToken)); + setLastAccessToken(session.supabaseAccessToken); + }, [session?.supabaseAccessToken]); + + return ( + + {children} + + ); +}; +export default SupabaseClientProvider diff --git a/apps/browser/lib/supabase/useSupabaseClient.ts b/apps/browser/lib/supabase/useSupabaseClient.ts deleted file mode 100644 index 730d36eb..00000000 --- a/apps/browser/lib/supabase/useSupabaseClient.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EvoSupabaseClient } from "./EvoSupabaseClient"; -import { createSupabaseClient } from "./createSupabaseClient"; - -import { useSession } from "next-auth/react"; - -export const useSupabaseClient = (): EvoSupabaseClient => { - const { data: session } = useSession(); - return createSupabaseClient(session?.supabaseAccessToken as string); -}; diff --git a/apps/browser/lib/supabase/useSupabaseClient.tsx b/apps/browser/lib/supabase/useSupabaseClient.tsx new file mode 100644 index 00000000..b48046a6 --- /dev/null +++ b/apps/browser/lib/supabase/useSupabaseClient.tsx @@ -0,0 +1,4 @@ +import React from "react"; +import { SupabaseClientContext } from "./SupabaseClientProvider"; + +export const useSupabaseClient = () => React.useContext(SupabaseClientContext); From c026b8ee414d61416a15868bbfa553e37146e2a0 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Thu, 28 Dec 2023 15:08:25 +0100 Subject: [PATCH 07/14] reusing evo for different goals --- apps/browser/lib/services/evo/EvoThread.ts | 63 +++++++++++++------ .../lib/services/evo/createEvoInstance.ts | 23 ++++--- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 9aaf1ad3..6f3a868c 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -1,5 +1,5 @@ import { createEvoInstance } from "@/lib/services/evo/createEvoInstance"; -import { GoalApi } from "@/lib/api"; +import { GoalApi, ProxyEmbeddingApi, ProxyLlmApi } from "@/lib/api"; import { ChatLog } from "@/components/Chat"; import { Evo, @@ -7,6 +7,8 @@ import { ChatMessage, Workspace, InMemoryWorkspace, + EmbeddingApi, + LlmApi, } from "@evo-ninja/agents"; export interface EvoThreadConfig { @@ -23,6 +25,9 @@ export interface EvoThreadConfig { export interface EvoThreadState { goal: string | undefined; + evo: Evo | undefined; + llm: LlmApi | undefined; + embedding: EmbeddingApi | undefined; status: string | undefined; isRunning: boolean; isLoading: boolean; @@ -47,6 +52,9 @@ export interface EvoThreadStartOptions { const INIT_STATE: EvoThreadState = { goal: undefined, + evo: undefined, + llm: undefined, + embedding: undefined, status: undefined, isRunning: false, isLoading: false, @@ -141,29 +149,46 @@ export class EvoThread { return; } - // Create an Evo instance - const evo = createEvoInstance( - goalId, - this._state.workspace, - options.openAiApiKey, - this._config.onMessagesAdded, - this._config.onVariableSet, - (chatLog) => this.onChatLog(chatLog), - (status) => this.onStatusUpdate(status), - () => this._callbacks?.onGoalCapReached(), - // onError - (error) => this._callbacks?.onError(error) - ); + if (this._state.evo && this._state.llm && this._state.embedding) { + console.log("Reusing existing Evo instance"); + } else { + console.log("Creating new Evo instance"); + // Create an Evo instance + const result = createEvoInstance( + this._state.workspace, + options.openAiApiKey, + this._config.onMessagesAdded, + this._config.onVariableSet, + (chatLog) => this.onChatLog(chatLog), + (status) => this.onStatusUpdate(status), + () => this._callbacks?.onGoalCapReached(), + // onError + (error) => this._callbacks?.onError(error) + ); - if (!evo) { - this.setIsRunning(false); - return; + if (!result) { + this.setIsRunning(false); + return; + } + + console.log("Evo instance created", result); + + this._state.evo = result.evo; + this._state.llm = result.llm; + this._state.embedding = result.embedding; } - await evo.init(); + if (this._state.llm instanceof ProxyLlmApi) { + console.log("Setting goal ID1", goalId); + this._state.llm.setGoalId(goalId); + } + if (this._state.embedding instanceof ProxyEmbeddingApi) { + console.log("Setting goal ID2", goalId); + this._state.embedding.setGoalId(goalId); + } // Run the evo instance against the goal - await this.runEvo(evo, options.goal); + await this.runEvo(this._state.evo, options.goal); this._state.goal = undefined; } diff --git a/apps/browser/lib/services/evo/createEvoInstance.ts b/apps/browser/lib/services/evo/createEvoInstance.ts index 8c648a87..35cf2246 100644 --- a/apps/browser/lib/services/evo/createEvoInstance.ts +++ b/apps/browser/lib/services/evo/createEvoInstance.ts @@ -24,7 +24,6 @@ import { import cl100k_base from "gpt-tokenizer/esm/encoding/cl100k_base"; export function createEvoInstance( - goalId: string, workspace: Workspace, openAiApiKey: string | undefined, onMessagesAdded: (type: ChatLogType, messages: ChatMessage[]) => Promise, @@ -33,7 +32,14 @@ export function createEvoInstance( onStatusUpdate: (status: string) => void, onGoalCapReached: () => void, onError: (error: string) => void -): Evo | undefined { +): { + evo: Evo, + llm: LlmApi, + embedding: EmbeddingApi +} | undefined { + let llm: LlmApi; + let embedding: EmbeddingApi; + try { const browserLogger = new BrowserLogger({ onLog: async (message: string) => { @@ -66,10 +72,8 @@ export function createEvoInstance( MAX_RESPONSE_TOKENS: "4096", }); - let llm: LlmApi; - let embedding: EmbeddingApi; - if (openAiApiKey) { + console.log("Using OpenAI API"); llm = new OpenAILlmApi( env.OPENAI_API_KEY, env.GPT_MODEL as LlmModel, @@ -85,19 +89,18 @@ export function createEvoInstance( env.OPENAI_API_BASE_URL, ); } else { + console.log("Using Evo API"); const llmProxy = new ProxyLlmApi( env.GPT_MODEL as LlmModel, env.CONTEXT_WINDOW_TOKENS, env.MAX_RESPONSE_TOKENS, onGoalCapReached, ); - llmProxy.setGoalId(goalId); llm = llmProxy; const embeddingProxy = new ProxyEmbeddingApi( cl100k_base, onGoalCapReached ); - embeddingProxy.setGoalId(goalId); embedding = embeddingProxy; } @@ -124,7 +127,11 @@ export function createEvoInstance( agentVariables ) ); - return evo; + return { + evo, + llm, + embedding + }; } catch (e: any) { onError(e.message); return undefined; From 25bbf6110c96d03392f401aed9a3e361ed538bca Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 29 Dec 2023 17:36:16 +0100 Subject: [PATCH 08/14] implemented support for multiple goals in browser --- apps/browser/lib/hooks/useEvoService.ts | 15 +++--- apps/browser/lib/queries/useChats.ts | 49 ++++++++++++------- apps/browser/lib/services/evo/EvoThread.ts | 42 ++++++++++------ .../lib/services/evo/createEvoInstance.ts | 2 - .../agents/src/agent-core/llm/chat/Chat.ts | 14 ++++-- 5 files changed, 76 insertions(+), 46 deletions(-) diff --git a/apps/browser/lib/hooks/useEvoService.ts b/apps/browser/lib/hooks/useEvoService.ts index 1fcce57b..875fb8c4 100644 --- a/apps/browser/lib/hooks/useEvoService.ts +++ b/apps/browser/lib/hooks/useEvoService.ts @@ -11,7 +11,7 @@ import { EvoThreadCallbacks, EvoThreadConfig } from "@/lib/services/evo/EvoThrea import { useAddChatLog } from "@/lib/mutations/useAddChatLog"; import { useAddMessages } from "@/lib/mutations/useAddMessages"; import { useAddVariable } from "@/lib/mutations/useAddVariable"; -import { fetchChats, useChats } from "@/lib/queries/useChats"; +import { Chat, fetchChats, useChats } from "@/lib/queries/useChats"; import { SupabaseWorkspace } from "@/lib/supabase/SupabaseWorkspace"; import { useWorkspaceFilesUpdate } from "@/lib/hooks/useWorkspaceFilesUpdate"; import { useWorkspaceUploadUpdate } from "@/lib/hooks/useWorkspaceUploadUpdate"; @@ -89,7 +89,7 @@ export const useEvoService = ( const config: EvoThreadConfig = { chatId, - loadChatLog, + loadChat, loadWorkspace, onChatLogAdded: handleChatLogAdded, onMessagesAdded: handleMessagesAdded, @@ -113,9 +113,9 @@ export const useEvoService = ( setIsConnected(true); }; - const loadChatLog = async (chatId: string) => { + const loadChat = async (chatId: string): Promise => { if (chatId === "") { - return []; + throw new Error("Cannot load chat for anonymous user."); } const { data: chats, error } = await fetchChats(supabase!); @@ -123,16 +123,15 @@ export const useEvoService = ( if (error) { console.error(error); setError("Failed to fetch user chats."); - return []; + throw error; } const currentChat = chats?.find(c => c.id === chatId); if (!currentChat) { - return []; + throw new Error(`Chat with id ${chatId} not found.`); } - - return currentChat.logs; + return currentChat; }; async function loadWorkspace(chatId: string): Promise { diff --git a/apps/browser/lib/queries/useChats.ts b/apps/browser/lib/queries/useChats.ts index 9bf97c46..2b4494f5 100644 --- a/apps/browser/lib/queries/useChats.ts +++ b/apps/browser/lib/queries/useChats.ts @@ -9,11 +9,16 @@ export interface Chat { id: string; created_at: string; title: string | null; - messages: ChatMessage[]; + messages: SavedMessage[]; logs: ChatLog[]; variables: Map } +export interface SavedMessage { + msg: ChatMessage, + temporary: boolean +} + interface MessageDTO { id: string; created_at: string; @@ -49,42 +54,52 @@ interface ChatDTO { messages: MessageDTO[]; } -const mapMessageDTOtoMessage = (dto: MessageDTO): ChatMessage & { temporary: boolean } => { +const mapMessageDTOtoMessage = (dto: MessageDTO): SavedMessage => { const messageRole = dto.role as "function" | "user" | "tool" | "system" | "assistant" switch (messageRole) { case "user": case "system": { return { - role: messageRole, - content: dto.content, - temporary: dto.temporary + msg: { + role: messageRole, + content: dto.content, + }, + temporary: dto.temporary, } } case "function": { return { - role: messageRole, - content: dto.content, + msg: { + role: messageRole, + content: dto.content, + name: dto.name as string + }, temporary: dto.temporary, - name: dto.name as string } } case "assistant": { return { - role: messageRole, - content: dto.content, + msg: { + role: messageRole, + content: dto.content, + // TODO: Json casting + function_call: dto.function_call as any ?? undefined, + tool_calls: dto.tool_calls as any + ? dto.tool_calls as any + : undefined, + }, temporary: dto.temporary, - // TODO: Json casting - function_call: dto.function_call as any, - tool_calls: dto.tool_calls as any, } } case "tool": { return { - role: messageRole, - content: dto.content, + msg: { + role: messageRole, + content: dto.content, + tool_call_id: dto.tool_call_id as string, + }, temporary: dto.temporary, - tool_call_id: dto.tool_call_id as string, } } } @@ -104,7 +119,7 @@ const mapChatDTOtoChat = (dto: ChatDTO): Chat => { id: dto.id, created_at: dto.created_at, title: dto.title, - messages, + messages: messages, variables, logs } diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 30dabd13..14cf552e 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -10,10 +10,11 @@ import { EmbeddingApi, LlmApi, } from "@evo-ninja/agents"; +import { Chat } from "@/lib/queries/useChats"; export interface EvoThreadConfig { chatId: string; - loadChatLog: (chatId: string) => Promise; + loadChat: (chatId: string) => Promise; loadWorkspace: (chatId: string) => Promise; onChatLogAdded: (chatLog: ChatLog) => Promise; onMessagesAdded: ( @@ -32,6 +33,7 @@ export interface EvoThreadState { isRunning: boolean; isLoading: boolean; logs: ChatLog[]; + chat: Chat | undefined; workspace: Workspace; } @@ -59,6 +61,7 @@ const INIT_STATE: EvoThreadState = { isRunning: false, isLoading: false, logs: [], + chat: undefined, workspace: new InMemoryWorkspace() }; @@ -85,12 +88,12 @@ export class EvoThread { thread._state.isLoading = true; const results = await Promise.all<[ - Promise, + Promise, Promise ]>([ - thread._config.loadChatLog(chatId).catch((reason) => { + thread._config.loadChat(chatId).catch((reason) => { thread._callbacks?.onError(reason.toString()); - return []; + throw reason; }), thread._config.loadWorkspace(chatId).catch((reason) => { thread._callbacks?.onError(reason.toString()); @@ -98,7 +101,8 @@ export class EvoThread { }) ]); - thread._state.logs = results[0]; + thread._state.chat = results[0]; + thread._state.logs = results[0].logs; thread._state.workspace = results[1]; thread._state.isLoading = false; @@ -177,11 +181,7 @@ export class EvoThread { return; } - if (this._state.evo && this._state.llm && this._state.embedding) { - console.log("Reusing existing Evo instance"); - } else { - console.log("Creating new Evo instance"); - // Create an Evo instance + if (!this._state.evo || !this._state.llm || !this._state.embedding) { const result = createEvoInstance( this._state.workspace, options.openAiApiKey, @@ -190,7 +190,6 @@ export class EvoThread { (chatLog) => this.onChatLog(chatLog), (status) => this.onStatusUpdate(status), () => this._callbacks?.onGoalCapReached(), - // onError (error) => this._callbacks?.onError(error) ); @@ -199,19 +198,32 @@ export class EvoThread { return; } - console.log("Evo instance created", result); - this._state.evo = result.evo; this._state.llm = result.llm; this._state.embedding = result.embedding; + + if (this._state.chat?.messages.length) { + await this._state.evo.context.chat.addWithoutEvents( + "persistent", + this._state.chat.messages + .filter(x => !x.temporary) + .map(x => x.msg) + ); + await this._state.evo.context.chat.addWithoutEvents( + "temporary", + this._state.chat.messages + .filter(x => x.temporary) + .map(x => x.msg) + ); + } else { + await this._state.evo.init(); + } } if (this._state.llm instanceof ProxyLlmApi) { - console.log("Setting goal ID1", goalId); this._state.llm.setGoalId(goalId); } if (this._state.embedding instanceof ProxyEmbeddingApi) { - console.log("Setting goal ID2", goalId); this._state.embedding.setGoalId(goalId); } diff --git a/apps/browser/lib/services/evo/createEvoInstance.ts b/apps/browser/lib/services/evo/createEvoInstance.ts index 35cf2246..633384ec 100644 --- a/apps/browser/lib/services/evo/createEvoInstance.ts +++ b/apps/browser/lib/services/evo/createEvoInstance.ts @@ -73,7 +73,6 @@ export function createEvoInstance( }); if (openAiApiKey) { - console.log("Using OpenAI API"); llm = new OpenAILlmApi( env.OPENAI_API_KEY, env.GPT_MODEL as LlmModel, @@ -89,7 +88,6 @@ export function createEvoInstance( env.OPENAI_API_BASE_URL, ); } else { - console.log("Using Evo API"); const llmProxy = new ProxyLlmApi( env.GPT_MODEL as LlmModel, env.CONTEXT_WINDOW_TOKENS, diff --git a/packages/agents/src/agent-core/llm/chat/Chat.ts b/packages/agents/src/agent-core/llm/chat/Chat.ts index 16c793ac..dc619bee 100644 --- a/packages/agents/src/agent-core/llm/chat/Chat.ts +++ b/packages/agents/src/agent-core/llm/chat/Chat.ts @@ -34,6 +34,16 @@ export class Chat { public async add(type: ChatLogType, msg: ChatMessage | ChatMessage[]): Promise { const msgs = Array.isArray(msg) ? msg : [msg]; + + this.addWithoutEvents(type, msgs); + + if (this.options?.onMessagesAdded) { + await this.options.onMessagesAdded(type, msgs); + } + } + + public addWithoutEvents(type: ChatLogType, msg: ChatMessage | ChatMessage[]) { + const msgs = Array.isArray(msg) ? msg : [msg]; const msgsWithTokens = msgs.map((msg) => { const tokens = this._tokenizer.encode(JSON.stringify(msg)).length; @@ -42,10 +52,6 @@ export class Chat { const tokens = msgsWithTokens.map(({ tokens }) => tokens); this._chatLogs.add(type, msgs, tokens) - - if (this.options?.onMessagesAdded) { - await this.options.onMessagesAdded(type, msgs); - } } public async persistent(role: ChatRole, content: string): Promise; From a650d9e8615e286a6ab6f08d51b68bbd66698d15 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 29 Dec 2023 19:03:38 +0100 Subject: [PATCH 09/14] using llm and embedding apis from agent context --- apps/browser/lib/services/evo/EvoThread.ts | 24 ++++++++----------- .../lib/services/evo/createEvoInstance.ts | 12 ++-------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 14cf552e..494f368c 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -27,8 +27,6 @@ export interface EvoThreadConfig { export interface EvoThreadState { goal: string | undefined; evo: Evo | undefined; - llm: LlmApi | undefined; - embedding: EmbeddingApi | undefined; status: string | undefined; isRunning: boolean; isLoading: boolean; @@ -55,8 +53,6 @@ export interface EvoThreadStartOptions { const INIT_STATE: EvoThreadState = { goal: undefined, evo: undefined, - llm: undefined, - embedding: undefined, status: undefined, isRunning: false, isLoading: false, @@ -181,8 +177,8 @@ export class EvoThread { return; } - if (!this._state.evo || !this._state.llm || !this._state.embedding) { - const result = createEvoInstance( + if (!this._state.evo) { + const evo = createEvoInstance( this._state.workspace, options.openAiApiKey, this._config.onMessagesAdded, @@ -193,14 +189,12 @@ export class EvoThread { (error) => this._callbacks?.onError(error) ); - if (!result) { + if (!evo) { this.setIsRunning(false); return; } - this._state.evo = result.evo; - this._state.llm = result.llm; - this._state.embedding = result.embedding; + this._state.evo = evo; if (this._state.chat?.messages.length) { await this._state.evo.context.chat.addWithoutEvents( @@ -220,11 +214,13 @@ export class EvoThread { } } - if (this._state.llm instanceof ProxyLlmApi) { - this._state.llm.setGoalId(goalId); + const { llm, embedding } = this._state.evo.context; + + if (llm instanceof ProxyLlmApi) { + llm.setGoalId(goalId); } - if (this._state.embedding instanceof ProxyEmbeddingApi) { - this._state.embedding.setGoalId(goalId); + if (embedding instanceof ProxyEmbeddingApi) { + embedding.setGoalId(goalId); } // Run the evo instance against the goal diff --git a/apps/browser/lib/services/evo/createEvoInstance.ts b/apps/browser/lib/services/evo/createEvoInstance.ts index 633384ec..57b28348 100644 --- a/apps/browser/lib/services/evo/createEvoInstance.ts +++ b/apps/browser/lib/services/evo/createEvoInstance.ts @@ -32,11 +32,7 @@ export function createEvoInstance( onStatusUpdate: (status: string) => void, onGoalCapReached: () => void, onError: (error: string) => void -): { - evo: Evo, - llm: LlmApi, - embedding: EmbeddingApi -} | undefined { +): Evo | undefined { let llm: LlmApi; let embedding: EmbeddingApi; @@ -125,11 +121,7 @@ export function createEvoInstance( agentVariables ) ); - return { - evo, - llm, - embedding - }; + return evo; } catch (e: any) { onError(e.message); return undefined; From 9f4f1007c21942de93b0c7d60d89c16838c6db27 Mon Sep 17 00:00:00 2001 From: dOrgJelli Date: Sat, 30 Dec 2023 01:46:05 +0100 Subject: [PATCH 10/14] fix: loadChat works for guest users --- apps/browser/lib/hooks/useEvoService.ts | 9 ++++++++- apps/browser/lib/services/evo/EvoThread.ts | 13 ++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/browser/lib/hooks/useEvoService.ts b/apps/browser/lib/hooks/useEvoService.ts index 875fb8c4..e99900f3 100644 --- a/apps/browser/lib/hooks/useEvoService.ts +++ b/apps/browser/lib/hooks/useEvoService.ts @@ -115,7 +115,14 @@ export const useEvoService = ( const loadChat = async (chatId: string): Promise => { if (chatId === "") { - throw new Error("Cannot load chat for anonymous user."); + return { + id: chatId, + created_at: new Date().toISOString(), + title: null, + messages: [], + logs: [], + variables: new Map() + }; } const { data: chats, error } = await fetchChats(supabase!); diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 494f368c..b682f31e 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -7,8 +7,6 @@ import { ChatMessage, Workspace, InMemoryWorkspace, - EmbeddingApi, - LlmApi, } from "@evo-ninja/agents"; import { Chat } from "@/lib/queries/useChats"; @@ -30,7 +28,6 @@ export interface EvoThreadState { status: string | undefined; isRunning: boolean; isLoading: boolean; - logs: ChatLog[]; chat: Chat | undefined; workspace: Workspace; } @@ -56,7 +53,6 @@ const INIT_STATE: EvoThreadState = { status: undefined, isRunning: false, isLoading: false, - logs: [], chat: undefined, workspace: new InMemoryWorkspace() }; @@ -98,7 +94,6 @@ export class EvoThread { ]); thread._state.chat = results[0]; - thread._state.logs = results[0].logs; thread._state.workspace = results[1]; thread._state.isLoading = false; @@ -117,7 +112,7 @@ export class EvoThread { // Dispatch reset values this._callbacks.setStatus(INIT_STATE.status); this._callbacks.setIsRunning(INIT_STATE.isRunning); - this._callbacks.setChatLog(INIT_STATE.logs); + this._callbacks.setChatLog([]); this._callbacks.setWorkspace(undefined); // Disconnect all callbacks @@ -138,7 +133,7 @@ export class EvoThread { // Send current state to newly connected callbacks this._callbacks.setStatus(this._state.status); this._callbacks.setIsRunning(this._state.isRunning); - this._callbacks.setChatLog(this._state.logs); + this._callbacks.setChatLog(this._state.chat!.logs); await this._callbacks.setWorkspace(this._state.workspace); } @@ -243,8 +238,8 @@ export class EvoThread { } private async onChatLog(chatLog: ChatLog): Promise { - this._state.logs = [...this._state.logs, chatLog]; - this._callbacks?.setChatLog(this._state.logs); + this._state.chat!.logs = [...this._state.chat!.logs, chatLog]; + this._callbacks?.setChatLog(this._state.chat!.logs); await this._config.onChatLogAdded(chatLog); } From 1892a62cf4dc9ac43b43eb4d7b72abf3e2046d8e Mon Sep 17 00:00:00 2001 From: dOrgJelli Date: Sat, 30 Dec 2023 01:50:37 +0100 Subject: [PATCH 11/14] fix: don't recreate session on every page refocus --- apps/browser/components/providers/Providers.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/browser/components/providers/Providers.tsx b/apps/browser/components/providers/Providers.tsx index 6518870f..610ef036 100644 --- a/apps/browser/components/providers/Providers.tsx +++ b/apps/browser/components/providers/Providers.tsx @@ -8,7 +8,12 @@ import SupabaseClientProvider from "@/lib/supabase/SupabaseClientProvider"; export function Providers({ children }: { children: React.ReactNode }) { return ( - + From b597d0c704b7560afc40fe567f18e545a259d300 Mon Sep 17 00:00:00 2001 From: Cesar Date: Tue, 2 Jan 2024 16:10:20 +0100 Subject: [PATCH 12/14] fix: social icon buttons --- apps/browser/components/Sidebar.tsx | 38 +++++++++++++++++------------ 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/apps/browser/components/Sidebar.tsx b/apps/browser/components/Sidebar.tsx index 17bf3a4a..dd61604b 100644 --- a/apps/browser/components/Sidebar.tsx +++ b/apps/browser/components/Sidebar.tsx @@ -326,24 +326,30 @@ const Sidebar = ({ From 90a2be511c5d66a63ead87e8783e042f2323e101 Mon Sep 17 00:00:00 2001 From: Colin Spence Date: Tue, 2 Jan 2024 11:04:51 -0700 Subject: [PATCH 13/14] removes console.log --- apps/browser/components/modals/ChatDetails.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/browser/components/modals/ChatDetails.tsx b/apps/browser/components/modals/ChatDetails.tsx index ac98004e..1f29338c 100644 --- a/apps/browser/components/modals/ChatDetails.tsx +++ b/apps/browser/components/modals/ChatDetails.tsx @@ -47,7 +47,6 @@ export default function ChatDetails({ const logs_index = Object.keys(logs.details).length - 1; const stepTitle = Object.keys(logs.details)[logs_index]; const stepDetails = Object.values(logs.details)[logs_index]; - console.log("layouteffect", contentRefs.current[logs_index]); if (contentRefs.current[logs_index] && stepDetails.length > 0) { toggleStep(stepTitle, logs_index); setInitialToggle(true); From 7bd9b4f13f2b52533405f421722c6c68df1fee05 Mon Sep 17 00:00:00 2001 From: Cesar Date: Tue, 2 Jan 2024 20:40:20 +0100 Subject: [PATCH 14/14] chore: handle llm error in logs --- apps/browser/lib/utils/sanitizeLogsDetails.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/browser/lib/utils/sanitizeLogsDetails.ts b/apps/browser/lib/utils/sanitizeLogsDetails.ts index cf84743d..fcd32d80 100644 --- a/apps/browser/lib/utils/sanitizeLogsDetails.ts +++ b/apps/browser/lib/utils/sanitizeLogsDetails.ts @@ -36,6 +36,13 @@ export function sanitizeLogs(messages: ChatLog[]): MessageSet[] { return sanitizedLogs; } + // Sometimes the LLM request errors and returns "{}" + // This handles the error and explicitly returns an error message to the UI + if (!("title" in currentMessage) || currentMessage.title === "{}") { + currentSet.evoMessage = "An error has happened, please contact support if this continue happening"; + return sanitizedLogs; + } + // Only user message (goal) and evo's answer does not start with # // Since user message is handled above, we now for sure that its evo's answer if (!currentMessage.title.startsWith("#")) {