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);